LeakCanary最新2.8.1版本源碼 原理分析 [2022年初kotlin版]

首先從LeakCanary的使用開(kāi)始講,接著會(huì)到底層分析源碼邏輯

kotlin新版本如何使用

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

只需要這樣一步就搞定了.

默認(rèn)監(jiān)測(cè)哪些泄漏

官方網(wǎng)站的說(shuō)明,無(wú)侵入式依賴,會(huì)自動(dòng)給注入如下幾個(gè)模塊的內(nèi)存泄漏監(jiān)聽(tīng)

LeakCanary automatically detects leaks of the following objects:

destroyed Activity instances
destroyed Fragment instances
destroyed fragment View instances
cleared ViewModel instances

整體工作流程

它會(huì)經(jīng)過(guò)下面4個(gè)步驟來(lái)完成所有的工作.

  • Detecting retained objects. 監(jiān)測(cè)保留未被回收的對(duì)象
  • Dumping the heap. 轉(zhuǎn)儲(chǔ)堆區(qū)
  • Analyzing the heap. 分析堆區(qū)
  • Categorizing leaks. 堆泄漏進(jìn)行分來(lái)

監(jiān)測(cè)未被回收的對(duì)象

在前臺(tái)可見(jiàn)的時(shí)候,是監(jiān)聽(tīng)到有5個(gè)未回收的對(duì)象就會(huì)開(kāi)始dump

在后臺(tái)不可見(jiàn)的時(shí)候,是監(jiān)聽(tīng)到有1個(gè)未被回收的對(duì)象就會(huì)開(kāi)始dump.

2秒鐘監(jiān)測(cè)一次,dump的周期是5秒鐘一次.

轉(zhuǎn)儲(chǔ)堆

當(dāng)達(dá)到上面的閾值情況下,就會(huì)觸發(fā)dump,
生成.hprof文件

分析堆區(qū)

現(xiàn)在是通過(guò)Shark來(lái)分析

泄漏分類

################################

步入正題,死磕源碼.

編譯后的文件里會(huì)有自動(dòng)注入一些provider和activity.

如圖所示


1.png
2.png
3.png

1: ProcessLifecycleOwnerInitializer

androidx.lifecycle.ProcessLifecycleOwnerInitializer

這是安卓系統(tǒng)自帶的一個(gè)ContentProvider

在onCreate方法里主要做了2個(gè)操作

LifecycleDispatcher.init(getContext());
ProcessLifecycleOwner.init(getContext());

1.1: LifecycleDispatcher

//底層會(huì)在application中把這個(gè)callback納入application的維護(hù)范疇內(nèi)
((Application) context.getApplicationContext())
                .registerActivityLifecycleCallbacks(new DispatcherActivityCallback());

在注意看DispatcherActivityCallback中其實(shí)就做了一個(gè)事情

    @VisibleForTesting
    static class DispatcherActivityCallback extends EmptyActivityLifecycleCallbacks {

        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
           //核心就是這句了.
            ReportFragment.injectIfNeededIn(activity);
        }
..........省略.......................
    }

上面的回調(diào)Callback 是Application中的一個(gè)接口<ActivityLifecycleCallbacks>,同時(shí)Application中維護(hù)了一個(gè)ArrayList<ActivityLifecycleCallbacks>

 @UnsupportedAppUsage
    private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
            new ArrayList<ActivityLifecycleCallbacks>();

1.2: ProcessLifecycleOwner

  static void init(Context context) {
        sInstance.attach(context);
    }
void attach(Context context) {
        mHandler = new Handler();
        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
        Application app = (Application) context.getApplicationContext();
        //核心還是下面這行代碼了 注冊(cè)activity的生命周期回調(diào)
        app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
            @Override
            public void onActivityPreCreated(@NonNull Activity activity,
                    @Nullable Bundle savedInstanceState) {
                activity.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
                .......省略.......
                onActivityPostStarted
                .......省略.......
                onActivityPostResumed
               .......省略.......
                });
            }

            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                if (Build.VERSION.SDK_INT < 29) {
                    ReportFragment.get(activity).setProcessListener(mInitializationListener);
                }
            }
          .......省略.......
        });
    }

**總結(jié): 上面的2個(gè)生命周期注冊(cè)回調(diào) ,最終都是在Application類中處理的.

