切换深色/浅色模式后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
相关产品推荐
相关产品推荐

