LeakCanary接入MK編譯App過程記錄

因爲工作需要,需要對固件中通過MK編譯的系統應用接入LeakCanary以檢查內存泄露。

幾經折騰,終於成功。

接入的LeakCanary 最新版本 2.2版本,免寫版本。

環境準備:使用的Android10 AOSP代碼(tag: android-10.0.0_r10),接入leakcanary前已經全編譯通過。

接入的APP路徑:packages/apps/Gallery2

對比的獨立應用接入LeakCanary: GcTest

 

來看看遇到的問題,及解決過程:

1、在MK中加入leakcanary2.2依賴編譯不過

解決方法:修改Android.mk

--- a/Android.mk
+++ b/Android.mk
@@ -2,6 +2,8 @@ LOCAL_PATH:= $(call my-dir)

 include $(CLEAR_VARS)

+COMMON_LIBS_PATH := ../../../prebuilts/tools/common/m2/repository
+
 LOCAL_MODULE_TAGS := optional

 LOCAL_STATIC_ANDROID_LIBRARIES := \
@@ -13,7 +15,22 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \
 LOCAL_STATIC_JAVA_LIBRARIES := \
     com.android.gallery3d.common2 \
     xmp_toolkit \
-    mp4parser
+    mp4parser \
+    leakcanary-android \
+    leakcanary-android-core \
+    leakcanary-object-watcher \
+    leakcanary-object-watcher-android \
+    shark \
+    shark-android \
+    shark-graph \
+    shark-hprof \
+    shark-log \
+    leakcanary-object-watcher-android-androidx \
+    kotlin-stdlib-1.2.50
+
+LOCAL_AAPT_FLAGS := \
+        --auto-add-overlay \
+        --extra-packages com.squareup.leakcanary \

 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
@@ -30,7 +47,8 @@ LOCAL_PRODUCT_MODULE := true

 LOCAL_OVERRIDES_PACKAGES := Gallery Gallery3D GalleryNew3D

-LOCAL_SDK_VERSION := current
+#LOCAL_SDK_VERSION := current
+LOCAL_PRIVATE_PLATFORM_APIS := true

 LOCAL_JNI_SHARED_LIBRARIES := \
     libjni_eglfence \
@@ -44,6 +62,21 @@ LOCAL_JAVA_LIBRARIES += org.apache.http.legacy
 LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt

 include $(BUILD_PACKAGE)
+#Added for source code of leakcannry compile start
+include $(CLEAR_VARS)
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+	leakcanary-android:libs/leakcanary-android-2.2.aar \
+	leakcanary-android-core:libs/leakcanary-android-core-2.2.aar \
+	leakcanary-object-watcher:libs/leakcanary-object-watcher-2.2.jar \
+	leakcanary-object-watcher-android:libs/leakcanary-object-watcher-android-2.2.aar \
+	leakcanary-object-watcher-android-androidx:libs/leakcanary-object-watcher-android-androidx-2.2.aar \
+	shark:libs/shark-2.2.jar \
+	shark-android:libs/shark-android-2.2.jar \
+	shark-graph:libs/shark-graph-2.2.jar \
+	shark-hprof:libs/shark-hprof-2.2.jar \
+	shark-log:libs/shark-log-2.2.jar \
+        kotlin-stdlib-1.2.50:$(COMMON_LIBS_PATH)/org/jetbrains/kotlin/kotlin-stdlib/1.2.50/kotlin-stdlib-1.2.50.jar
+include $(BUILD_MULTI_PREBUILT)

 ifeq ($(strip $(LOCAL_PACKAGE_OVERRIDES)),)

  怎麼知道要依賴這些包?

  通過在獨立應用上去引用LeakCanary來獲取到的。

  獨立應用引用LeakCanary參見 https://square.github.io/leakcanary/upgrading-to-leakcanary-2.0/  

  特別的 LeakCanary使用kotlin編寫,這裏需要依賴kotlin-stdlib庫,使用AOSP自帶的kotlin-stdlib庫聲明編譯依然有問題,自己定義了一個本地的kotlin-stdlib-1.2.50版本引用後編譯通過。

 2、安裝應用後發現並沒有Leaks圖標顯示

  2.2版本LeakCanary打入應用,安裝後會顯示一個與應用相同的圖標到桌面,名稱爲Leaks。

  解壓apk後查看AndroidManifest.xml,發現依賴的aar裏面AndroidManifest.xml的內容沒有合並進來。

  原來aar是需要聲明到LOCAL_STATIC_JAVA_AAR_LIBRARIES下面,而不LOCAL_STATIC_JAVA_LIBRARIES下面

  另外,還有一個缺失的okio依賴也添加上了。

  

+LOCAL_STATIC_JAVA_AAR_LIBRARIES := \
+    leakcanary-android \
+    leakcanary-android-core \
+    leakcanary-object-watcher-android
+    leakcanary-object-watcher-android-androidx
+
 LOCAL_STATIC_JAVA_LIBRARIES := \
     com.android.gallery3d.common2 \
     xmp_toolkit \
     mp4parser \
