总结工作中的Android内存泄漏问题

简单判断是否有内存泄漏

判断内存泄漏的定位的大单位是Activity。

可以通过反复进入退出一个Activity,然后用adb shell dumpsys meminfo + 包名 查看虚拟机的堆是否有不断地增长


定位内存泄漏

1.使用Leak Canary

在代码上加入Leak Canary,然后不断跑Monkey或者手动反复进出不同页面。若出现内存泄漏问题,会自动导出来,生成以下页面。

总结工作中的Android内存泄漏问题_第1张图片
内存泄漏的引用关系图

2.使用DDMS导出hprof,并用MAT工具进行分析

0)强烈建议先跑30分钟Monkey测试

1)使用eclipse的ddms找到对应的进程,触发一次gc后,dump出里面的内存快照hprof文件以分析当前应用内存的堆有什么东西

2)使用Android SDK 里的platform-tools文件夹的 hprof-conv工具,对刚才 hprof 文件进行转换,以至于 后面MAT工具能正常打开

3)使用MAT打开hprof文件,进入Histogram。输入自己猜测可能泄漏的Activity(项目中Activity不多时,可每个Activity都重复以下3、4、5步骤)

总结工作中的Android内存泄漏问题_第2张图片
在MAT中搜索BrowserActiviy

4)右键该其中一项,打开菜单选择list objects ->with incoming refs将列出该类的实例

下图展示了对象间的引用关系。

总结工作中的Android内存泄漏问题_第3张图片
BrowserActivity在内存中的引用关系

5)右健Path to GC Roots-->exclue all phantom/weak/soft etc. reference,找出这个实例GC后,还会存在什么对象的引用关系。

下图的情况是:NetworkChangeNotifier类导致BrowserActivity导致内存泄漏

总结工作中的Android内存泄漏问题_第4张图片
导致BrowserActivity内存泄漏的引用关系图

常见导致内存泄漏的几个点

生命周期的原因

比如:Activity中关联了一个生命周期超过Activity的Thread,这个Thread 若持有该Activity的引用,就会导致内存泄漏。

内部类的原因

因为内部类会隐式地持有外部类的引用,若内部类不被释放,外部类也是无法释放。常见的有内部的Listener、Callback、Handler等导致。

情景1:若外部类应该释放的时候,内部类还在执行里面的函数,会导致外部类无法释放。

情景2:若一个异步操作,会回调内部类的Listener、Callback、Handler。当外部类应该释放的时候,但是这个异步操作还存在,而这个异步操作类又持有了Listener、Callback、Handler,导致外部类无法被释放。PS:这个原因也属于生命周期的原因。

静态变量的原因

单例类里包含Activity

静态变量的类里引用到Activity

注册与反注册、打开与关闭没成对出现的原因

比如:注册广播接收器、注册观察者(典型的譬如数据库的监听)等。或者自己写的跟Activity引用有关的clear()函数没有成对出现


解决方法

1)解决内部类的问题(以Handler作为例子)

  1. onDestroy时候remove所有msgActivity finish后未处理的msg是问题根源,所以清空所有未被执行的msg
mHandler.removeCallbacksAndMessages(null);

PS:比如Listener、Callback等其他内部类的问题,页面退出的时候,应该完成必要的清理操作,比如Cancel 请求

  1. 使用静态内部类 + weakReference
    静态内部类不会保留对外部类的引用,如果一定要引用外部类,使用weakReference
    static class MyHandler extends Handler {
        WeakReference mActivityReference;
        MyHandler(Activity activity) {
            mActivityReference= new WeakReference(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
            final Activity activity = mActivityReference.get();
            if (activity != null) {
                mImageView.setImageBitmap(mBitmap);
            }
        }
    }

PS:比如Listener、Callback等其他内部类的问题,也可以通过这个方法来解决

2)单例类里面尽量不要传入Activity,最好穿入ApplicationContext。假如传入了Activity,持有的时长也不能大于Activity的生命周期

3)对象的注册与反注册要成对出现

4)不使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存

5)因为View会持有Context,所以注意不要异步引用View,不要让静态对象持有View,不要在集合框架中存储View

你可能感兴趣的:(总结工作中的Android内存泄漏问题)