Android Memory Leak检测上报

背景

之前统计crash信息时统计到的top5的崩溃,OOM导致的崩溃数量排在第二位

crash top5分布

Android sdk的OOM崩溃率持续增长,为了检测出内存泄漏问题,决定改造下LeakCanary解决目前测试中的几个痛点。

  1. Memory leak发生时不能很快的将信息传递给开发人员,之前需要打开leak activity 然后找到发生的leak记录,打开每级的stack信息。截图给开发,简直不能忍。

    1. 原始memory leak发生时的通知提示,打开以后如下图,需要将这个图给到开发定位问题。
    image
  2. 当测试需要清除应用数据时会造成leak信息一并被清除,导致数据丢失,leak信息无法追溯。

  3. 当使用monkey等自动测试手段进行稳定性测试时,查看leak trace的界面是一个新的Activity,在跑Monkey的过程中会进入这个activity不断的操作,会删除已经产生的leak信息,并且会有相当长一段时间可能无法回到测试产品自身的界面中操作。

Leakcanary的使用

  • 原理图

image
  • 过程解析

    • 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、 服务端简单实现

  1. 主要功能实现
  • 接受LeakUploadService post过来的leak数据

  • 对数据处理并入库

  • 定时任务触发,通过邮件的形式上报收集到的leak信息,并更新数据库状态

  • 重复leak的过滤

2 重复leak信息过滤,主要依据infer生成的report json对比实现,当前可以完全过滤掉重复的leak信息。
通过对比发现生成的数据中,可以通过下面的代码过滤重复信息


image2019-4-12_15-28-43.png

三、 测试

测试中无需关注leak的发生,只需要关注被测应用的功能测试,当leak发生时会自动上传leak信息到服务端,服务端会自动邮件通知开发者。

四、改造以后于原始方式的对比

  1. 改造以后可以通过邮件直接通知开发,memoryleak的发生,还有泄露的详细信息,

  2. 测试过程中完全可以不再关注leak的发生,发生leak也不会再中断测试过程。

  3. 邮件通知,减少测试与开发之间沟通的成本

  4. 提升测试工作效率,减少不必要的时间开支

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容