开发中,如果代码不规范很容易产生内存泄漏,比如Handler、Context、线程等使用。本文使用Android Studio自带的Profiler和MAT工具进行内存泄漏分析。
一、内存泄漏
1、定义handler
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
mHandler.sendEmptyMessage(0);
return false;
}
});
2、在onCreate()方法调用
mHandler.sendEmptyMessage(0);
以上件简单大的几句代码就发生了内存泄漏,可以用profiler工具查看。
二、profiler
1、使用profiler运行程序
点击小图标即可
2、选择分析Memory
点击MEMORY一栏
3、按返回键退出当前页面,然后点击垃圾桶小图标
4、在回收后截取内存的一块
5、底部按包名查找,找到对应包名下的文件
Allocations:申请的大小
Deallocations:释放的大小
Total Count:对象引用次数
Shallow Size:对象自身占用的内存大小
这里重点关注第三个参数,我们已经退出了当前的MainActivity界面,但是发下对象的引用个数为1,说明它没有被回收。发生了内存泄漏。下面使用MAT工具,分析具体的内存泄漏原因。
三、MAT
1、点击如下图标:
2、将选中的内存保存下来,后缀为hprof
3、只分析内存部分,需要使用命令行对文件进行过滤
找到hprof-conv.exe所在的目录,一般在SDK目录下的platform-tools目录。截图如下:
把文件复制到这里。然后执行命令行,生成新的文件memory-2.hprof。命令行如下:
hprof-conv -z 当前文件 目标文件
4、用MAT工具打开当前文件
(1)点击Histogram。
(2)在这里可以输入你要分析的文件名称,由上面的Profile工具分析可以看出泄漏的文件为MainActivity。所以直接输入MainActivity,按回车,右击当前文件
排除掉所有的弱引用和软引用等。我们只分析强引用对象。
(3)展开引用,排除系统的引用。查看哪些外部对象引用它
(4)如下图
排除系统的,发现MainActiivty$2内部类仍然连接着原始对象this。说明this是一个根对象,Handler是与他相连的。导致它无法回收。
四、解决
(1)方式一:使用移除消息的方式
在onDestroy()方法中添加如下代码:
mHandler.removeMessages(0);
优点:能够解决内存泄漏。
缺点:如果我们需要在界面结束的时候,需要继续在后台完成一个操作。完成后需要Handler通知,那么这种方式就失效了。
(2)方式二:使用弱引用
代码如下:
private class MyHandler extends Handler {
private WeakReference<MainActivity> mWeakReference;
MyHandler(MainActivity activity) {
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.e("11111111111", "2222222222222");
if (mWeakReference.get() != null) {
mHandler.sendEmptyMessage(0);
}
}
}
private Handler mHandler = new MyHandler(this);
优点:能够解决内存泄漏。
缺点:由于采用弱引用,不清楚什么时候被回收。代码不可控。
(3)方式三:使用静态内部类
代码如下:
private static class MyHandler extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.e("11111111111","2222222222222");
mHandler.sendEmptyMessage(0);
}
}
private static Handler mHandler = new MyHandler();
增加了static修饰符。静态内部类不持有外部类的引用。
优点:能够解决内存泄漏。
缺点:由于static修饰的对象,生命周期比较长。所以大量使用static容易导致内存溢出。
综上,还是要看自己的具体业务逻辑,采用不同的方式。