背景
之前统计crash信息时统计到的top5的崩溃,OOM导致的崩溃数量排在第二位
Android sdk的OOM崩溃率持续增长,为了检测出内存泄漏问题,决定改造下LeakCanary解决目前测试中的几个痛点。
-
Memory leak发生时不能很快的将信息传递给开发人员,之前需要打开leak activity 然后找到发生的leak记录,打开每级的stack信息。截图给开发,简直不能忍。
- 原始memory leak发生时的通知提示,打开以后如下图,需要将这个图给到开发定位问题。
当测试需要清除应用数据时会造成leak信息一并被清除,导致数据丢失,leak信息无法追溯。
当使用monkey等自动测试手段进行稳定性测试时,查看leak trace的界面是一个新的Activity,在跑Monkey的过程中会进入这个activity不断的操作,会删除已经产生的leak信息,并且会有相当长一段时间可能无法回到测试产品自身的界面中操作。
Leakcanary的使用
-
原理图
-
过程解析
RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
然后在后台线程检查引用是否被清除,如果没有,调用GC。
如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄漏。
HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄漏。如果是的话,建立导致泄漏的引用链。
引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。
一、开始使用
在项目 build.gradle 中加入引用,不同的编译使用不同的引用:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
在 Application 中:
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
这样,在 debug build 中,如果检测到某个 activity 有内存泄露,LeakCanary 就是自动地显示一个通知。点击通知即可进入leak activity进行查看相关的leak详情。
二、改造
1、新建LeakUploadService
类
在Application类所在的package内新建一个LeakUploadService
类,继承DisplayLeakService
类:
import com.squareup.leakcanary.*
public class LeakUploadService extends DisplayLeakService {
@Override
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
if (!result.leakFound || result.excludedLeak){
return;
}
uploadService();
}
}
其中,发生泄漏的类名为result.className.toString();,其余信息诸如软件包名、软件版本号、leak trace等,均在leakInfo中一般将软件包名、版本号、leak trace(第一行下面,* Retaining: 131 KB.之上的部分)、泄漏大小等信息上传到数据库即可,有了这些信息,开发就可以定位问题了。
处理方法也很简单,就是对String对象进行一些操作。这里我是将发送泄漏的类名、软件包名、软件版本号、整个泄漏信息(除了Details部分)上传到数据库
String className = result.className.toString();
String pkgName = leakInfo.trim().split(":")[0].split(" ")[1]
String pkgVer = leakInfo.trim().split(":")[1]
String leakDetail = leakInfo.split("\n\n")[0] + "\n\n" + leakInfo.split("\n\n")[1];
使用Okhttp编写一个简单的上传leak信息的代码,在LeakUploadService
中检测到memoryload时调用上传即可。
2、注册service
接下来需要在AndroidManifest.xml
中注册service,即在<application android:name=...>和</application>之间添加<service android:name="com.squareup.leakcanary.LeakUploadService" android:exported="false"/>
,根据LeakUploadService所在的包自行调整。
3、改造屏蔽leak activity
DisplayLeakActivity
类是LeakCanary展示leak trace的类,这个类的存在,会导致跑Monkey的过程中,多次进入这个Activity,在其中操作,从而减少在软件本身界面中的操作时间。
屏蔽DisplayLeakActivity
类
-
由于
LeakCanary
类正好是在前面接入LeakCanary时添加代码LeakCanary.install(this);
用到的类,因此想到的方法是自己创建一个新的类LeakCanaryWithoutDisplay
,位置在com/squareup/leakcanary
下(需要自己新建这个package),里面的代码直接复制LeakCanary
类,然后做如下修改:把
public final class LeakCanary {
改为public final class LeakCanaryWithoutDisplay {
把
private LeakCanary() {
改为private LeakCanaryWithoutDisplay() {
修改
enableDisplayLeakActivity()
函数,将true
改为false
将主Application类中安装LeakCanary的代码
LeakCanary.install();
改为LeakCanaryWithoutDisplay.install();
调用LeakCanaryWithoutDisplay.enableDisplayLeakActivity(this);之后就会屏蔽activity和通知信息。
最后修改主Application类中的安装方式,改为:
public class MainApplication extends Application {
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher = LeakCanaryWithoutDisplay.install(this);
LeakCanaryWithoutDisplay.enableDisplayLeakActivity(this);
}
}
做了如上操作后,每次发生泄漏都不会出现通知和leak activity,并且会自动将发送泄漏的类名、软件包名、软件版本号、泄漏信息等上传指定服务端了。
4、 服务端简单实现
- 主要功能实现
接受LeakUploadService post过来的leak数据
对数据处理并入库
定时任务触发,通过邮件的形式上报收集到的leak信息,并更新数据库状态
重复leak的过滤
2 重复leak信息过滤,主要依据infer生成的report json对比实现,当前可以完全过滤掉重复的leak信息。
通过对比发现生成的数据中,可以通过下面的代码过滤重复信息
三、 测试
测试中无需关注leak的发生,只需要关注被测应用的功能测试,当leak发生时会自动上传leak信息到服务端,服务端会自动邮件通知开发者。
四、改造以后于原始方式的对比
改造以后可以通过邮件直接通知开发,memoryleak的发生,还有泄露的详细信息,
测试过程中完全可以不再关注leak的发生,发生leak也不会再中断测试过程。
邮件通知,减少测试与开发之间沟通的成本
提升测试工作效率,减少不必要的时间开支