public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
        synchronized (mActivityLifecycleCallbacks) {
            mActivityLifecycleCallbacks.add(callback);
        }
    }

    public void unregisterActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
        synchronized (mActivityLifecycleCallbacks) {
            mActivityLifecycleCallbacks.remove(callback);
        }
    }

2: LeakCanaryFileProvider

leakcanary.internal.LeakCanaryFileProvider

這個(gè)類我也沒(méi)看懂具體看嘛的, 大概意思就是操作file類時(shí)使用到.

3: MainProcessAppWatcherInstaller

leakcanary.internal.MainProcessAppWatcherInstaller

這個(gè)類也是集成了ContentProvider, 替代了以前老版本LeackCanary手動(dòng)install, 在這個(gè)類的onCreate方法中會(huì)自動(dòng)執(zhí)行如下操作[神來(lái)之筆]

 override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }

3.1: 核心代碼

 @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  ) {
  //校驗(yàn)當(dāng)前是否在主線程  Looper.getMainLooper().thread === Thread.currentThread()
    checkMainThread()
    if (isInstalled) {
      throw IllegalStateException(
        "AppWatcher already installed, see exception cause for prior install call", installCause
      )
    }
    check(retainedDelayMillis >= 0) {
      "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
    }
    installCause = RuntimeException("manualInstall() first called here")
    this.retainedDelayMillis = retainedDelayMillis
    if (application.isDebuggableBuild) {
    //debug模式 打開(kāi)日志開(kāi)關(guān)
      LogcatSharkLog.install()
    }
    // Requires AppWatcher.objectWatcher to be set
    LeakCanaryDelegate.loadLeakCanary(application)

    watchersToInstall.forEach {
      it.install()
    }
  }

3.2: 反射加載InternalLeakCanary

  @Suppress("UNCHECKED_CAST")
  val loadLeakCanary by lazy {
    try {
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE")
        .get(null) as (Application) -> Unit
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
  }

上面?zhèn)鲄s Application會(huì)執(zhí)行到invoke方法

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
..................省略................

override fun invoke(application: Application) {
    _application = application
 //校驗(yàn)是否開(kāi)啟了只在debug模式使用. 設(shè)計(jì)原理只給debug時(shí)候使用
    checkRunningInDebuggableBuild()
    //創(chuàng)建AppWatcher對(duì)象 同時(shí)設(shè)置監(jiān)聽(tīng)
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
    //GC回收工具
    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }
    //創(chuàng)建異步線程
    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    //異步線程用于后臺(tái)服務(wù)
    val backgroundHandler = Handler(handlerThread.looper)

    heapDumpTrigger = HeapDumpTrigger(
      application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger,
      configProvider
    )
    //監(jiān)聽(tīng)?wèi)?yīng)用是否可見(jiàn)的狀態(tài) 可見(jiàn)和不可見(jiàn) retained的閾值不一樣  5 ---1
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      //通知更新 如果可能話這里會(huì)觸發(fā)轉(zhuǎn)儲(chǔ)堆
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    //監(jiān)聽(tīng)onResume onPause
    registerResumedActivityListener(application)
    //創(chuàng)建桌面快捷圖標(biāo) 點(diǎn)擊直接進(jìn)入LeakActivity
    addDynamicShortcut(application)
    
 
    mainHandler.post {
     backgroundHandler.post {
        SharkLog.d {
          //校驗(yàn)是否可以dump  如果可以dump的話 則發(fā)送notification的廣播
          when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
            is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
            is Nope -> application.getString(
              R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
            )
          }
        }
      }
    }

綜上所述:通過(guò)Android里ContentProvider的特有機(jī)制,自動(dòng)觸發(fā)install操作,在install操作中再通過(guò)類的反射去invoke執(zhí)行.

  • 校驗(yàn)是否只開(kāi)啟debug模式使用
  • 創(chuàng)建Watcher對(duì)象并監(jiān)聽(tīng)
  • 創(chuàng)建GC回收器
  • 創(chuàng)建后臺(tái)異步線程
  • 監(jiān)聽(tīng)?wèi)?yīng)用可見(jiàn)與否,并調(diào)用不同的閾值策略進(jìn)行dump heap
  • 創(chuàng)建桌面快捷圖標(biāo)

3.3: 不同的監(jiān)聽(tīng)器自動(dòng)注入

在AppWatcher的注冊(cè)里面最后3行 有很關(guān)鍵的動(dòng)作,如下

watchersToInstall.forEach {
      it.install()
    }
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)

