How to fix memory leak in android.app.OpApplicationPackageManagerInjector reported by LeakCanary?

Viewed 231

After spending a lot of time googling, I was unsuccessful in finding the root cause, hence the fix for the issue. Please find details of the leak reported by LeakCanary below:

Here is the memory leak reported by LeakCanary:

┬───
│ GC Root: System class
│
├─ android.app.OpApplicationPackageManagerInjector class
│    Leaking: NO (a class is never leaking)
│    ↓ static OpApplicationPackageManagerInjector.opIconPackHelper
│                                                 ~~~~~~~~~~~~~~~~
├─ xne.xne.xne instance
│    Leaking: UNKNOWN
│    Retaining 251 B in 7 objects
│    mContext instance of android.app.ContextImpl
│    ↓ xne.mContext
│          ~~~~~~~~
├─ android.app.ContextImpl instance
│    Leaking: UNKNOWN
│    Retaining 5.1 kB in 61 objects
│    mAutofillClient instance of a.b.c.settings.SettingsActivity
│    with mDestroyed = true
│    mOuterContext instance of android.app.ContextImpl
│    ContextImpl.mOuterContext == ContextImpl.this: not tied to any particular
│    lifecycle
│    ↓ ContextImpl.mAutofillClient
│                  ~~~~~~~~~~~~~~~
╰→ a.b.c.settings.SettingsActivity instance
​     Leaking: YES (ObjectWatcher was watching this because a.b.
​     c.settings.SettingsActivity received Activity#onDestroy()
​     callback and Activity#mDestroyed is true)
​     Retaining 118.0 kB in 2396 objects
​     key = 03a1e35f-6b30-43ad-8fdd-f1e639b84f65
​     watchDurationMillis = 5182
​     retainedDurationMillis = 182
​     mApplication instance of a.b.c.App
​     mBase instance of androidx.appcompat.view.ContextThemeWrapper

METADATA

Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: OnePlus
LeakCanary version: 2.6
App process name: a.b.c
Count of retained yet cleared: 5 KeyedWeakReference instances
Stats: LruCache[maxSize=3000,hits=7082,misses=89380,hitRate=7%]
RandomAccess[bytes=4439965,reads=89380,travel=47383456311,range=25592742,size=31
984708]
Heap dump reason: 9 retained objects, app is visible
Analysis duration: 4531 ms

Here is the SettingsActivity:

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import androidx.annotation.Nullable;

import com.google.android.material.appbar.MaterialToolbar;
import a.b.c.R;
import a.b.c.common.navigation.NavHostFragmentActivity;

public class SettingsActivity extends NavHostFragmentActivity {

    private static final String ACTION_LANGUAGE_CHANGED = "a.b.c.language.changed";

    private MaterialToolbar toolbar;

    public static Intent openIntent(Context context) {
        return new Intent(context, SettingsActivity.class);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        toolbar = findViewById(R.id.toolbar);
    }

    public void onLanguageChanged() {
        setResult(Activity.RESULT_OK, new Intent().setAction(ACTION_LANGUAGE_CHANGED));
        finish();
    }

    public static boolean isLanguageChanged(int resultCode, @Nullable Intent data) {
        return resultCode == Activity.RESULT_OK && data != null && ACTION_LANGUAGE_CHANGED.equals(data.getAction());
    }

    @Override
    public int getNavigationGraphId() {
        return R.navigation.nav_graph_settings;
    }

    @Override
    protected int getContentView() {
        return R.layout.activity_settings;
    }

    public MaterialToolbar getToolbar() {
        return toolbar;
    }
}

Here is the NavHostFragmentActivity:

import android.os.Bundle;

import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;

import a.b.c.R;
import a.b.c.common.BaseActivity;

import javax.inject.Inject;

abstract public class NavHostFragmentActivity extends BaseActivity implements NavigationGraphProvider {

    @Inject
    NavHostFragmentPresenter navHostFragmentPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        navHostFragmentPresenter.onCreate(this, this, getContentView(), savedInstanceState);
    }

    @LayoutRes
    protected int getContentView() {
        return R.layout.activity_nav_host_fragment;
    }

}

Here is the NavHostFragmentPresenter:

import android.os.Bundle;

import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.fragment.NavHostFragment;

import a.b.c.R;

import javax.inject.Inject;

public class NavHostFragmentPresenter {

    @Inject
    NavHostFragmentPresenter() {
    }

    public void onCreate(AppCompatActivity activity,
                         NavigationGraphProvider navigationGraphProvider,
                         @Nullable Bundle savedInstanceState) {
        onCreate(activity, navigationGraphProvider, R.layout.activity_nav_host_fragment, savedInstanceState);
    }

    public void onCreate(AppCompatActivity activity,
                         NavigationGraphProvider navigationGraphProvider,
                         @LayoutRes int contentView,
                         @Nullable Bundle savedInstanceState) {
        activity.setContentView(contentView);
        if (savedInstanceState == null) {
            NavHostFragment fragment = NavHostFragment.create(navigationGraphProvider.getNavigationGraphId());
            activity.getSupportFragmentManager()
                    .beginTransaction()
                    .replace(R.id.nav_host_fragment, fragment)
                    .commit();
        }
    }
}
0 Answers
Related