androidmemory-leaksandroid-memory

WindowManagerGlobal.sDefaultWindowManager memory leak


I am using navigation components, and get a memory leak while exiting the application..

Here is the LeakCanary logcat

    ┬───
    │ GC Root: System class
    │
    ├─ android.view.WindowManagerGlobal class
    │    Leaking: NO (a class is never leaking)
    │    ↓ static WindowManagerGlobal.sDefaultWindowManager
    │                                 ~~~~~~~~~~~~~~~~~~~~~
    ├─ android.view.WindowManagerGlobal instance
    │    Leaking: UNKNOWN
    │    ↓ WindowManagerGlobal.mViews
    │                          ~~~~~~
    ├─ java.util.ArrayList instance
    │    Leaking: UNKNOWN
    │    ↓ ArrayList.elementData
    │                ~~~~~~~~~~~
    ├─ java.lang.Object[] array
    │    Leaking: UNKNOWN
    │    ↓ Object[].[0]
    │               ~~~
    ├─ android.widget.LinearLayout instance
    │    Leaking: YES (View.mContext references a destroyed activity)
    │    mContext instance of .....ui.MainActivity with mDestroyed = true
    │    View#mParent is set
    │    View#mAttachInfo is not null (view attached)
    │    View.mWindowAttachCount = 1
    │    ↓ LinearLayout.mContext
    ╰→ .....ui.MainActivity instance
    ​     Leaking: YES (ObjectWatcher was watching this because .....ui.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
    ​     key = 4a23bf94-97b5-4f44-87d2-6f9a59fc053f
    ​     watchDurationMillis = 5135
    ​     retainedDurationMillis = 134

My activity code:


public class MainActivity extends AppCompatActivity {

    private NavController mNavController;
    private AppBarConfiguration mAppBarConfiguration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupStatusBar();

        // Reset the shared prefs values only for the first launch of the app.
        PreferenceManager.setDefaultValues(this, R.xml.settings, false);

    }


    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        // return for API-19+
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
            return;

        // Customize Activity to not overlap with the bottom software buttons navigation bar (back, home, & menu)
        boolean hasMenuKey = ViewConfiguration.get(this).hasPermanentMenuKey();
        boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);

        if (!hasMenuKey && !hasBackKey) { // check if this device has a bottom navigation bar
            try {
                ConstraintLayout rootLayout = findViewById(R.id.activity_root);
                int NAVIGATION_BAR_HEIGHT = 70;
                rootLayout.getLayoutParams().height = rootLayout.getHeight() - NAVIGATION_BAR_HEIGHT;

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void setupStatusBar() {
        // Making status bar transparent and overlapping with the activity
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
            getWindow().setFlags(FLAG_LAYOUT_NO_LIMITS, FLAG_LAYOUT_NO_LIMITS);
    }

    public void setupActionBar() {
        mNavController = Navigation.findNavController(this, R.id.nav_host_fragment);
        mAppBarConfiguration = new AppBarConfiguration.Builder(
                R.id.mainFragment, R.id.readFragment) // remove up button from all these fragments
                .build();
        NavigationUI.setupActionBarWithNavController(this, mNavController, mAppBarConfiguration);

    }



    @Override
    public boolean onSupportNavigateUp() {
        return NavigationUI.navigateUp(mNavController, mAppBarConfiguration) // navigateUp tries to pop the back stack
                || super.onSupportNavigateUp();
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        getWindow().clearFlags( WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
        mAppBarConfiguration = null;
        mNavController = null;
    }
}

I tried to clear window flags I set, in onDestroy(), and even tried to set mAppBarConfiguration & mNavController to null, without success.

Also in the main fragment I set mContext to null in onDestroyView()

Note: I don't have any LinearLayout's in xml layouts.


Solution

  • I set activity Support ActionBar dynamically in some fragments in the navigation components; after setting the actionbar, I call setupActionBar() in order to adjust the new set action bar with the navigation components:

    In fragment

    Toolbar toolbar = requireView().findViewById(R.id.toolbar);
    ((MainActivity) requireActivity()).setSupportActionBar(toolbar);
    ((MainActivity) requireActivity()).setupActionBar();
    

    I solved the WindowManagerGlobal.sDefaultWindowManager memory leak by setting the action bar back to null in onDestroy() of the activity.

    @Override
    protected void onDestroy() {
        super.onDestroy();
        setSupportActionBar(null);
    }