四大金剛正式登場(chǎng)

  fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }

很多朋友反饋LeakCanary老版本功能有限,只監(jiān)聽(tīng)Activity和Fragment, 不能監(jiān)聽(tīng)Service, 這次給安排上了.

3.3.1: ActivityWatcher

繼承InstallableWatcher接口 只有install和unInstall2個(gè)方法, 通過(guò)聲明一個(gè)全局變量來(lái)做下面的操作

 private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        //watchObject 和 description 這個(gè)描述會(huì)Log日志
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }

3.3.2: FragmentAndViewModelWatcher

這里面底層其實(shí)還是依賴于Activity, 同時(shí)還細(xì)分兼容為如下的
Android8.0及以上的

Android x系列的Fragment

Android support系列的fragment

//定義List<Activity>的集合 
  private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
    val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

    //大于等于8.0版本的AndroidOFragmentDestroyWatcher
    if (SDK_INT >= O) {
      fragmentDestroyWatchers.add(
        AndroidOFragmentDestroyWatcher(reachabilityWatcher)
      )
    }

    //AndroidX 里的Fragment
    getWatcherIfAvailable(
      ANDROIDX_FRAGMENT_CLASS_NAME,
      ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
      reachabilityWatcher
    )?.let {
      fragmentDestroyWatchers.add(it)
    }

    //support系列里的Fragment
    getWatcherIfAvailable(
      ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
      ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
      reachabilityWatcher
    )?.let {
      fragmentDestroyWatchers.add(it)
    }
    fragmentDestroyWatchers
  }

fragmentDestroyWatchers 的使用

private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        for (watcher in fragmentDestroyWatchers) {
          watcher(activity)
        }
      }
    }
3.3.2.1: AndroidOFragmentDestroyWatcher
import android.app.Fragment
import android.app.FragmentManager

 private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null) {
        reachabilityWatcher.expectWeaklyReachable(
          view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
          "(references to its views should be cleared to prevent leaks)"
        )
      }
    }

    override fun onFragmentDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      reachabilityWatcher.expectWeaklyReachable(
        fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
      )
    }
  }

//底層執(zhí)行 通過(guò)寄存的Activity獲取到對(duì)應(yīng)的FragmentManager 設(shè)置生命周期回調(diào)
  override fun invoke(activity: Activity) {
    val fragmentManager = activity.fragmentManager
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
  }
3.3.2.2: AndroidXFragmentDestroyWatcher 和上面的有區(qū)別

和AndroidOFragmentDestroyWatcher寫(xiě)法一樣,唯一就是導(dǎo)入的fragment包不一樣 ,以及多了2個(gè)重寫(xiě)的方法處理

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager

private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    //比androidOFragmentDestroyWatcher多了下面這些處理
    override fun onFragmentCreated(
      fm: FragmentManager,
      fragment: Fragment,
      savedInstanceState: Bundle?
    ) {
      ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
    }

    override fun onFragmentViewDestroyed......省略同AndroidOFragmentDestroyWatcher里..... }
    
     override fun onFragmentDestroyed......省略同AndroidOFragmentDestroyWatcher里..... }


  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      //比androidOFragmentDestroyWatcher多了下面這一行 
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }

通過(guò)Androidx 里的ViewModel

  companion object {
    fun install(
      storeOwner: ViewModelStoreOwner,
      reachabilityWatcher: ReachabilityWatcher
    ) {
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
3.3.2.3: AndroidSupportFragmentDestroyWatcher

和AndroidOFragmentDestroyWatcher一樣,唯一就是引用的Fragment包不一樣

import android.support.v4.app.Fragment
import android.support.v4.app.FragmentActivity
import android.support.v4.app.FragmentManager

綜上所述,在fragmentWatcher處理的時(shí)候由區(qū)分兼容處理,最終通過(guò)Fragment依賴的Activity中fragmentManager進(jìn)行生命周期管理.

3.3.3: RootViewWatcher

主要處理View相關(guān)的,前提是View不依附于Activity/popWindow, 以及項(xiàng)目中配置的是否支持彈窗

private val listener = OnRootViewAddedListener { rootView ->
    val trackDetached = when(rootView.windowType) {
      PHONE_WINDOW -> {
        when (rootView.phoneWindow?.callback?.wrappedCallback) {
          // Activities are already tracked by ActivityWatcher
            //如果是依附于activity的就不處理
          is Activity -> false
          //如果是彈窗里的 則根據(jù)配置文件來(lái)覺(jué)得
          is Dialog -> {
            // Use app context resources to avoid NotFoundException
            // https://github.com/square/leakcanary/issues/2137
            val resources = rootView.context.applicationContext.resources
            resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
          }
          // Probably a DreamService
          else -> true
        }
      }
      // Android widgets keep detached popup window instances around.
      //依賴于pop window的也不處理
      POPUP_WINDOW -> false
      TOOLTIP, TOAST, UNKNOWN -> true
    }
    //可溯源追蹤的就執(zhí)行如下
    if (trackDetached) {
      rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
      
        val watchDetachedView = Runnable {
          reachabilityWatcher.expectWeaklyReachable(
            rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
          )
        }

        override fun onViewAttachedToWindow(v: View) {
          mainHandler.removeCallbacks(watchDetachedView)
        }

        override fun onViewDetachedFromWindow(v: View) {
          mainHandler.post(watchDetachedView)
        }
      })
    }
  }

