Principio de funcionamiento de LeakCanary 2.0 y explicación detallada

Una introducción a LeakCanary

LeakCanary es una herramienta para la detección de fugas de memoria en la plataforma Android, creada por la conocida empresa Square y de código abierto ( Square / LeakCanary ), que puede ayudar a los desarrolladores a reducir significativamente el problema de la aplicación que no responde  y el problema de bloqueo de OutOfMemoryError en la aplicación. En la actualidad, se suele utilizar en la etapa de desarrollo y prueba de la aplicación, y se detecta y repara con antelación.fugaCanario.png

1.1 Introducción a las fugas de memoria

En el desarrollo de Android, una pérdida de memoria es un error de programación que se manifiesta como una aplicación que retiene referencias a objetos que ya no se necesitan. No se pudo recuperar la memoria asignada para el objeto, lo que provocó un bloqueo de OutOfMemoryError (OOM). Para obtener una introducción detallada a las fugas de memoria, consulte: Resumen de fugas de memoria de Android

1.2 Ventajas y desventajas de LeakCanary

ventaja

  • Comprobación de fugas de memoria totalmente automatizada para componentes de actividad de Android
  • Algunos comportamientos se pueden personalizar (número de dumparchivos y objetos de rastreo de fugas, procesamiento personalizado de resultados de análisis, etc.)
  • Proceso de integración simple y bajo costo de uso
  • Visualización y notificación de interfaz amigable

defecto

  • No apto para monitoreo en línea
  • No se pueden detectar problemas de OOM causados ​​por la solicitud de memoria de gran capacidad, la memoria de mapa de bits no se libera

1.3 Cómo funciona LeakCanary

Una vez que se instala LeakCanary, detectará e informará automáticamente las fugas de memoria en 4 pasos:

  1. Detectar objetos no recogidos por GC
  2. montón de volcado
  3. Analizar el montón
  4. Clasificar derrames

1.3.1 Detección de objetos que no han sido reclamados por GC

LeakCanary Hook 到 Android lifecycle 以自动检测 Activitis 和 Fragments 何时被 Destroy 并且被 GC 回收。这些被 Destroy 的对象被传递给一个 ObjectWatcher,它持有对它们的弱引用。LeakCanary 能够自动检测以下对象的泄漏:

  • 被销毁的 Activity实例
  • 被销毁的 Fragment实例
  • 被销毁的 fragmentView实例
  • 被清除 ViewModel实例

可以查看任意一个不再使用的对象,例如 detached view 或 destroyed presenter:

AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")

如果在等待 5 秒并运行 GC 回收后,ObjectWatcher持有的弱引用没有被清除,则该对象被认为是未被回收的,并且可能会产生泄漏。LeakCanary 就会将这些对象记录到 Logcat:

D LeakCanary: Watching instance of com.example.leakcanary.MainActivity
  (Activity received Activity#onDestroy() callback) 

... 5 seconds later ...

D LeakCanary: Scheduling check for retained objects because found new object
  retained

LeakCanary 在转储堆之前等待未被回收对象(retained objects)的计数达到阈值,并显示具有最新计数的通知。 notificación-retenida.png

D LeakCanary: Rescheduling check for retained objects in 2000ms because found only 4 retained objects (< 5 while app visible)

App 处于前台时默认阈值为 5 个 retained objects,App 处于后台时默认阈值为 1 个 retained object。如果看到 retained objects通知,然后将 App 置于后台(例如通过按下 Home 按钮),则阈值从 5 变为 1,并且 LeakCanary 会在 5 秒内转储堆。点击通知会强制 LeakCanary 立即转储堆。

1.3.2 转储堆

当未被回收对象的数量达到阈值时,LeakCanary 将 Java 堆 dump 到 Android 文件系统中的.hprof文件(堆转储)中(请参阅 LeakCanary 在哪里存储堆转储? )。转储堆会在短时间内冻结应用程序,在此期间 LeakCanary 显示以下 toast: dumping-toast.png

1.3.3 分析堆

LeakCanary 使用 Shark 来解析 .hprof 文件并在该堆转储中定位未被回收的对象。 encontrar-retenido-notificación.png

对于每个未被回收对象,LeakCanary 会找到阻止该对象被 GC 垃圾回收的引用路径:它的 leak tracenotificación-de-rastros-de-fugas-del-edificio.png

分析完成后,LeakCanary 会显示一个带有摘要的通知,并将结果打印在 Logcat中。请注意下面 4 个未被回收的对象是如何被分组为两种不同的泄漏项。LeakCanary 为每个 leak trace 创建一个签名,并将具有相同签名的泄漏项划分在一组,即由相同错误引起的泄漏。 análisis-hecho (1).png

====================================
HEAP ANALYSIS RESULT
====================================
2 APPLICATION LEAKS

Displaying only 1 leak trace out of 2 with the same signature
Signature: ce9dee3a1feb859fd3b3a9ff51e3ddfd8efbc6
┬───
│ GC Root: Local variable in native code
│
...

点击通知会启动一个提供更多详细信息的 Activity。稍后通过点击 LeakCanary 启动器图标再次返回它: lanzador.png

每行对应一组具有相同签名的泄漏项。LeakCanary 在应用程序第一次使用该签名触发泄漏时将一行标记为 Newheap-dump.png

点击泄漏项以打开其 leak trace显示详情。可以通过下拉菜单在不同的泄漏对象间切换。 pantalla-de-fugas.png

泄漏签名是导致泄漏的每个引用的串联哈希,即每个引用都显示有红色下划线: firma.png

leak trace以文本形式共享时,这些相同的可疑引用会带有下划线~~~

...
│  
├─ com.example.leakcanary.LeakingSingleton class
│    Leaking: NO (a class is never leaking)
│    ↓ static LeakingSingleton.leakedViews
│                              ~~~~~~~~~~~
├─ java.util.ArrayList instance
│    Leaking: UNKNOWN
│    ↓ ArrayList.elementData
│                ~~~~~~~~~~~
├─ java.lang.Object[] array
│    Leaking: UNKNOWN
│    ↓ Object[].[0]
│               ~~~
├─ android.widget.TextView instance
│    Leaking: YES (View.mContext references a destroyed activity)
...

在上面示例中,泄漏签名的计算方式为:

val leakSignature = sha1Hash(
    "com.example.leakcanary.LeakingSingleton.leakedView" +
    "java.util.ArrayList.elementData" +
    "java.lang.Object[].[x]"
)
println(leakSignature)
// dbfa277d7e5624792e8b60bc950cd164190a11aa

1.3.4 对泄漏进行分类

LeakCanary 将它在 App 中发现的泄漏分为两类:Application Leaks 和 Library LeaksLibrary Leaks是 App 中依赖的三方代码库中的已知错误引起的泄漏。此泄漏会直接影响到 App 的表现,但开发者无法直接在 App 中修复它,因此 LeakCanary 将其分离出来。

这两个类别在 Logcat中打印的结果中是分开的:

====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS

====================================
1 LIBRARY LEAK

...
┬───
│ GC Root: Local variable in native code
│
...

LeakCanary 在其泄漏列表中标记为 Library Leakbiblioteca-fuga.png

LeakCanary 附带一个已知泄漏的数据库,它通过对引用名称的模式匹配来识别它。例如

Leak pattern: instance field android.app.Activity$1#this$0
Description: Android Q added a new IRequestFinishCallback$Stub class [...]
┬───
│ GC Root: Global variable in native code
│
├─ android.app.Activity$1 instance
│    Leaking: UNKNOWN
│    Anonymous subclass of android.app.IRequestFinishCallback$Stub
│    ↓ Activity$1.this$0
│                 ~~~~~~
╰→ com.example.MainActivity instance

可以在 AndroidReferenceMatchers 类中查看已知泄漏的完整列表

二 LeakCanary 使用

2.1 引入依赖

首先,需要将 leakcanary-android 依赖添加到项目的 app’s build.gradle 中:

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}

