1. 程式人生 > >每日一問:說說你對 LeakCanary 的瞭解

每日一問:說說你對 LeakCanary 的瞭解

昨天的問題說到了關於 記憶體洩漏需要注意的點,在文章最後有說到 LeakCanary 檢測記憶體洩漏。實際上,我相信絕大多數人也知道甚至使用過這個庫。

這個系列通常來說如果發現了不錯的資源,會選擇直接擷取部分拿過來,所以對於文章底部的參考連結一般都是非常不錯的,可以直接去看喲~

LeakCanary 的基本工作流程是怎樣的?

LeakCanary 的使用方式非常簡單,只需要在 build.gradle 裡面直接寫上依賴,並且在 Application 類裡面做註冊就可以了。

當然,需要在 Application 裡面註冊這樣的操作僅在大多數人接觸的 1.x 版本,實際上 LeakCanary 現在已經升級到了 2.x 版本,程式碼侵入性更低,而且純 Kotlin 寫法。從 Google 各種 Demo 主推 Kotlin 以及各種主流庫都在使用 Kotlin 編寫來看可見 Kotlin 確實在 Android 開發中愈發重要,沒使用的小夥伴必須得去學習一波了,目前我也是純 Kotlin 做開發的。

對於工作原理我相信大家應該也是或多或少有一定了解,這裡剛好有一張非常不錯的流程圖就直接借用過來了,另外他從原始碼角度理解 LeakCanary 的這篇文章也寫的非常不錯,感興趣的點選文章底部的連結直達。

初次使用 LeakCanary 為什麼沒有 Icon 入口

我們常常在使用 LeakCanary 的時候會發現這樣一個問題:最開始並沒有出現 LeakCanary 的 Launcher icon,但當出現了記憶體洩漏警告的時候系統桌面就多了這麼一個圖示,一般情況下都是會非常好奇的。

從 1.x 的原始碼中就可以看出端倪。在 leakcanary-android 的 manifast 中,我們可以看到相關配置:

<!--leakcanary-sample/src/main/AndroidManifest.xml-->
<service
    android:name=".internal.HeapAnalyzerService"
    android:process=":leakcanary"
    android:enabled="false"
    />
<service
    android:name=".DisplayLeakService"
    android:process=":leakcanary"
    android:enabled="false"
    />
<activity
    android:theme="@style/leak_canary_LeakCanary.Base"
    android:name=".internal.DisplayLeakActivity"
    android:process=":leakcanary"
    android:enabled="false"
    android:label="@string/leak_canary_display_activity_label"
    android:icon="@mipmap/leak_canary_icon"
    android:taskAffinity="com.squareup.leakcanary.${applicationId}"
    >
  <intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
  </intent-filter>
</activity>

我們可以看到 DisplayLeakActivity 被設定為了 Launcher,並設定上了對應的圖示,所以我們使用 LeakCanary 會在系統桌面上生成 Icon 入口。但是 DisplayLeakActivityenable 屬性預設是 false,所以在桌面上是不會顯示入口的。而在發生記憶體洩漏的時候,LeakCanary 會主動將 enable 屬性置為 true。

LeakCanary 2 都做了些什麼

最近 LeakCanary 升級到了 2.x 版本,這是一次完全的重構,去除了 1.x release 環境下引用的空包 leakcanary-android-no-op。並且 Kotlin 語言覆蓋高達 99.8%,也再也不需要在 Application 裡面做類似下面的程式碼。

//com.example.leakcanary.ExampleApplication
@Override
public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
        // This process is dedicated to LeakCanary for heap analysis.
        // You should not init your app in this process.
        return;
    }
    LeakCanary.install(this);
}

只需要在依賴裡面新增這樣的程式碼就可以了。

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'
}

初次看到這樣的操作,會覺得非常神奇,仔細閱讀原始碼才回發現它竟然使用了一個騷操作:ContentProvider

leakcanary-leaksentry 模組的 AndroidManifest.xml檔案中可以看到:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.leakcanary.leaksentry"
    >

  <application>
    <provider
        android:name="leakcanary.internal.LeakSentryInstaller"
        android:authorities="${applicationId}.leak-sentry-installer"
        android:exported="false"/>
  </application>
</manifest>

再經過檢視 LeakSentryInstaller 可以看到:

package leakcanary.internal

import android.app.Application
import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
import leakcanary.CanaryLog

/**
 * Content providers are loaded before the application class is created. [LeakSentryInstaller] is
 * used to install [leaksentry.LeakSentry] on application start.
 */
internal class LeakSentryInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    CanaryLog.logger = DefaultCanaryLog()
    val application = context!!.applicationContext as Application
    InternalLeakSentry.install(application)
    return true
  }

  override fun query(
    uri: Uri,
    strings: Array<String>?,
    s: String?,
    strings1: Array<String>?,
    s1: String?
  ): Cursor? {
    return null
  }

  override fun getType(uri: Uri): String? {
    return null
  }

  override fun insert(
    uri: Uri,
    contentValues: ContentValues?
  ): Uri? {
    return null
  }

  override fun delete(
    uri: Uri,
    s: String?,
    strings: Array<String>?
  ): Int {
    return 0
  }

  override fun update(
    uri: Uri,
    contentValues: ContentValues?,
    s: String?,
    strings: Array<String>?
  ): Int {
    return 0
  }
}

確實是真的騷,不過細細品嚐會覺得確實是挺巧的。

參考:https://www.jianshu.com/p/49239eac7