12

I'm attempting to implement Chrome Custom Tabs and detecting a memory leak through LeakCanary.

The demo application does not appear to leak unless we add another Activity layer (i.e. MainActivity launches Activity2, which binds/unbinds to the custom tab service and launches the url -- everything the MainActivity does in the demo app).

MainActivity looks like this:

public class MainActivity extends Activity implements OnClickListener {
    private Button mLaunchButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LeakCanary.install(getApplication());

        setContentView(R.layout.main);

        mLaunchButton = (Button) findViewById(R.id.launch_button);
        mLaunchButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        int viewId = v.getId();

        if (viewId == R.id.launch_button) {
            Intent intent = new Intent(getApplicationContext(), Activity2.class);
            startActivity(intent);
        }
    }
}

Returning from Activity2 to MainActivity will cause this leak:

09-04 13:49:26.783  10456-12161/org.chromium.customtabsclient.example D/LeakCanary﹕ In org.chromium.customtabsclient.example:1.0:1.
09-04 13:49:26.783  10456-12161/org.chromium.customtabsclient.example D/LeakCanary﹕ * org.chromium.customtabsclient.Activity2 has leaked:
09-04 13:49:26.783  10456-12161/org.chromium.customtabsclient.example D/LeakCanary﹕ * GC ROOT android.support.customtabs.CustomTabsClient$1.val$callback (anonymous class extends android.support.customtabs.ICustomTabsCallback$Stub)
09-04 13:49:26.783  10456-12161/org.chromium.customtabsclient.example D/LeakCanary﹕ * references org.chromium.customtabsclient.Activity2$2.this$0 (anonymous class extends android.support.customtabs.CustomTabsCallback)
09-04 13:49:26.783  10456-12161/org.chromium.customtabsclient.example D/LeakCanary﹕ * leaks org.chromium.customtabsclient.Activity2 instance

https://gist.github.com/abvanpelt/ddbc732f31550b09fc27

My question is: is this a bug in the demo application? (Maybe unbindCustomTabsService() is missing some needed teardown?) Or is this a bug in the Chrome Custom Tabs library itself?

Thank you.

Allison
  • 501
  • 4
  • 15

2 Answers2

5

Found the Answer for this question -

If you are launching customTab as follow

private void launchChromeCustomTab(final Context context, final Uri uri) {

     mServiceConnection = new CustomTabsServiceConnection() {
        @Override
        public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabsClient client) {
            client.warmup(0L);
            final CustomTabsIntent intent = new CustomTabsIntent.Builder().build();
            intent.launchUrl(context, uri);
            mIsCustomTabsLaunched = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    CustomTabsClient.bindCustomTabsService(context, "com.android.chrome", mServiceConnection);
}

Then you need to unbind this mServiceConnection onDestroy method as -

@Override
protected void onDestroy() {
    super.onDestroy();
    this.unbindService(mServiceConnection);
    mServiceConnection = null;
}

That will stop throwing

android.app.ServiceConnectionLeaked: Activity <Your_Activity> has leaked ServiceConnection 
Manisha
  • 813
  • 13
  • 24
  • 3
    Ideally, you'd want to connect to the CustomTabs service on onCreate or onStart, so Chrome is spinned in the background, before the tab is opened. The implementation on launchChromeCustomTab seems to connect tot he service when the tab is opened. This is likely to null the benefits of connecting to the service and would have similar results to just creating the Intent and calling launchUrl directly. – andreban Mar 08 '18 at 08:00
  • It appears to me that CustomTabsServiceConnection brings nothing aside from the memory leak warning and a lot of GC actions during idle time, no benefits, a silly service. – LXJ Apr 04 '21 at 11:29
1

The MainActivity in the sample creates an instance of CustomTabsServiceConnection and CustomTabsCallback as anonymous inner classes.

If you change them to be static inner classes, therefore removing the this reference to the MainActivity, and set the references to the MainActivity as WeakReferences, you will see that LeakCanary stops reporting about the MainActivity leaking.

Now, you may still see leak canary report about the ServiceConnection leaking if you set it to watch that object. The reason is that it is linked to the Chrome service and cannot be cleaned by GC until GC also runs on the server side.

I created a test that binds and unbinds the Service in a loop and I've confirmed that the ServiceConnections are indeed being collected after a while.

So, the Demo can be improved in order to avoid the ServiceConnection holding a reference to the MainActivity, avoiding having a heavy object like an Activity being alive a long time after the service is disconnected, and it's not a problem with the Custom Tabs library.

andreban
  • 4,621
  • 1
  • 20
  • 49
  • Thanks, this fixed that leak. Strangely, I'm seeing a different leak (documented here: https://code.google.com/p/chromium/issues/detail?id=473146) which was fixed in Chromium 42. I'm on Chromium 44 and still seeing a `ResourcesContextWrapperFactory` leak. :/ – Allison Sep 11 '15 at 22:32
  • As an update, the demo app on github has been updated to avoid the memory leak. – andreban Oct 10 '15 at 17:28
  • :( Could not do anything about this leak issue, Still looking for good solution. I am having same issue in my application where it keeps complaining about the has leaked ServiceConnection – Manisha Feb 21 '17 at 01:31