LeakCanary傻瓜式的内存泄露检测工具

在开发Android应用的过程中如果需要处理图片或者大量数据的时候,就有可能会遇到OOMjava.lang.OutOfMemoryError),一般出现最多的是在创建Bitmap上,也有可能是在内存中处理了大量的数据上。出现OOM应用会直接崩溃,即使没有出现OOM,内存使用过大的时候应用也会出现卡顿。所以内存的优化在开发Android应用时是一个比较重要的任务。
一般会针对Bitamp的内存优化有下面几种方式:
1. 增加进程的内存
2. 使用Bitmap.Config.ALPHA_8(图片失真)
3. 显示的调用System.gc()
4. catch Exception
5. 调用bitmap.recycle()
6. 缩小bitmap的大小(如果是读取的原图是一个大图应该先采用这种方式,Bitmap如果是刚好适配屏幕的就不需要缩小了)
7. 使用弱引用和软引用(google已经不建议使用了,Android的GC效率非常高,只要保证对象没有被引用即可)
但是我们忽略掉一个问题就是什么造成了OOM
一般来说发生OOM崩溃的地方不一定是内存泄露的地方,崩溃的原因有可能是Activity造成的内存泄露,也可能是操作数据库造成的内存泄露,当内存已经非常接近峰值的时候,这个时候恰巧要创建一个Bitmap对象就会发生OOM(Bitmap对象占用的内存比较大)。
是什么原因造成了内存泄露呢?

内存泄露

我们知道Android中每个对象都有自己的生命周期,比如Activity的生命周期最后会调用onDestroy方法做销毁处理,但如果使用Activity中调用了类似于Toast这种对象,就会把这个Activity的引用传给了Toast,而Toast的生命周期不会随着Activity的销毁而销毁,这样就造成了Activity的内存泄露,它会被Toast对象引用,无法被销毁。
常见的内存泄露形成的原因:

  • Toast持有Activity的引用
  • 数据库游标Cursor没有关闭
  • Adapter没有复用convertView
  • 对象被生命周期更长的对象引用,Activity被静态集合引用
  • ....

那如何知道应用的内存有没有出现泄露呢?

监控内存的方式

Heap Dump:常见的内存监控方式是Heap DumpHeap Dump是一种在Java中比较常用的检测内存的方式:

简单来说就是我们在一个初始状态A, 在这个时候Dump一次内存,在做了一些操作之后回到状态A,再Dump一次内存。
对两次Dunp的内存数据(hprof)使用分析工具做分析(MAT),根据分析的结果就能知道是否存在内存泄露,这种方式比较复杂和繁琐并不是特别易用。

Moitors:这是Android SDK自带的内存监控工具,Monitors能检测到内存的变化,比如内存是增加还是减少。
打开一个Activity会导致内存增加,关闭一个Activity会导致内存减少,反复的做这样的操作,如果每次打开一个Activity再关闭之后增加的内存不会减少就说明这个Activity有可能有内存泄露,再借助log辅助进行检测,就可以发现内存泄露的问题,
这种方式的缺点是并不是特别的准确,因为内存的释放和对象的生命周期有关也和GC的调度有关。
另一种方式就是LeakCanary,LeakCanary是一个简单的,方便的内存检测工具,可以轻易的发现内存问题,还会生成更加简单清晰的报告。

LeakCanary

LeakCanary是一个开源的检测内存泄露的java库。项目地址:https://github.com/square/leakcanary
LeakCanary实际上就是在本机上自动做了Heap dump,对生成的hprof文件进行分析,展示分析的结果。和手工分析Heap Dump的方式得到的结果是一样的。只不过这部分的工作完全自动化完成了。
下面是一个LeakCanary的结果截图:

LeakCanary

从上图可以看到,LeakCanary能清晰简单的展示出那里有内存泄露的问题。那LeakCanary如何使用呢?

集成LeakCanary

build.gradle添加依赖:

dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
 }

使用LeakCanary对应用进行检测它会影响程序的性能,尤其是在做Heap dump分析操作时,因此需要在依赖里面指定对应的版本,debug的时候才进行分析,release的时候不能进行分析。
debugCompile可以使用检测版本:

com.squareup.leakcanary:leakcanary-android

releaseCompile使用no-op模式,即No Operation Performed就是不会把对应的类库编译,指定类库为无用的指令:

com.squareup.leakcanary:leakcanary-android-no-op

这样就可以指定LeakCanary为无用指令,不会在release的时候进行编译。
Application中加入分析Activity的代码:

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}

这样就可以检测所有Activity的内存泄露了。LeakCanary内部实现使用了ActivityLifecycleCallbacks方法监听所有Activity的生命周期。
除了Activity会发生内存泄露以外,其他对象也有可能会出现内存泄露,如果对其他对象进行检测呢?

检测其他对象

LeakCanary中提供了RefWatcher类,可以用来监控所有的对象。
首先实例化RefWatcher:

public static RefWatcher sRefWatcher=LeakCanary.install(mContext);

对于监控的对象使用:

sRefWatcher.watch(this)

一般我们是在对象销毁的时候对对象进行监控,比如内部实现的对于Activity的监控的原理如下:

private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }

        public void onActivityStarted(Activity activity) {
        }

        public void onActivityResumed(Activity activity) {
        }

        public void onActivityPaused(Activity activity) {
        }

        public void onActivityStopped(Activity activity) {
        }

        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }

        public void onActivityDestroyed(Activity activity) {
            ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
    };

只是在onActivityDestroyed的时候才对于activity进行监控即可。
检测到了内存泄露,如果解决呢?

解决内存泄露

一般情况内存泄露的原因都是由于引用的使用不当造成的,Android GC能够保证回收循环引用(如果一个循环引用没有外部引用时就会被回收),且Android GC效率很高,当然GC的算法本身也在不停的改进。
一般情况下只需要尽量避免错误的引用方式带来的内存泄露问题即可:

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

推荐阅读更多精彩内容