3.3.4: ServiceWatcher

弱引用關(guān)聯(lián)

private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()

反射創(chuàng)建

 private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }

  private val activityThreadInstance by lazy {
    activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!
  }

  private val activityThreadServices by lazy {
    val mServicesField =
      activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }

    @Suppress("UNCHECKED_CAST")
    mServicesField[activityThreadInstance] as Map<IBinder, Service>
  }

service這里面的核心源碼暫時(shí)沒(méi)看懂, 大概能知道是就是有反射調(diào)用IActivityManager

install方法里的核心源碼貼一下

try {
      swapActivityThreadHandlerCallback { mCallback ->
        uninstallActivityThreadHandlerCallback = {
          swapActivityThreadHandlerCallback {
            mCallback
          }
        }
        Handler.Callback { msg ->
          // https://github.com/square/leakcanary/issues/2114
          // On some Motorola devices (Moto E5 and G6), the msg.obj returns an ActivityClientRecord
          // instead of an IBinder. This crashes on a ClassCastException. Adding a type check
          // here to prevent the crash.
          if (msg.obj !is IBinder) {
            return@Callback false
          }

          if (msg.what == STOP_SERVICE) {
            val key = msg.obj as IBinder
            activityThreadServices[key]?.let {
              onServicePreDestroy(key, it)
            }
          }
          mCallback?.handleMessage(msg) ?: false
        }
      }
      swapActivityManager { activityManagerInterface, activityManagerInstance ->
        uninstallActivityManager = {
          swapActivityManager { _, _ ->
            activityManagerInstance
          }
        }
        Proxy.newProxyInstance(
          activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
        ) { _, method, args ->
          if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
            val token = args!![0] as IBinder
            if (servicesToBeDestroyed.containsKey(token)) {
              onServiceDestroyed(token)
            }
          }
          try {
            if (args == null) {
              method.invoke(activityManagerInstance)
            } else {
              method.invoke(activityManagerInstance, *args)
            }
          } catch (invocationException: InvocationTargetException) {
            throw invocationException.targetException
          }
        }
      }
    } catch (ignored: Throwable) {
      SharkLog.d(ignored) { "Could not watch destroyed services" }
    }

綜上,我們知道 為什么新版本leakCanary只要依賴就行. 因?yàn)樯厦娑际亲詣?dòng)給處理了的.

4: 接著分析轉(zhuǎn)儲(chǔ)堆區(qū)

我們?cè)贏ppWatcher類里面維護(hù)著一個(gè)ObjectWatcher類

class ObjectWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  /**
   * Calls to [watch] will be ignored when [isEnabled] returns false
   */
  private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {

4.1: 在 AppWatcher 類里面如下

  @Volatile
  var retainedDelayMillis = RETAINED_DELAY_NOT_SET
  
  
 val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
      check(isInstalled) {
        "AppWatcher not installed"
      }
      //重要操作 發(fā)送延遲操作的任務(wù)  
      mainHandler.postDelayed(it, retainedDelayMillis)
    },//傳
    isEnabled = { true }
  ),
    isEnabled = { true }

上面所提到的handler就是主線程的handler

internal val mainHandler by lazy { Handler(Looper.getMainLooper()) }

在InternalLeakCanary的invoke執(zhí)行方法里面,有監(jiān)測(cè)的時(shí)候發(fā)送Notification的操作

5: NotificationReceiver

這個(gè)類主要負(fù)責(zé)接收廣播事件 DUMP_HEAP的操作,

