Android-性能优化-02-内存优化-内存泄露

300 阅读4分钟

内存泄露(Memory Leak)是指程序中某些对象的内存得不到释放,即使这些对象已经不再被使用,但由于仍然有引用指向它们,垃圾回收器(GC)无法回收这些内存,从而导致内存占用越来越高,最终可能导致应用崩溃。

通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所有的引用链,当一个对象到GC Roots 没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的.

image.png


1. 内存泄露的常见原因

(1) 静态引用持有对象

  • 静态变量的生命周期与应用进程一致,如果静态变量引用了某个长生命周期对象,就会导致该对象无法被回收。

    示例:

    kotlin
    复制代码
    object Singleton {
        var context: Context? = null
    }
    

    如果 Singleton.context 持有 Activity 的引用,即使 Activity 被销毁,它的内存也无法释放。


(2) Handler 导致的泄露

  • 当使用匿名或非静态内部类定义 Handler 时,它会隐式持有外部类的引用(通常是 ActivityFragment)。

  • 如果消息处理队列中仍有未处理的消息,即使 Activity 被销毁,引用关系仍然存在,导致内存泄露。

    示例:

    kotlin
    复制代码
    private val handler = Handler(Looper.getMainLooper()) {
        // 操作与 Activity 相关的逻辑
        true
    }
    

(3) 监听器未解绑

  • 在生命周期中注册的监听器(如广播接收器、观察者模式等)没有在合适的时机解除绑定。

    示例:

    kotlin
    复制代码
    LocalBroadcastManager.getInstance(context).registerReceiver(receiver, intentFilter)
    // 忘记调用 unregisterReceiver
    

(4) 内部类或匿名类

  • 非静态内部类和匿名类会隐式持有外部类的引用,导致外部类的生命周期延长。

    示例:

    kotlin
    复制代码
    class ExampleActivity : AppCompatActivity() {
        private val runnable = Runnable {
            // 持有外部 Activity 的引用
        }
    }
    

(5) WebView 导致的泄露

  • WebView 持有大量资源(如缓存、JS 引擎)。如果没有正确清理,WebView 的内存无法释放。

(6) Bitmap 和 Drawable

  • 大型图片对象未及时回收,或者被 ImageView 等控件引用,导致内存占用增加。

    示例:

    kotlin
    复制代码
    imageView.setImageBitmap(bitmap)
    // Activity 销毁后,bitmap 仍被引用,无法释放
    

2. 检测内存泄露的方法

(1) LeakCanary

  • 一款广泛使用的 Android 内存泄露检测工具。

  • 在 Debug 模式下使用,能自动检测内存泄露并生成报告。

    集成方式:

    groovy
    复制代码
    implementation 'com.squareup.leakcanary:leakcanary-android:2.x'
    

(2) Android Studio Profiler

  • 使用 Memory Profiler 查看内存分配和对象引用。
  • 可以手动触发垃圾回收,检查泄露对象是否存在。

(3) Debug API

  • 使用 Debug.dumpHprofData() 导出内存堆文件(.hprof),通过工具(如 MAT,Memory Analyzer Tool)分析内存泄露。

3. 解决内存泄露的策略

(1) 避免静态引用 Context 或 Activity

  • 如果必须使用 Context,尽量使用 ApplicationContext,因为它的生命周期与应用一致。

    错误示例:

    kotlin
    复制代码
    object Singleton {
        var context: Context? = null
    }
    

    正确示例:

    kotlin
    复制代码
    object Singleton {
        lateinit var context: Context
            private set
    
        fun init(applicationContext: Context) {
            context = applicationContext
        }
    }
    

(2) 静态内部类处理 Handler

  • Handler 定义为静态内部类,并持有 WeakReference 指向外部类,避免隐式引用。

    示例:

    kotlin
    复制代码
    private class MyHandler(activity: ExampleActivity) : Handler(Looper.getMainLooper()) {
        private val activityRef = WeakReference(activity)
    
        override fun handleMessage(msg: Message) {
            val activity = activityRef.get() ?: return
            // 操作 activity
        }
    }
    

(3) 生命周期中解绑监听器

  • onDestroyonStop 中解绑监听器。

    示例:

    kotlin
    复制代码
    override fun onDestroy() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
        super.onDestroy()
    }
    

(4) WebView 的销毁

  • Activity 销毁时,正确释放 WebView。

    示例:

    kotlin
    复制代码
    override fun onDestroy() {
        webView.apply {
            loadUrl("about:blank")
            clearHistory()
            removeAllViews()
            destroy()
        }
        super.onDestroy()
    }
    

(5) 及时清理 Bitmap 和 Drawable

  • 使用完 Bitmap 或 Drawable 后,及时调用 recycle() 或解除引用。

    示例:

    kotlin
    复制代码
    override fun onDestroy() {
        imageView.setImageDrawable(null)
        super.onDestroy()
    }
    

4. 内存泄露的危害

  • 内存占用增加:无法释放的对象长期占用内存,最终导致内存不足。
  • 性能下降:应用运行缓慢,GC 频率增加。
  • 应用崩溃:因内存耗尽触发 OOM。

5. 总结

  • 预防关键:关注对象生命周期,避免无意的长时间引用。
  • 检测手段:使用 LeakCanary、Memory Profiler 等工具及时发现问题。
  • 优化实践:使用弱引用(WeakReference)、解绑监听器、清理大型资源,确保内存及时回收。

及时发现和解决内存泄露问题,对提升应用性能和稳定性至关重要。