play-billing-samples: BillingBroadcastReceiver Memory Leak

After buying a item successfully, I am closing the current activity which holds purching process. But Leak Canary catch a memory leak about BillingBroadcastReceiver. I init billing client OnCreate and release onDestroy.

Here is my init method

billingClient = BillingClient.newBuilder(this).setListener(this).build();
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(int responseCode) {

                if (responseCode == BillingClient.BillingResponse.OK) {
                    // The billing client is ready. You can query purchases here.
                    loadProducts();
                } else {
                    // Error
                }

            }

            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                Timber.d("Connection Error");
            }
        });

Here is my release Method

        if (billingClient != null && billingClient.isReady()) {
            billingClient.endConnection();
            billingClient = null;
        }

OnPurchaseUpdated I made a service call and close this activity based on service result.

    public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases) {

        if (responseCode == BillingClient.BillingResponse.OK && purchases != null) { 
            for (Purchase purchase : purchases) {
                billingClient.consumeAsync(purchase.getPurchaseToken(), new ConsumeResponseListener() {
                    @Override
                    public void onConsumeResponse(int responseCode, String purchaseToken) {
                        if (responseCode == BillingClient.BillingResponse.OK && purchaseToken != null) {
                            Timber.d("onConsumeResponse --> %s", purchaseToken);
                            getViewModel().informPurchase(necessary data);
                        }
                    }
                });
            }
        } else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) {
            // Handle an error caused by a user canceling the purchase flow.
            Timber.d("Billing Cancelled");

        } else {
            Timber.d("An Error Occured");
        }
    }

Here is the Leak Canary error

BroadcastError

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 1
  • Comments: 24 (1 by maintainers)

Most upvoted comments

With billingClient 4.1.0 still have a similar leak. Looks like billingClient.endConnection() doesn’t destroy some Handler with a reference to some listener

    ┬───
    │ GC Root: System class
    │
    ├─ android.app.ActivityThread class
    │    Leaking: NO (MessageQueue↓ is not leaking and a class is never leaking)
    │    ↓ static ActivityThread.sMainThreadHandler
    ├─ android.app.ActivityThread$H instance
    │    Leaking: NO (MessageQueue↓ is not leaking)
    │    ↓ Handler.mQueue
    ├─ android.os.MessageQueue instance
    │    Leaking: NO (MessageQueue#mQuitting is false)
    │    HandlerThread: "main"
    │    ↓ MessageQueue[2]
    │                  ~~~
    ├─ android.os.Message instance
    │    Leaking: UNKNOWN
    │    Retaining 120 B in 4 objects
    │    Message.what = 0
    │    Message.when = 23510943 (16342 ms after heap dump)
    │    Message.obj = null
    │    Message.callback = instance @317222688 of com.android.billingclient.api.zzz
    │    Message.target = instance @317222768 of android.os.Handler
    │    ↓ Message.callback
    │              ~~~~~~~~
    ├─ com.android.billingclient.api.zzz instance
    │    Leaking: UNKNOWN
    │    Retaining 56 B in 3 objects
    │    ↓ zzz.zzb
    │          ~~~
    ├─ com.android.billingclient.api.zzy instance
    │    Leaking: UNKNOWN
    │    Retaining 12 B in 1 objects
    │    ↓ zzy.zza
    │          ~~~
    ├─ com.my.app.OrderFragment$1 instance
    │    Leaking: UNKNOWN
    │    Retaining 12 B in 1 objects
    │    Anonymous class implementing com.android.billingclient.api.SkuDetailsResponseListener
    │    ↓ OrderFragment$1.this$0
    │                      ~~~~~~
    ╰→ com.my.app.OrderFragment instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.my.app.OrderFragment received Fragment#onDestroy()
    ​     callback and Fragment#mFragmentManager is null)
    ​     Retaining 2,5 MB in 14148 objects
    ​     key = f04ce3ba-e9b2-470d-bfca-ad05e6f57e98
    ​     watchDurationMillis = 7928
    ​     retainedDurationMillis = 2928
    ​     mainActivity instance of com.my.app.MainActivity with mDestroyed = true
    ====================================
    0 LIBRARY LEAKS

I believe we found the issue: PBL is holding an activity context and didn’t free it. The issue has been fixed. It shall be rolled out with the next PBL release.

On Thu, Jan 7, 2021 at 2:29 AM haris15 notifications@github.com wrote:

@yusufceylan https://github.com/yusufceylan have you found any solution ? if yes then help me

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/android/play-billing-samples/issues/185#issuecomment-756030039, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOOVKZHP373H36O6C7ZAC2LSYWEJ7ANCNFSM4G6YKTAQ .

Same problem with me too. Can reproduce it by toggling between dark mode and light mode.

Might you be able to follow the TrivialDriveKotlin sample so as to decouple your billing implementation from your activity lifecycle?

You don’t need to check isReady() before calling endConnection(). Otherwise, the serviceConnection could still live (even if it is not ready) without being ended.

if (billingClient != null) {
    billingClient.endConnection();
    billingClient = null;
}