项目开发时,由于各种原因会导致内存泄漏,小则几B,大则几百K甚至几M,Android内存本就有限,内存泄漏会使有限的内存不能够充分使用,严重影响性能.严重时会导致OOM程序崩溃.检测内存泄漏的工具有很多,Android Studio自带的就有,今天介绍一下著名的检测内存泄漏的开源库Leak Canary.几行代码就可以帮你检测项目中存在的内存泄漏,帮助我们进行代码优化.
名字解释:
1.内存泄漏:一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然 该对象占用的内存就无法被使用,这就造成了内存泄露。
2.内存溢出OOM:应用程序申请使用的内存超过了Android虚拟机分配的最大内存.
3.OOM发生的原因:
Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。我们平常看到的OutOfMemory的错误,通常 是堆内存溢出.对一般手机应用,堆内存的上限值只有16M。Android的缺 省值是16M(某些机型是24M),而对于普通应用这是不能改的,当应用程序处理大资源的资源,如图片或视频等媒体资源时 ,数量一多,时间一长,这个16M是很容易耗尽的,OOM是很容易出现的。
为了能够使得Android应用程序安全且快速的运行,Android的每个应用程序都 会使用一个专有的Dalvik虚拟机实例来运行,它是由Zygote服务进程孵化出来的,也就是说每个应用程序都是在属于自己的进程中运行的。 Android为不同类型的进程分配了不同的内存使用上限,如果程序在运行过程中出现了内存泄漏的而造成应用进程使用的内存超过了这个上限,则会被系统视 为内存溢出,从而被kill掉,这使得仅仅自己的进程被kill掉,而不会影响其他进程(如果是system_process等系统进程出问题的话,则会 引起系统重启),我们的应用程序就会崩溃,我们就会看到OOM。
Leak Canary基本使用
Github地址:<a href="https://github.com/square/leakcanary" target="_blank">https://github.com/square/leakcanary</a>
引入依赖:
//debug时采用的库
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
//打包Release采用的库,no-op:空操作的意思,其实这个库是一种空实现,里面没有任何实际的代码,这样打APK包时就不会增加APK的大小
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
在自己项目的Applicaiton的onCreate中加入如下代码:
public void onCreate() {
super.onCreate();
/**
* 为什么要加这个判断:
* 判断Application是否是在service进程里面启动
* 因为leakcanay会开启一个远程service用来分析每次产生的内存泄露,
* 而安卓的应用每次开启进程都会调用Applicaiton的onCreate方法,
* 因此我们有必要预先判断此次Application启动是不是在analyze service启动时,
*/
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
//这个进程是用于LeakCanary堆分析的,你不应该在此进程初始化你的app
return;
}
refWatcher = LeakCanary.install(this);
}
好了,Leak Canary的使用就是这么简单,接下来debug模式运行你的项目,如果有内存泄漏发生,会有一个弹窗提示,并且在通知栏会有追踪信息,你可以根据这些信息找到内存泄露的位置,进而分析原因,优化项目.
前面的操作,只是能检测到Activity中发生的内存泄漏,因为Leak Canary内部已经帮我们实现的activity中检测内存泄露的逻辑.如果要在Fragment中也检测到内存泄漏.只要加一行代码即可:
不过Application中的代码需要略加改变:
public class LeakApplication extends Application {
//需要保存refWatcher变量,因为在fragment中需要用到
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
refWatcher = LeakCanary.install(this);
}
public RefWatcher getRefWatcher(){
return refWatcher;
}
}
然后在fragment中的onDestroyView()方法中添加如下代码(建议在BaseFragment中添加):
//LeakApplication是你的Application类
RefWatcher refWatcher = ((LeakApplication) getActivity().getApplication()).getRefWatcher();
if (refWatcher != null) {
refWatcher.watch(this);
}
使用实例分析:
1:非静态内部类导致的内存泄漏
在activity中添加如下代码:
new Thread(new Runnable() {
@Override
public void run() {
try {
//睡眠较长时间
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
然后按返回键,稍等片刻会有内存泄漏提示,追踪链信息如下:
点击右侧的加号可以查看详细的信息:
根据这些信息,分析自己写的代码大概可以定位到内存泄漏的位置:我在activity中new了一个子线程,用了一个Runnable匿名内部类,然后模拟很耗时的操作,让线程睡眠了30s,按下返回键时,activity执行onDestroy()方法,应该被GC掉,释放内存.但是因为非静态内部类会对外部类对象有一个隐式的强引用,也就是说runnable对Activity持有强引用,导致activity不能得到释放,发生了内存泄漏.
2.handler使用不规范导致的内存泄漏
public class HandlerActivity extends AppCompatActivity {
@Bind(R.id.tvCount)
TextView tvCount;
private Handler mhandler = new Handler();
private int count = 60;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
ButterKnife.bind(this);
}
@OnClick(R.id.btnStart)
public void onClick() {
//handler每隔1s发送一个消息,实现倒计时
mhandler.removeCallbacksAndMessages(null);
count = 60;
mhandler.post(new Runnable() {
@Override
public void run() {
tvCount.setText(count+"");
count--;
mhandler.postDelayed(this,1000);
}
});
}
}
解决办法是在activity的onDestroy()方法中调用:
mhandler.removeCallbacksAndMessages(null);//移除该handler消息队列中所有的消息和callback
还有很多内存泄漏发生的场景,这里就不在一一举例了,可以参考博客:
Android OOM原因总结