androidkotlinmemory-leaksleakcanary

Android MainActivity Data Leak after Changing app to Darkmode


I have a basic android app for now, where there's 2 fragments they are showing text only and 1 bottom navigation bar The app checks if the default mode is Darkmode or no so i can update my design... For some reason after changing the app to dark mode or light mode, The app flashes and onDestroy is called and there's a Memory Leak

LeakCanary Log:

====================================
1 APPLICATION LEAKS

References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.

175008 bytes retained by leaking objects
Signature: 11f05db2bd6fd24ef9e96dc39221a0e6e79ac535
┬───
│ GC Root: System class
│
├─ android.net.ConnectivityManager class
│    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
│    ↓ static ConnectivityManager.sInstance
├─ android.net.ConnectivityManager instance
│    Leaking: NO (MainActivity↓ is not leaking)
│    mContext instance of com.yousefelsayed.example.activity.MainActivity with mDestroyed = false
│    ↓ ConnectivityManager.mContext
├─ com.yousefelsayed.example.activity.MainActivity instance
│    Leaking: NO (DecorView↓ is not leaking and Activity#mDestroyed is false)
│    mApplication instance of android.app.Application
│    mBase instance of androidx.appcompat.view.ContextThemeWrapper
│    ↓ Activity.mDecor
├─ com.android.internal.policy.DecorView instance
│    Leaking: NO (View attached)
│    View is part of a window view hierarchy
│    View.mAttachInfo is not null (view attached)
│    View.mWindowAttachCount = 1
│    mContext instance of com.android.internal.policy.DecorContext, wrapping activity com.yousefelsayed.example.
│    activity.MainActivity with mDestroyed = false
│    ↓ DecorView.mMSActions
│                ~~~~~~~~~~
├─ com.samsung.android.multiwindow.MultiSplitActions instance
│    Leaking: UNKNOWN
│    Retaining 43 B in 1 objects
│    ↓ MultiSplitActions.mWindow
│                        ~~~~~~~
├─ com.android.internal.policy.PhoneWindow instance
│    Leaking: YES (Window#mDestroyed is true)
│    Retaining 15.0 kB in 285 objects
│    mContext instance of com.yousefelsayed.example.activity.MainActivity with mDestroyed = true
│    mOnWindowDismissedCallback instance of com.yousefelsayed.example.activity.MainActivity with mDestroyed = true
│    ↓ Window.mContext
╰→ com.yousefelsayed.example.activity.MainActivity instance
​     Leaking: YES (ObjectWatcher was watching this because com.yousefelsayed.example.activity.MainActivity received
​     Activity#onDestroy() callback and Activity#mDestroyed is true)
​     Retaining 175.0 kB in 3213 objects
​     key = b5185190-4e4a-405f-845a-c271e3a3fd46
​     watchDurationMillis = 5639
​     retainedDurationMillis = 638
​     mApplication instance of android.app.Application
​     mBase instance of androidx.appcompat.view.ContextThemeWrapper
====================================

onCreate

//Views
private lateinit var view: ActivityMainBinding
private lateinit var navController: NavController

//Backend
private lateinit var sp: SharedPreferences

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    view = DataBindingUtil.setContentView(this, R.layout.activity_main)
    init()
    setupLightMode(sp.getInt("DarkMode",0))

}

init() fun

private fun init(){
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragmentView) as NavHostFragment
    navController = navHostFragment.findNavController()
    sp = getSharedPreferences("Example",0)
    //setup bottomNav
    view.bottomNav.setupWithNavController(navController)
    //check for darkMode to setupValues, Default value is 3 to make sure if it's app first run
    if (sp.getInt("DarkMode",3) == 3){
        Log.d("Debug","FirstAppRun")
        when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
            Configuration.UI_MODE_NIGHT_YES -> {
                view.lightModeImageView.setImageResource(R.drawable.ic_baseline_dark_mode_24)
                sp.edit().putInt("DarkMode",1).apply()
            }
            Configuration.UI_MODE_NIGHT_NO -> {
                view.lightModeImageView.setImageResource(R.drawable.ic_baseline_wb_sunny_24)
                sp.edit().putInt("DarkMode",0).apply()
            }
        }
    }
}

setupLightMode() fun

private fun setupLightMode(darkMode: Int){
    if (darkMode == 0){
        view.lightModeImageView.setImageResource(R.drawable.ic_baseline_wb_sunny_24)
        AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_NO)
    }else if(darkMode == 1) {
        view.lightModeImageView.setImageResource(R.drawable.ic_baseline_dark_mode_24)
        AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_YES)
    }
}

Thanks


Solution

  • Here's what the leaktrace you shared tells us:

    So what does this mean? Samsung phones have custom Android features, and one of those features seem to be the ability to split windows, and they've done changes to the Android Framework to support that. Unfortunately, the MultiSplitActions object that seems to help with that keeps a reference to an old window instead of updating the reference as the activity gets reconfigured, and therefore causes a leak.

    What can you do? Not much, besides reaching out to Samsung to let them know there's a leak they should fix.