Android 内存泄漏排查方法
Android 内存泄漏排查方法
内存泄漏是对象已经失去业务价值,却仍被一条强引用链持有。页面泄漏会让视图、图片和业务数据一起留在内存中,重复进入后可能引发卡顿甚至内存溢出。排查时不应只盯着某个大对象,而要找到从长期存活对象到目标对象的完整引用路径。
一、先确认是否真的泄漏
垃圾回收不是立即发生,图片缓存和对象池也会主动保留内存。单次退出页面后内存没有立刻下降,不足以证明泄漏。
更可靠的方法是重复执行相同进入和退出路径,触发合适的内存分析,观察目标 Activity、Fragment 或 View 实例数量是否持续增长。
二、Context 持有错误
单例、静态集合和长生命周期服务如果持有页面 Context,会连带保留整棵视图树。
class ConfigurationStore(context: Context) {
private val appContext = context.applicationContext
}
只有不需要主题、窗口和页面能力时才可使用应用上下文。需要页面 Context 的对象,应把生命周期限制在页面范围内,而不是简单替换成应用上下文。
三、Fragment ViewBinding 泄漏
Fragment 对象可能继续存在,而它的视图已经销毁。绑定引用必须在 onDestroyView 清除。
private var _binding: FragmentDetailBinding? = null
private val binding: FragmentDetailBinding
get() = checkNotNull(_binding)
override fun onDestroyView() {
recyclerView.adapter = null
_binding = null
super.onDestroyView()
}
适配器是否需要解除取决于它是否通过观察者或回调持有页面。应遵循项目组件约定,不必对所有简单适配器机械清空。
四、匿名内部类与回调
长期任务、事件总线和全局管理器保存回调时,闭包可能捕获页面。
override fun onStart() {
super.onStart()
accountManager.addListener(accountListener)
}
override fun onStop() {
accountManager.removeListener(accountListener)
super.onStop()
}
注册与解除应成对,并选择与业务可见性一致的生命周期。重复注册还可能导致事件执行多次。
五、协程与异步任务
页面相关任务应使用 lifecycleScope 或视图生命周期作用域,页面状态任务使用 viewModelScope。自建全局作用域会让任务失去停止条件。
即使协程最终会结束,大对象也可能在任务完成前被闭包持有。无限流、轮询和无法取消的底层调用尤其需要检查。
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect(::render)
}
}
六、Handler 与延迟任务
延迟消息如果持有页面回调,会延长页面生命周期。页面销毁时应移除仍无价值的任务,或者使用生命周期感知调度方式。
不要通过弱引用掩盖本应取消的任务。先取消无用工作,再根据所有权决定引用方式。
七、动画和监听器
属性动画、转场、视图树监听和文本监听都可能保留目标视图。持续动画在视图离开窗口后应停止,临时监听器完成使命后立即移除。
自定义 View 可以在 onDetachedFromWindow 中释放只属于挂载期间的动画和回调,但如果 View 会短暂分离再复用,要确保重新挂载时能正确恢复。
八、WebView 的特殊成本
WebView 本身资源较重,创建时使用的 Context、加入的容器和原生桥接都可能形成引用。应由项目统一组件管理创建、移除和销毁顺序。
销毁前先停止加载、移出父容器并解除业务桥接。具体步骤遵循现有封装和最低系统兼容要求,不在业务页面复制一套不一致实现。
九、从引用链定位责任
发现未释放对象后,沿强引用链向根节点查看:
- 静态字段或单例集合。
- 正在运行的线程和任务。
- 系统消息队列中的回调。
- 全局观察者或监听器。
- 父视图、窗口或适配器。
引用链中的最后一个业务对象通常是修复入口。不要看到目标对象就随意把字段改成弱引用,弱引用可能破坏必要所有权。
十、验证修复
修复后重复原路径,确认实例能够在预期时间回收,同时验证功能没有因为过早解除引用失效。还要观察:
- 配置变化后监听是否重新注册。
- 页面返回后任务是否正确恢复。
- 适配器和弹窗是否仍能处理事件。
- 后台业务任务是否被误取消。
总结
内存泄漏排查的本质是分析所有权。通过重复操作确认增长,再沿根引用找到长期持有者,重点检查 Context、绑定、监听器、协程、动画和重型视图。正确修复通常是缩短任务或订阅生命周期,而不是到处添加弱引用。
- 点赞
- 收藏
- 关注作者
评论(0)