5.1: 在上面的3.3步驟中是可以看到發(fā)送廣播的處理

backgroundHandler.post {
        SharkLog.d {
          //校驗(yàn)是否可以dump  如果可以dump的話 則發(fā)送notification的廣播
          when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
            is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
            is Nope -> application.getString(
              R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
            )
          }
        }
      }

5.2 在HenapDumpControl類中

fun iCanHasHeap(): ICanHazHeap {
    ........省略代碼.........

    synchronized(this) {
      if (::latest.isInitialized && dumpHeap is Yup && latest is Nope) {
        //dump的調(diào)度處理
        InternalLeakCanary.scheduleRetainedObjectCheck()
      }
      latest = dumpHeap
    }

    return dumpHeap
  }

5.3: 在INternalLeakCanary中

  fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.scheduleRetainedObjectCheck()
    }
  }

5.4 在HeapDumpTrigger中

fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      return
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      //檢查是否需要dump的地方 根據(jù)需要的時(shí)候就發(fā)送廣播出去
      checkRetainedObjects()
    }, delayMillis)
  }

5.6 在廣播中接收處理

 override fun onReceive(
    context: Context,
    intent: Intent
  ) {
    when (intent.action) {
      DUMP_HEAP.name -> {
      //具體的執(zhí)行看下面5.7
        InternalLeakCanary.onDumpHeapReceived(forceDump = false)
      }
      CANCEL_NOTIFICATION.name -> {
        // Do nothing, the notification has auto cancel true.
      }
      else -> {
        SharkLog.d { "NotificationReceiver received unknown intent action for $intent" }
      }
    }
  }

5.7: InternalLeakCanary類中的Dump接收處理

 fun onDumpHeapReceived(forceDump: Boolean) {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onDumpHeapReceived(forceDump)
    }
  }

5.8 在HeapDumpTrigger中的處理

fun onDumpHeapReceived(forceDump: Boolean) {
    backgroundHandler.post {
      //取消notify提示
      dismissNoRetainedOnTapNotification()
      //手動(dòng)執(zhí)行GC 底層調(diào)用 Runtime.getRuntime().gc()
      gcTrigger.runGc()
      val retainedReferenceCount = objectWatcher.retainedObjectCount
      if (!forceDump && retainedReferenceCount == 0) {
       ....省略代碼.......
        return@post
      }

      SharkLog.d { "Dumping the heap because user requested it" }
      //重要操作
      dumpHeap(retainedReferenceCount, retry = false, "user request")
    }
  }

5.9 dumpHeap的處理

 private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean,
    reason: String
  ) {
    //創(chuàng)建存儲(chǔ)dump 文件的 目錄
    val directoryProvider =
      InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)
    //在dump文件夾創(chuàng)建新的dump文件    目錄context.cacheDir
    val heapDumpFile = directoryProvider.newHeapDumpFile()

    val durationMillis: Long
    try {
      //發(fā)送事件  當(dāng)前的事件唯一id
      InternalLeakCanary.sendEvent(DumpingHeap(currentEventUniqueId))
      if (heapDumpFile == null) {
        throw RuntimeException("Could not create heap dump file")
      }
      saveResourceIdNamesToMemory()
      val heapDumpUptimeMillis = SystemClock.uptimeMillis()
      //UUID為key 的一個(gè)弱引用因 
      KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
      durationMillis = measureDurationMillis {
        configProvider().heapDumper.dumpHeap(heapDumpFile)
      }
      if (heapDumpFile.length() == 0L) {
        throw RuntimeException("Dumped heap file is 0 byte length")
      }
      lastDisplayedRetainedObjectCount = 0
      lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
      objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
      currentEventUniqueId = UUID.randomUUID().toString()
      InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId, heapDumpFile, durationMillis, reason))

5.10 LeakCanary類中的事件集合

 val eventListeners: List<EventListener> = listOf(
      LogcatEventListener,
      ToastEventListener,
      LazyForwardingEventListener {
        if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
      },
      when {
          RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->
            RemoteWorkManagerHeapAnalyzer
          WorkManagerHeapAnalyzer.workManagerInClasspath -> WorkManagerHeapAnalyzer
          else -> BackgroundThreadHeapAnalyzer
      }
    ),

