You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

切换深色/浅色模式后Android MainActivity内存泄漏问题求助

问题:深色/浅色模式切换导致MainActivity内存泄漏及闪屏问题

我开发了一个基础Android应用,包含2个仅显示文本的Fragment和1个底部导航栏。应用会检测系统默认深色/浅色模式来更新UI,但切换模式后会出现闪屏,MainActivity的onDestroy被调用,且存在内存泄漏。

LeakCanary泄漏日志

====================================
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
====================================

相关代码片段

MainActivity 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() 函数

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() 函数

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)
    }
}

解决方案

1. 分析泄漏原因

从日志可见,泄漏链指向三星设备的MultiSplitActions组件,它通过DecorView.mMSActions持有了已销毁的MainActivity引用,属于系统框架层面的问题,但可通过反射主动清理。

2. 修复内存泄漏

在MainActivity的onDestroy方法中,通过反射清理DecorView的mMSActions引用:

override fun onDestroy() {
    super.onDestroy()
    // 仅针对三星设备清理多窗口相关引用
    try {
        val decorView = window.decorView
        val msActionsField = decorView.javaClass.getDeclaredField("mMSActions")
        msActionsField.isAccessible = true
        msActionsField.set(decorView, null)
    } catch (e: NoSuchFieldException) {
        // 非三星设备无此字段,忽略
    } catch (e: IllegalAccessException) {
        e.printStackTrace()
    }
}

3. 优化深色模式设置逻辑

当前每次Activity创建时调用setDefaultNightMode会触发Activity重建,导致闪屏。优化逻辑:

  • 检查当前模式是否与目标模式一致,避免重复设置:
private fun setupLightMode(darkMode: Int) {
    val currentMode = AppCompatDelegate.getDefaultNightMode()
    when(darkMode) {
        0 -> {
            if (currentMode != AppCompatDelegate.MODE_NIGHT_NO) {
                view.lightModeImageView.setImageResource(R.drawable.ic_baseline_wb_sunny_24)
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
            }
        }
        1 -> {
            if (currentMode != AppCompatDelegate.MODE_NIGHT_YES) {
                view.lightModeImageView.setImageResource(R.drawable.ic_baseline_dark_mode_24)
                AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
            }
        }
    }
}

4. 使用Application Context获取SharedPreferences

避免用Activity上下文持有SharedPreferences引用,改用全局上下文:

// 替换init()中的sp初始化
sp = applicationContext.getSharedPreferences("Example", Context.MODE_PRIVATE)

5. 可选:避免Activity重建(不推荐)

若想彻底避免闪屏,可在AndroidManifest.xml中给MainActivity添加配置变更声明,但会丢失系统自动适配的UI逻辑,需自行处理所有配置变更:

<activity
    android:name=".activity.MainActivity"
    android:configChanges="uiMode|orientation|screenSize">
    ...
</activity>

内容的提问来源于stack exchange,提问作者Yousef Elsayed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 14:10:48