因为 LeakCanary 有以下问题,所以通常只会使用在线下 debug 阶段,release 版本中不会引入 LeakCanary。

  • 每次内存泄漏以后,都会生成并解析 hprof 文件,容易引起手机卡顿等问题
  • 多次调用 GC,可能会对线上性能产生影响
  • hprof 文件较大,信息回捞成问题

然后,可过滤 Logcat 中的标签来确认 LeakCanary 在启动时是否成功运行:

D LeakCanary: LeakCanary is running and ready to detect leaks

2.2 配置 LeakCanary

因为 LeakCanary 2.0 版本后完全使用 Kotlin 重写,只需引入依赖,不需要初始化代码,就能执行内存泄漏检测。

当然也可以在自定义 Application 的 onCreate方法对 LeakCanary 进行一些自定义配置:

class LeakApplication: Application() {
    override fun onCreate() {
        super.onCreate()
        leakCanaryConfig()
    }
    private fun leakCanaryConfig() {
        //App 处于前台时检测保留对象的阈值,默认是 5
        LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3)
        //自定义要检测的保留对象类型,默认监测 Activity,Fragment,FragmentViews 和 ViewModels
        AppWatcher.config= AppWatcher.config.copy(watchFragmentViews = false)
        //隐藏泄漏显示活动启动器图标,默认为 true
        LeakCanary.showLeakDisplayActivityLauncherIcon(false)
    }
}

2.3 检测内存泄漏

以下,举一例非静态内部类导致的内存泄漏,如何使用 LeakCanary 监控其异常,代码如下所示:

class LeakTestActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_leak_test)
        val leakThread = LeakThread()
        leakThread.start()
    }

    // LeakThread 定义为 LeakTestActivity 的内部类
    inner class LeakThread : Thread() {
        override fun run() {
            super.run()
            try {
                //线程内耗时操作
                sleep(6 * 60 * 1000)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
        }
    }
}

LeakTestActivity 存在内存泄漏,原因就是非静态内部类 LeakThread 持有外部类 LeakTestActivity 的引用,LeakThread 中做了耗时操作,导致 LeakTestActivity 无法被释放。 运行 App 程序,这时会在 Launch 界面生成一个名为 Leaks 的应用图标。接下来跳转到 App 的 LeakTestActivity 页面并不断地切换横竖屏,4 次切换后屏幕会弹出提示:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过 Notification 展示出来,如下图所示

Screenshot_2022-07-17-19-56-16-79_03d3fd918927e238aec8c5190edea983.jpg

Notification 中提示了 LeakTestActivity 发生了内存泄漏,有 4 个对象未被回收。点击 Notification 就可以进入内存泄漏详细页,除此之外也可以通过 Leaks 应用的列表界面进入,列表界面如下图所示。

Screenshot_2022-07-17-19-56-26-45_03d3fd918927e238aec8c5190edea983.jpg

内存泄漏详细页如下图所示:

Screenshot_2022-07-17-19-56-31-96_03d3fd918927e238aec8c5190edea983.jpg

Todo el detalle es una cadena de referencia: la clase interna LeakThread de LeakTestActivity hace referencia a este $ 0 de LeakThread, el significado de este $ 0 es una referencia a la clase externa que la clase interna retiene automáticamente, y esta clase externa es la LeakTestActivity dada en el último línea de la instancia de detalles, lo que hará que LeakTestActivity no pueda ser GC, lo que provocará una pérdida de memoria.

La solución es cambiar LeakThread a una clase interna estática. Ejecutar el programa LeakThread nuevamente no dará indicios de una pérdida de memoria.

    ...
    companion object {
        class LeakThread : Thread() {
            override fun run() {
                super.run()
                try {
                    sleep(6 * 60 * 1000)
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
            }
        }
    }

referencias

Introducción a LeakCanary

Optimización de la memoria de Android (6) Explicación detallada del uso de LeakCanary

Supongo que te gusta

Origin juejin.im/post/7121320019878903816
Recomendado
Clasificación