Análisis de LeakCanary: un canario elegante

LeakCanary es una biblioteca de código abierto de Square. Puede detectar pérdidas de memoria durante la ejecución de la Aplicación. Cuando se produce una pérdida de memoria, se generará una cadena de referencia del objeto filtrado y se notificará al desarrollador del programa.

Di un pequeño punto de conocimiento que es la guinda del pastel:

En el siglo XVII, los mineros británicos descubrieron que los canarios son muy sensibles al gas. Incluso si hay una pequeña cantidad de gas en el aire, el canario dejará de cantar; y cuando el contenido de gas exceda un cierto límite, aunque los humanos aburridos no lo sepan, el canario ha sido envenenado y asesinado durante mucho tiempo. En ese momento, con el equipo minero relativamente rudimentario, los trabajadores traían un canario como "indicador de detección de gas" cada vez que bajaban al pozo para evacuación de emergencia en situaciones peligrosas.

LeakCanary es un canario que puede detectar con sensibilidad las pérdidas de memoria para ayudarnos a evitar el peligro de OOM tanto como sea posible. Este concepto también se refleja en el diseño de su logotipo:

Cómo inicializar

La introducción de LeakCanary solo necesita agregar el siguiente código al archivo build.gradle de la aplicación

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

「¡Eso es todo, no es necesario cambiar el código!」

¿Por qué se puede hacer en una sola línea?

class AppWatcherInstaller : ContentProvider() {
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application) // auto install 
    return true
  }
}

El ContentProvider definido en el manifiesto se inicializará automáticamente cuando se inicie el proceso de la aplicación principal, y AppWatcher.manualInstall también se puede llamar automáticamente. ¿Crees que esto es muy elegante y que todas las bibliotecas de terceros tienen ContentProvider completo? Inicio de la aplicación de Jetpack para aprender sobre los envases basados ​​en este principio. https://developer.android.google.cn/topic/libraries/app-startup

Entonces solo quiero inicializar manualmente (como la optimización de la velocidad de inicio), ¿qué debo hacer?

La respuesta está en la definición de este ContentProvider:

<provider
    android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
    android:authorities="com.sam.demo.leakcanary-installer"
    android:enabled="@bool/leak_canary_watcher_auto_install" // here
    android:exported="false" />

// 我们在 app 重写这个 bool 值就可以取消自动安装
<resources>
  <bool name="leak_canary_watcher_auto_install">false</bool>
</resources>
// 然后手动在合适的地方调用 AppWatcher.manualInstall 初始化

Pero, de nuevo, esta biblioteca se está ejecutando en el entorno de depuración, que estaría inactiva para optimizar la versión de depuración, net install X.

emmm, viene la pregunta, ¿qué me pasará en la liberación? Realice los siguientes cambios, seleccione release en Build Variants y ejecútelo:

dependencies {
  implementation 'com.squareup.leakcanary:leakcanary-android:2.4'
}

Dangdang, tan pronto como abrí la aplicación, se informó un error:

? E: FATAL EXCEPTION: main
    Process: com.sam.demo, PID: 28838
    java.lang.Error: LeakCanary in non-debuggable build
    
    LeakCanary should only be used in debug builds, but this APK is not debuggable.
    Please follow the instructions on the "Getting started" page to only include LeakCanary in
    debug builds: https://square.github.io/leakcanary/getting_started/
    
    If you're sure you want to include LeakCanary in a non-debuggable build, follow the 
    instructions here: https://square.github.io/leakcanary/recipes/#leakcanary-in-release-builds
        at leakcanary.internal.InternalLeakCanary.checkRunningInDebuggableBuild(InternalLeakCanary.kt:160)

La cadena de llamadas donde se informa el error

   AppWatcher.manualInstall(application) 
-> InternalAppWatcher.install(application)
-> nternalAppWatcher.onAppWatcherInstalled(application)
-> InternalLeakCanary.invoke()
-> InternalLeakCanary.checkRunningInDebuggableBuild()

  private fun checkRunningInDebuggableBuild() {
    if (isDebuggableBuild) {
      return
    }
    if (!application.resources.getBoolean(R.bool.leak_canary_allow_in_non_debuggable_build)) {
      throw Error(...)
    }
  }

Entonces, si realmente desea introducir fugaCanary en el lanzamiento, simplemente:

// 在 app 重写这个 bool 值
// 自己开发 SDK 时,这种配置方式学会了吗?
<resources>
  <bool name="leak_canary_allow_in_non_debuggable_build">true</bool>
</resources>

Cuándo escuchar las fugas

Actividad

No hay nada que decir, a través de registerActivityLifecycleCallbacks para monitorear las devoluciones de llamada del ciclo de vida de la actividad, cuando onActivityDestroyed, objectWatcher.watch (actividad,…)

Fragmento 、 fragment.view

