因为工作需要,需要对固件中通过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