定义
- 在Android中,内存泄漏指的是不再被使用的对象,被意外持有引用,而无法被垃圾回收,即长生命周期的对象持有了短生命周期的对象。长期累计会导致APP内存占用不断上升。
常见的导致内存泄漏的方式
-
静态/单例持有:Context/Activity被赋值给静态变量持有
-
内部类:内部类以及匿名内部类会隐式持有外部类的引用。如果Activity等的内部类执行异步操作、延迟任务,当外部Activity被销毁后,内部类还在运行延迟被其他线程持有,导致引用的Activity无法释放,导致泄漏
-
Handler:Handler关联到其线程的Looper,持有对外部的引用。
- 如果Handler中发送延迟消息,未处理/取消的时候Activity销毁了,则Handler引用的Activity也会泄漏.
- 例如使用handler.removeCallbacksAndMessages(null)取消全部消息
-
Activity/Fragment中注册了广播/回调,但是没有在销毁的时候取消。
-
资源对象未关闭:File Socket Cursor Stream等资源没有及时关闭。
内存泄漏检测
- LeakCanary由Square开源,可以在APP运行的时候,自动检测内存泄漏。
- 原理是hook Activity/Fragment的生命周期(Application.ActivityLifecycleCallbacks),在销毁后用ObjectWatcher弱引用Activity/Fragment,5s后手动GC,并检测这些弱引用对象是否被回收。
- 未回收则表明出现了内存泄漏,此时heap dump:Debug.dumpHprofData(),通过HPROF解析模块(给予hprof-conv改写的Kotlin实现)转为可操作的内存模型,包括实例表,类信息以及引用关系图。
- 解析完成后,从内存对象回溯到GC Roots,生成报告。
- Android Studio的Profiler。手动捕获heap dump,进行内存泄漏寻找,泄漏路径定位。
一个具体的例子
class LeakActivity : AppCompatActivity() {
// 主线程的handler
private val mHandler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_leak)
// 模拟内存泄漏
mHandler.postDelayed({
// 这里会导致内存泄漏,因为Runnable持有Activity的引用
run {
// 模拟一些操作
println("Doing something...")
}
}, 100_000)
}
// override fun onDestroy() {
// super.onDestroy()
// // 这里可以清除Handler的消息,避免内存泄漏
// mHandler.removeCallbacksAndMessages(null)
// }
}
启动这个LeakActivity后,因为延迟消息会延迟100秒后处理,所以100秒内关闭这个Activity,理论上应该销毁回收,但是实际上仍然被主线程的MessageQueue引用而导致泄漏,可以通过AndroidStudio的Profiler进行泄漏引用链路观察
可见,泄漏的链路是:
LeakActivity
----(被引用)--->
ExternalSyntheticLambda0(post的lambda代码块)
----(被引用)--->
Message
----(被引用)--->
MessageQueue
----(被引用)--->
Looper
----(被引用)--->
ActivityThread$H
GC Root: Looper (主线程常驻)
└── MessageQueue
└── Message
└── Runnable lambda
└── LeakActivity
解决的方式就是在onDestroy的时候,释放掉延迟中的消息mHandler.removeCallbacksAndMessages(null)。