5.10.1: RemoteWorkManagerHeapAnalyzer

  override fun onEvent(event: Event) {
    if (event is HeapDump) {
      val application = InternalLeakCanary.application
      val heapAnalysisRequest =
        OneTimeWorkRequest.Builder(RemoteHeapAnalyzerWorker::class.java).apply {
          val dataBuilder = Data.Builder()
            .putString(ARGUMENT_PACKAGE_NAME, application.packageName)
            .putString(ARGUMENT_CLASS_NAME, REMOTE_SERVICE_CLASS_NAME)
          setInputData(event.asWorkerInputData(dataBuilder))
          with(WorkManagerHeapAnalyzer) {
            addExpeditedFlag()
          }
        }.build()
      SharkLog.d { "Enqueuing heap analysis for ${event.file} on WorkManager remote worker" }
      val workManager = WorkManager.getInstance(application)
      //入棧 執(zhí)行workQueue
      workManager.enqueue(heapAnalysisRequest)
    }

最終調(diào)用HeapAnalysis中的幾個(gè)數(shù)據(jù)類 HeapAnalysisFailure HeapAnalysisSuccess,里面的toString()方法就是拼接的 我們?cè)贚eakCanary中看到的錯(cuò)誤記錄.

摘要部分代碼

data class HeapAnalysisFailure(
  override val heapDumpFile: File,
  override val createdAtTimeMillis: Long,
  override val dumpDurationMillis: Long = DUMP_DURATION_UNKNOWN,
  override val analysisDurationMillis: Long,
  /**
   * An exception wrapping the actual exception that was thrown.
   */
  val exception: HeapAnalysisException
) : HeapAnalysis() {

  override fun toString(): String {
    return """====================================
HEAP ANALYSIS FAILED

You can report this failure at https://github.com/square/leakcanary/issues
Please provide the stacktrace, metadata and the heap dump file.
====================================
STACKTRACE

$exception====================================
METADATA

Build.VERSION.SDK_INT: ${androidSdkInt()}
Build.MANUFACTURER: ${androidManufacturer()}
LeakCanary version: ${leakCanaryVersion()}
Analysis duration: $analysisDurationMillis ms
Heap dump file path: ${heapDumpFile.absolutePath}
Heap dump timestamp: $createdAtTimeMillis
===================================="""
  }

5.10.2: WorkManagerHeapAnalyzer

和5.10.1有些類似 步驟更少

  override fun onEvent(event: Event) {
    if (event is HeapDump) {
      val heapAnalysisRequest = OneTimeWorkRequest.Builder(HeapAnalyzerWorker::class.java).apply {
        setInputData(event.asWorkerInputData())
        addExpeditedFlag()
      }.build()
      SharkLog.d { "Enqueuing heap analysis for ${event.file} on WorkManager remote worker" }
      val application = InternalLeakCanary.application
      WorkManager.getInstance(application).enqueue(heapAnalysisRequest)
    }
  }

5.10.3: BackgroundThreadHeapAnalyzer

override fun onEvent(event: Event) {
    if (event is HeapDump) {
      heapAnalyzerThreadHandler.post {
        val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(event) { event ->
          InternalLeakCanary.sendEvent(event)
        }
        InternalLeakCanary.sendEvent(doneEvent)
      }
    }
  }

以上: LeakCanary的依賴 監(jiān)聽(tīng) dump 及分析都基本上理清了.

6 PlumberInstaller

leakcanary.internal.PlumberInstaller

另外加餐補(bǔ)充這個(gè), 這個(gè)類的作用主要是反射調(diào)用不同版本不同手機(jī)廠商 對(duì)有些api是否支持到.

主要針對(duì)不同機(jī)型 不同版本,有些特殊的場(chǎng)景會(huì)導(dǎo)致Leak的時(shí)候,在Activity銷毀的時(shí)候手動(dòng)置為null方便回收.

AndroidLeakFixes.applyFixes(application)

這個(gè)分析是在后臺(tái)通過(guò)單一線程池執(zhí)行的

 Executors.newSingleThreadScheduledExecutor

舉個(gè)例子,比如 這里面就有針對(duì)三星設(shè)備 且不是19到21之間的版本
反射TextView中的mLastHoveredView字段

  override fun apply(application: Application) {
      if (MANUFACTURER != SAMSUNG || SDK_INT !in 19..21) {
        return
      }

      backgroundExecutor.execute {
        val field: Field
        try {
          field = TextView::class.java.getDeclaredField("mLastHoveredView")
          field.isAccessible = true
        } catch (ignored: Exception) {
          SharkLog.d(ignored) { "Could not fix the $name leak" }
          return@execute
        }

        application.onActivityDestroyed {
          try {
            field.set(null, null)
          } catch (ignored: Exception) {
            SharkLog.d(ignored) { "Could not fix the $name leak" }
          }
        }
      }
    }
  },

LeakActivity

leakcanary.internal.activity.LeakActivity

我們打開(kāi)金絲雀圖標(biāo)展示的就是這個(gè)Activity.

導(dǎo)入.hprof文件

 private fun importHprof(fileUri: Uri) {
    try {
      contentResolver.openFileDescriptor(fileUri, "r")
        ?.fileDescriptor?.let { fileDescriptor ->
          val inputStream = FileInputStream(fileDescriptor)
          InternalLeakCanary.createLeakDirectoryProvider(this)
            .newHeapDumpFile()
            ?.let { target ->
              inputStream.use { input ->
                target.outputStream()
                  .use { output ->
                    input.copyTo(output, DEFAULT_BUFFER_SIZE)
                  }
              }
              InternalLeakCanary.sendEvent(
                HeapDump(
                  uniqueId = UUID.randomUUID().toString(),
                  file = target,
                  durationMillis = -1,
                  reason = "Imported by user"
                )
              )
            }
        }
    } catch (e: IOException) {
      SharkLog.d(e) { "Could not imported Hprof file" }
    }
  }

RequestStoragePermissionActivity

主要就是申請(qǐng)權(quán)限使用的.

以上就是整體的分析過(guò)程.

  • 從設(shè)計(jì)模式來(lái)看,使用到了工廠模式(Wacher和分析器)
  • 巧妙運(yùn)用ContentProvider特性達(dá)到無(wú)侵入式 一行代碼接入
  • 相比老版本,新增了RootView及Service的監(jiān)測(cè)

注意事項(xiàng): 這個(gè)官方聲明只能在debug模式.

才疏學(xué)淺,歡迎探討.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牧抽,一起剝皮案震驚了整個(gè)濱河市干跛,隨后出現(xiàn)的幾起案子解孙,更是在濱河造成了極大的恐慌律罢,老刑警劉巖蛆橡,帶你破解...
    沈念sama閱讀 211,496評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異贮乳,居然都是意外死亡霜幼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)瞄勾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)费奸,“玉大人,你說(shuō)我怎么就攤上這事进陡≡覆” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,091評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵趾疚,是天一觀的道長(zhǎng)缨历。 經(jīng)常有香客問(wèn)我,道長(zhǎng)糙麦,這世上最難降的妖魔是什么辛孵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,458評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮赡磅,結(jié)果婚禮上魄缚,老公的妹妹穿的比我還像新娘。我一直安慰自己焚廊,他們只是感情好冶匹,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,542評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著咆瘟,像睡著了一般嚼隘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袒餐,一...
    開(kāi)封第一講書(shū)人閱讀 49,802評(píng)論 1 290
  • 那天飞蛹,我揣著相機(jī)與錄音谤狡,去河邊找鬼。 笑死卧檐,一個(gè)胖子當(dāng)著我的面吹牛墓懂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泄隔,決...
    沈念sama閱讀 38,945評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拒贱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了佛嬉?” 一聲冷哼從身側(cè)響起逻澳,我...
    開(kāi)封第一講書(shū)人閱讀 37,709評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎暖呕,沒(méi)想到半個(gè)月后斜做,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,158評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡湾揽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,502評(píng)論 2 327
  • 正文 我和宋清朗相戀三年瓤逼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片库物。...
    茶點(diǎn)故事閱讀 38,637評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡霸旗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出戚揭,到底是詐尸還是另有隱情诱告,我是刑警寧澤,帶...
    沈念sama閱讀 34,300評(píng)論 4 329
  • 正文 年R本政府宣布民晒,位于F島的核電站精居,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏潜必。R本人自食惡果不足惜靴姿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,911評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磁滚。 院中可真熱鬧佛吓,春花似錦、人聲如沸垂攘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,744評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)搜贤。三九已至,卻和暖如春钝凶,著一層夾襖步出監(jiān)牢的瞬間仪芒,已是汗流浹背唁影。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,982評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掂名,地道東北人据沈。 一個(gè)月前我還...
    沈念sama閱讀 46,344評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像饺蔑,于是被迫代替她去往敵國(guó)和親锌介。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,500評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容