每日一問:說說你對 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
  }
}

確實是真的騷,我們都知道 ContentProvideronCreate() 的調用時機介於 ApplicationattachBaseContext()onCreate() 之間,LeakCanary 這么做,把 init 的邏輯放到庫內部,讓調用方完全不需要在 Application 里去進行初始化了,十分方便。這樣下來既可以避免開發者忘記初始化導致一些錯誤,也可以讓我們龐大的 Application 代碼更加簡潔。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM