因爲工作需要,需要對固件中通過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