-    leakcanary-android \
-    leakcanary-android-core \
     leakcanary-object-watcher \
-    leakcanary-object-watcher-android \
     shark \
     shark-android \
     shark-graph \
     shark-hprof \
     shark-log \
-    leakcanary-object-watcher-android-androidx \
-    kotlin-stdlib-1.2.50
+    kotlin-stdlib-1.2.50 \
+    okio
+
@@ -69,15 +69,20 @@ LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
        leakcanary-android-core:libs/leakcanary-android-core-2.2.aar \
        leakcanary-object-watcher:libs/leakcanary-object-watcher-2.2.jar \
        leakcanary-object-watcher-android:libs/leakcanary-object-watcher-android-2.2.aar \
        leakcanary-object-watcher-android-androidx:libs/leakcanary-object-watcher-android-androidx-2.2.aar \
        shark:libs/shark-2.2.jar \
        shark-android:libs/shark-android-2.2.jar \
        shark-graph:libs/shark-graph-2.2.jar \
        shark-hprof:libs/shark-hprof-2.2.jar \
        shark-log:libs/shark-log-2.2.jar \
-        kotlin-stdlib-1.2.50:$(COMMON_LIBS_PATH)/org/jetbrains/kotlin/kotlin-stdlib/1.2.50/kotlin-stdlib-1.2.50.jar
+        kotlin-stdlib-1.2.50:$(COMMON_LIBS_PATH)/org/jetbrains/kotlin/kotlin-stdlib/1.2.50/kotlin-stdlib-1.2.50.jar \
+       okio:libs/okio-2.2.2.jar
 include $(BUILD_MULTI_PREBUILT)

   編譯後,安裝上 Leaks 圖標顯示到桌面了。

   開始寫一下內存泄露的demo進去,然後測試。

   問題來了:內存泄露的Activity銷燬後並未觸發自動的檢查,即使手動的去點擊 Dump Heap Now,結果還是顯示Found 0 retained Objects。就是沒檢查到內存泄露。

   3、檢測不到內存泄露

    開始解壓Gallery2.apk與GcTest.apk對比裏面的打入的leakcanary的class、AndroidManifest裏面關於leakcanary的聲明的區別。

    沒有發現什麼特別的異常。

    心裏隱隱的覺得是Gallery2.apk是沒有去做打入WeakReference的操作,至於爲什麼Gallery2.apk沒有做,倒沒有認真的想。

    通過Gallery2.apk與GcTest.apk的Leaks分別去檢查了對方導出的hprof,結果是Gallery2能夠分析出GcTest hprof中的內存泄露,GcTest分析Gallery2 hprof的結果同樣是未發現內存泄露。這也證明了我的猜測,Gallery2裏是沒有做WeakReference的。

    好吧,只能去進一步看下LeakCanary的文檔、源碼。

    爲了看這kotlin的代碼,還專門去瞄了下kotlin的語法。

    參考leakcanary官方說明: https://square.github.io/leakcanary/recipes/#watching-objects-with-a-lifecycle

     然後再去對比 Gallery2.apk與GcTest.apk 銷燬內存泄露的Activity之後的日誌( tag:LeakCanary),發現GcTest會有如下的日誌:

04-02 06:57:49.874 13112 13112 D LeakCanary: Watching instance of com.example.gctest.LeakActivity (com.example.gctest.LeakActivity received Activity#onDestroy() callback) with key e63419f1-7884-4f1a-9abd-d656ebd98466

 接着就開始dump、分析,提示泄露。

  去看LeakCanary源碼裏面的這個日誌的出自,然後自己再增加了一些日誌。

  分別更新到Gallery2.apk與GcTest.apk 下編譯後運行。

  發現下面問題點在於ObjectWatcher 裏面的  if (!isEnabled()) 判定這裏跳出了,所以後續的邏輯都沒有走到。

@Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      SharkLog.d { "not isEnabled()" }
      return
    }
    removeWeaklyReachableObjects()
    SharkLog.d { "after removeWeaklyReachableObjects" }
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    SharkLog.d{ "after KeyedWeakReference" }
    SharkLog.d {
      "Watching " +
          (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
          (if (description.isNotEmpty()) " ($description)" else "") +
          " with key $key"
    }

    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

  進一步去看這個isEnabled()的判定,來自 AppWatcher.config.enabled,代碼裏面來如下面的判斷

  val isDebuggableBuild by lazy {
    (application.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
  }
這表示該應用爲Debug編譯則enable爲true, 反之則爲false。

   恍然大悟,想起來官方文檔裏面有一段,當時也沒多想
  

   好吧,修改了代碼,強制賦值 isDebuggableBuild 爲true

  val isDebuggableBuild = true

  再重新編譯了一個leakcanary-object-watcher-android.aar 替換到Gallery2下面,編譯後驗證。

  終於能夠正常的自動觸發、並分析出內存泄露了。

  2020-04-03

    

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章