Android内存泄漏一文搞清

98 阅读2分钟

定义

  • 在Android中,内存泄漏指的是不再被使用的对象,被意外持有引用,而无法被垃圾回收,即长生命周期的对象持有了短生命周期的对象。长期累计会导致APP内存占用不断上升。

常见的导致内存泄漏的方式

  1. 静态/单例持有:Context/Activity被赋值给静态变量持有

  2. 内部类:内部类以及匿名内部类会隐式持有外部类的引用。如果Activity等的内部类执行异步操作、延迟任务,当外部Activity被销毁后,内部类还在运行延迟被其他线程持有,导致引用的Activity无法释放,导致泄漏

  3. Handler:Handler关联到其线程的Looper,持有对外部的引用。

  • 如果Handler中发送延迟消息,未处理/取消的时候Activity销毁了,则Handler引用的Activity也会泄漏.
  • 例如使用handler.removeCallbacksAndMessages(null)取消全部消息
  1. Activity/Fragment中注册了广播/回调,但是没有在销毁的时候取消。

  2. 资源对象未关闭:File Socket Cursor Stream等资源没有及时关闭。

内存泄漏检测

  1. LeakCanary由Square开源,可以在APP运行的时候,自动检测内存泄漏。
  • 原理是hook Activity/Fragment的生命周期(Application.ActivityLifecycleCallbacks),在销毁后用ObjectWatcher弱引用Activity/Fragment,5s后手动GC,并检测这些弱引用对象是否被回收。
  • 未回收则表明出现了内存泄漏,此时heap dump:Debug.dumpHprofData(),通过HPROF解析模块(给予hprof-conv改写的Kotlin实现)转为可操作的内存模型,包括实例表,类信息以及引用关系图。
  • 解析完成后,从内存对象回溯到GC Roots,生成报告。
  1. 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进行泄漏引用链路观察

image.png

可见,泄漏的链路是: LeakActivity ----(被引用)---> ExternalSyntheticLambda0(post的lambda代码块)  ----(被引用)---> Message ----(被引用)---> MessageQueue ----(被引用)---> Looper ----(被引用)---> ActivityThread$H

GC Root: Looper (主线程常驻) 
  └── MessageQueue 
        └── Message 
              └── Runnable lambda
                    └── LeakActivity

解决的方式就是在onDestroy的时候,释放掉延迟中的消息mHandler.removeCallbacksAndMessages(null)。