LeakCanary RenderNodeAnimator instance memory leak

174 Views Asked by At

On my app, when I change theme, I get an instant memory leak notification from LeakCanary. The code I use to change theme:

fun execute(isDark: Boolean){
    if (isDark) {
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
    } else {
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
    }
}

on fragment:

// set sharedPref for theme
sharedPref = requireContext().getSharedPreferences(Constants.THEME_PREF, MODE_PRIVATE)
val theme = sharedPref!!.getBoolean(Constants.SWITCH_STATE_KEY, true)


switchButton?.let { switch ->
    switchButton?.isChecked = theme

    switch.setOnCheckedChangeListener { _, isChecked ->
        val editor = sharedPref?.edit()
        editor?.putBoolean(Constants.SWITCH_STATE_KEY, isChecked)
        editor?.apply()

        viewModel.changeTheme(isChecked)
    }
}

In all my fragments, I set every long-lasting object to null when onViewDestroy is called. For example:

override fun onDestroyView() {
    super.onDestroyView()
    viewModel.multipleCoinsListResponse.removeObservers(viewLifecycleOwner)
    walletAdapter = null
    navigation = null
    menuItem?.setOnMenuItemClickListener(null)
    menuItem = null
    menuProvider?.let { provider ->
        menuHost?.removeMenuProvider(provider)
        menuProvider = null
    }
    menuHost = null
    toolbar?.setOnClickListener(null)
    toolbar = null
    appBarLayout = null
    actionBar = null
    sharedPrefTheme = null
    sharedPrefRefresh = null
    mProgressDialog?.dismiss()
    mProgressDialog = null
    onBackPressedCallback?.remove()
    onBackPressedCallback = null
    constraintLayout?.removeAllViews()
    constraintLayout = null
    binding = null
}

Leak log states that there is a leak starting from RenderNodeAnimator and menuItem is totally leaking. Even though I set menuHost, menuProvider and menuItem to null when view is destroyed. On a forum, LeakCanary might find this RenderNodeAnimator leak when theme is changed. To test this, I tracked the app memory usage with Memory Profiler on Android Studio. When leak canary dependency was off and I try to change theme, I saw that there were no memory leaks when theme is switched. whenever I navigate to an another Fragment after switching theme, garbage collector was collecting just fine. Is this issue I am facing, should I ingore it or investigate further? Profiler is not detecting any major memory leaks but the second I switch theme, LeakCanary does. Am I missing something on my code? I'm open to any ideas or suggestions.

full leak log:

D/LeakCanary: ├─ android.graphics.animation.RenderNodeAnimator instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 243.9 kB in 6906 objects
D/LeakCanary: │    mListeners[0] = android.graphics.drawable.RippleAnimationSession$3
D/LeakCanary: │    ↓ RenderNodeAnimator.mTarget
D/LeakCanary: │                         ~~~~~~~
D/LeakCanary: ├─ android.graphics.RenderNode instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 241.8 kB in 6837 objects
D/LeakCanary: │    ↓ RenderNode.mAnimationHost
D/LeakCanary: │                 ~~~~~~~~~~~~~~
D/LeakCanary: ├─ android.view.ViewAnimationHostBridge instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 241.8 kB in 6836 objects
D/LeakCanary: │    ↓ ViewAnimationHostBridge.mView
D/LeakCanary: │                              ~~~~~
D/LeakCanary: ├─ androidx.appcompat.view.menu.ActionMenuItemView instance
D/LeakCanary: │    Leaking: YES (View.mContext references a destroyed activity)
D/LeakCanary: │    Retaining 241.7 kB in 6835 objects
D/LeakCanary: │    View not part of a window view hierarchy
D/LeakCanary: │    View.mAttachInfo is null (view detached)
D/LeakCanary: │    View.mID = R.id.actionSettings
D/LeakCanary: │    View.mWindowAttachCount = 1
D/LeakCanary: │    mContext instance of android.view.ContextThemeWrapper, wrapping activity com.example.investmenttracker.
D/LeakCanary: │    presentation.MainActivity with mDestroyed = true
D/LeakCanary: │    ↓ View.mContext
D/LeakCanary: ├─ android.view.ContextThemeWrapper instance
D/LeakCanary: │    Leaking: YES (ActionMenuItemView↑ is leaking and ContextThemeWrapper wraps an Activity with Activity.mDestroyed
D/LeakCanary: │    true)
D/LeakCanary: │    Retaining 177 B in 9 objects
D/LeakCanary: │    mBase instance of dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper, wrapping
D/LeakCanary: │    activity com.example.investmenttracker.presentation.MainActivity with mDestroyed = true
D/LeakCanary: │    ↓ ContextWrapper.mBase
D/LeakCanary: ├─ dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper instance
D/LeakCanary: │    Leaking: YES (ContextThemeWrapper↑ is leaking and ViewComponentManager$FragmentContextWrapper wraps an Activity
D/LeakCanary: │    with Activity.mDestroyed true)
D/LeakCanary: │    Retaining 40 B in 2 objects
D/LeakCanary: │    mBase instance of com.example.investmenttracker.presentation.MainActivity with mDestroyed = true
D/LeakCanary: │    ↓ ContextWrapper.mBase
D/LeakCanary: ╰→ com.example.investmenttracker.presentation.MainActivity instance
D/LeakCanary: ​     Leaking: YES (ObjectWatcher was watching this because com.example.investmenttracker.presentation.MainActivity
D/LeakCanary: ​     received Activity#onDestroy() callback and Activity#mDestroyed is true)
D/LeakCanary: ​     Retaining 147.7 kB in 4816 objects
D/LeakCanary: ​     key = b442ccea-1cb2-4f91-82c0-15f40b2e22e3
D/LeakCanary: ​     watchDurationMillis = 5504
D/LeakCanary: ​     retainedDurationMillis = 503
D/LeakCanary: ​     mApplication instance of com.example.investmenttracker.CoinWiseApp
D/LeakCanary: ​     mBase instance of androidx.appcompat.view.ContextThemeWrapper
0

There are 0 best solutions below