Está intentando agregar monitoreo desde diferentes fragmentManagers (modo de estrategia emmm)

  • O (Oreo) y superior -> activity.fragmentManager (AndroidOFragmentDestroyWatcher.kt)
  • androidX -> activity.supportFragmentManager (AndroidXFragmentDestroyWatcher.kt)
  • soporte 包 -> activity.supportFragmentManager (AndroidSupportFragmentDestroyWatcher.kt)

Llame a fragmentManager.registerFragmentLifecycleCallbacks para supervisar.

Y la actividad utilizada anteriormente también se obtiene a través de onActivityCreated de registerActivityLifecycleCallbacks

AndroidOFragmentDestroyWatcher.kt

override fun onFragmentViewDestroyed(
  fm: FragmentManager,
  fragment: Fragment
) {
  val view = fragment.view
  // 观察 view 是否回收
  objectWatcher.watch(view,...) 
}

override fun onFragmentDestroyed(
  fm: FragmentManager,
  fragment: Fragment
) {
  // 观察 fragment 对象是否回收
  objectWatcher.watch(fragment,...) 
}

ViewModel

En AndroidXFragmentDestroyWatcher.kt mencionado anteriormente, habrá monitoreo adicional

 override fun onFragmentCreated(
   fm: FragmentManager,
   fragment: Fragment,
   savedInstanceState: Bundle?
 ) {
   ViewModelClearedWatcher.install(fragment, objectWatcher, configProvider)
 }

La implementación específica de install es tomar un ViewModelClearedWatcher en el ViewModelProvider de este fragmento. Este también es un ViewModel, cuando se recicla, volverá a llamar al método onCleared para agregar todos los ViewModels a la observación

init {
  // We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
  // however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
  // does not have ViewModelStore#keys. All versions currently have the mMap field.
  // 通过反射获取以这个 fragment 为 onwer 所有的 viewModel
  viewModelMap = try {
    val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
    mMapField.isAccessible = true
    @Suppress("UNCHECKED_CAST")
    mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
  } 
}

override fun onCleared() {
  if (viewModelMap != null && configProvider().watchViewModels) {
    viewModelMap.values.forEach { viewModel ->
      objectWatcher.watch( viewModel, ... )
    }
  }
}

Detecta si se filtra un objeto

Los fundamentos de una JVM:

/**
 * <p> Suppose that the garbage collector determines at a certain point in time
 * that an object is <a href="package-summary.html#reachability">weakly
 * reachable</a>.  At that time it will atomically clear all weak references to
 * that object and all weak references to any other weakly-reachable objects
 * from which that object is reachable through a chain of strong and soft
 * references.  At the same time it will declare all of the formerly
 * weakly-reachable objects to be finalizable.  At the same time or at some
 * later time it will enqueue those newly-cleared weak references that are
 * registered with reference queues.
 */
public class WeakReference<T> extends Reference<T> {
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

WeakReference en Java es un tipo de referencia débil. Siempre que se produzca un GC, si el objeto que contiene no está retenido por otras referencias fuertes, el objeto al que se refiere se reciclará y, al mismo tiempo o más tarde, esta WeakReference se pondrá en cola al ReferenceQueue La detección de fugas de memoria en LeakCanary se basa en este principio.

Puntos de implementación:

  • Cuando es necesario reciclar un objeto, se genera una clave correspondiente, se encapsula en una KeyedWeakReference personalizada y se pasa una ReferenceQueue personalizada al constructor KeyedWeakReference.
  • Al mismo tiempo, esta KeyedWeakReference se almacenará en caché en el mapa (ObjectWatcher.watchedObjects)
  • Finalmente, el GC se activa activamente, atravesando todos los registros en la ReferenceQueue personalizada y eliminando los elementos correspondientes en el Mapa de acuerdo con el valor de la clave en la KeyedWeakReference obtenida.

"Después de los 3 pasos anteriores, lo que queda en el mapa son: los objetos que debería recopilar el GC, pero que en realidad todavía están en la memoria, es decir, los objetos filtrados".

Veamos el código específico:

ObjectWatcher.kt
fun watch(
   watchedObject: Any,
   description: String
 ) {
   // 遍历 queue ,从 watchedObjects 移除相应项
   removeWeaklyReachableObjects() 
 
   val key = UUID.randomUUID().toString()
   val watchUptimeMillis = clock.uptimeMillis()
   val reference =
     KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
   }

   watchedObjects[key] = reference
   checkRetainedExecutor.execute {
     // checkRetainedExecutor 通过 Handler.postDelayed 实现
     // 默认延迟 5s , 去保留的对象里查看一下这个 key 是否还在
     moveToRetained(key)
   }
 }

 fun moveToRetained(key: String) {
   removeWeaklyReachableObjects() // 再检查一遍是否已经回收
   val retainedRef = watchedObjects[key]
   if (retainedRef != null) {
     retainedRef.retainedUptimeMillis = clock.uptimeMillis()
     onObjectRetainedListeners.forEach { it.onObjectRetained() }
   }
 }

Si el objeto aún está marcado después de 5S, llame al método onObjectRetained para notificar el procesamiento, se llama a la implementación de InternalLeakCanary

override fun onObjectRetained() {
  if (this::heapDumpTrigger.isInitialized) {
    // 看名字就知道,这是触发 heap dump 相关的逻辑
    heapDumpTrigger.onObjectRetained()
  }
}

Luego mire las llamadas relacionadas en HeapDumpTrigger:

   onObjectRetained()
-> scheduleRetainedObjectCheck(...)
-> checkRetainedObjects(reason)


ivate fun checkRetainedObjects(reason: String) {
val config = configProvider()

var retainedReferenceCount = objectWatcher.retainedObjectCount

if (retainedReferenceCount > 0) {
  // 执行一次 GC ,再来看还剩下多少对象未被回收
  // 小细节:GC 之后还 sleep(100) 等回收的引用入队
  gcTrigger.runGc() 
  retainedReferenceCount = objectWatcher.retainedObjectCount
}

// checkRetainedCount 以下两种情况 return true 不继续后面的流程
// 1. 若之前有显示有泄漏,且当前已经全部回收,显示无泄漏的通知  
// 2. 存留的对象超过 5 个(默认,可配)且 (app 可见或不可见超过 5s), 
//    延迟 2s 再进行检查(避免应用卡频繁卡) 
// 判断 app 是否可见代码:VisibilityTracker.kt
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
  // 如果正在调试,可能影响检测结果,还是晚点再试吧
  scheduleRetainedObjectCheck(
        reason = "debugger is attached",
        rescheduling = true,
        delayMillis = WAIT_FOR_DEBUG_MILLIS
    )
    return
  }

  val now = SystemClock.uptimeMillis()
  val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
  if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
    // 一分钟之内才 dump 过,等离上次 dump 1分钟了再来吧
    scheduleRetainedObjectCheck(
        reason = "previous heap dump was ${xx}ms ago (< ${xx}ms)",
        rescheduling = true,
        delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
    )
    return
  }
  
  // 终极操作
  // 通过 Debug.dumpHprofData(filePath)  dump heap
  // objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) 清除这次 dump 开始以前的所有引用
  // HeapAnalyzerService.runAnalysis 通过一个 IntentService 去分析 heap
  dumpHeap(retainedReferenceCount, retry = true) 
}

En HeapAnalyzerService, se llama a la biblioteca Shark para analizar el montón y los resultados del análisis se devuelven a DefaultOnHeapAnalyzedListener.onHeapAnalyzed para almacenar los resultados del análisis en la base de datos y enviar mensajes de notificación.

Shark: Shark es el analizador de montón que impulsa LeakCanary 2. Es una biblioteca de análisis de montón independiente de Kotlin que se ejecuta a  「alta velocidad」  con una  「huella de memoria baja」 .

Todavía recuerdo que la versión anterior usaba una  biblioteca llamada "jaja" producida por Square. No  importa si la biblioteca está tan bien escrita, es asombroso cómo puedes nombrarla.

Sesión de huevos de pascua

El tiburón muerde al canario, y los que aman al pájaro lo condenan enérgicamente (cabeza de perro manual)

                   ^`.                 .=""=.
   ^_              \  \               / _  _ \
   \ \             {   \             |  d  b  |
   {  \           /     `~~~--__     \   /\   /
   {   \___----~~'              `~~-_/'-=\/=-'\,
    \                         /// a  `~.      \ \
    / /~~~~-, ,__.    ,      ///  __,,,,)      \ |
    \/      \/    `~~~;   ,---~~-_`/ \        / \/
                     /   /            '.    .'
                    '._.'             _|`~~`|_
                                      /|\  /|\

Hay huevos de pascua, me encanta leer el código fuente, de: https://github.com/square/leakcanary/blob/main/docs/shark.md

para resumir

Este artículo analiza la inicialización de LeakCanary, el momento para agregar la supervisión de objetos y el proceso y principio de detección de fugas de memoria. Descubrimos que esta biblioteca no solo es fácil de usar, sino también muy inteligente de implementar. La lectura de dicho código fuente no solo puede hacer frente a las entrevistas, sino que también puede ayudar a escribir un código hermoso en el trabajo ordinario.

Este artículo se basa en: "fugacanary-android: 2.4"

Al final

Aquí también comparto un PDF de aprendizaje de Android + video de arquitectura + documento de entrevista + notas de origen , mapa mental avanzado de tecnología de arquitectura avanzada, materiales especiales de entrevistas de desarrollo de Android, materiales de arquitectura avanzada avanzada recopilados y organizados por varios grandes .

Estos son materiales excelentes que leeré una y otra vez en mi tiempo libre. Puede ayudar eficazmente a todos a dominar el conocimiento y a comprender los principios. Por supuesto, también puede utilizarlo para comprobar si hay omisiones y mejorar su competitividad.

Si lo necesitas, puedes  conseguirlo aquí

Si te gusta este artículo, también puedes darme un pequeño me gusta, dejar un mensaje en el área de comentarios o reenviarlo y apoyarlo ~

Supongo que te gusta

Origin blog.csdn.net/River_ly/article/details/107214841
Recomendado
Clasificación