1、LeakCanary的简介:
在Android的性能优化中,内存优化是必不可少的点,而内存优化最重要的一点就是解决内存泄漏的问题,在Android的内存泄漏分析工具也不少,比如PC端的有:AndroidStudio自带的Android Profiler、MAT等工具;手机端也有,就是我们今天要介绍的
Square公司基于MAT开源了LeakCanary
LeakCanary显示内存泄漏的页面:
2、LeakCanary的使用
在app build.gradle 中加入引用:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
在 Application 中:
public class LeakApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {//1
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
如果当前的进程是用来给LeakCanary 进行堆分析的则return,否则会执行LeakCanary的install方法。如果检测到某个Activity 有内存泄露,LeakCanary 就会给出提示。
代码检测Activity的泄露以及其他类的泄露,需要使用RefWatcher来进行监控。改写Application,如下所示:
public class MyApplication extends Application {
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher= setupLeakCanary();
}
private RefWatcher setupLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
}
return LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
MyApplication leakApplication = (MyApplication) context.getApplicationContext();
return leakApplication.refWatcher;
}
}
install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。
3、举个栗子:
内存泄漏的代码中引入LeakCanary监控
(1)、LeakThread
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LeakThread leakThread = new LeakThread();
leakThread.start();
}
class LeakThread extends Thread {
@Override
public void run() {
try {
CommonUtils.getInstance(MainActivity.this);
Thread.sleep(6 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。
运行程序,会在界面生成一个名为Leaks的应用图标。接下来不断的切换横竖屏,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来。
解决方法就是将LeakThread改为静态内部类,再次运行程序LeakThread就不会给出内存泄漏的提示了。
如果还关注fragment的泄漏情况,那么Application加上RefWatcher,然后在对应fragment页面中onDestroy中加入
RefWatcher refWatcher = MyApplication.getRefWatcher(this);
refWatcher.watch(this);
下面举个单例造成的内存泄漏
(2)Singleton
public class Singleton {
private static Singleton singleton;
private Context context;
private Singleton(Context context) {
this.context = context;
}
public static Singleton newInstance(Context context) {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null){//双重检查锁定
singleton = new Singleton(context);
}
}
}
return singleton;
}
}
在需要监听的对象中调用RefWatcher的watch方法进行监听,我们可以在该Acitivity中onCreate方法中添加getRefWatcher().watch(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getRefWatcher().watch(this);
setContentView(R.layout.activity_main);
Singleton singleton = Singleton.newInstance(this);
}
解决方案就是 单列模式来产生内存泄漏 Singleton singleton = Singleton.newInstance(this)的调用传入Contxt时使用ApplicationContext
(3)Handler
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
mTextView.setText(msg.obj + "");
break;
default:
}
}
};
private void doInBackground() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//耗时操作完成,发送消息给UI线程
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "更新UI";
mHandler.sendMessage(msg);
}
}).start();
}
}
handler发送的消息在当前handler的消息队列中,如果此时activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类(非静态内部类),我们知道内部类天然持有外部类的实例引用,这样在GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,因而就不会去回收这个Activity,进而导致activity泄露,所以handler要定义为static。
(4)、非静态内部类创建的静态实例造成的内存泄漏
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
}
class TestResource {
//...
}
}
这样就在Activity中创建了非静态内部类,非静态内部类默认持有Activity类的引用,但是他的生命周期还是和应用程序一样长,所以当Activity销毁时,静态内部类的对象引用不会被GC回收,就会造成了内存溢出,解决办法:
1、将内部类改为静态内部类。 2、将这个内部类封装成一个单例,Context使用Application的Context
4、针对LeakCanary解决方案
出现的场景: 出现的详细场景
(1)、单例设计模式造成的内存泄漏
(2)、非静态内部类创建的静态实例造成的内存泄漏
(3)、Handler造成的内存泄漏
(4)、线程造成的内存泄漏
(5)、资源未关闭造成的内存泄漏
(6)、集合类造成内存泄漏
(7)、WebView造成内存泄漏
(8)EditText造成内存泄漏
(9)属性动画造成内存泄漏
常见的解决方案: 内存泄漏检测工具:Android Profiler LeakCanary
1、尽量使用Application的Context而不是Activity的
2、使用弱引用或者软引用漏
3、手动设置null,解除引用关系漏
4、将内部类设置为static,不隐式持有外部的实例漏
5、在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源时,一定要在Activity中的OnDestry中及时的关闭、注销或者释放内存,
5、LeakCanary检测原理 源文
(1)、ReftWatcher是leakcancay检测内存泄露的发起点。使用方法为,在对象生命周期即将结束的时候,调用
RefWatcher.watch(Object object)
public void watch(Object watchedReference, String referenceName) {
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
//如果处于debug模式,则直接返回
if (debuggerControl.isDebuggerAttached()) {
return;
}
//记住开始观测的时间
final long watchStartNanoTime = System.nanoTime();
//生成一个随机的key,并加入set中
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
//生成一个KeyedWeakReference
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//调用watchExecutor,执行内存泄露的检测
watchExecutor.execute(new Runnable() {
@Override public void run() {
ensureGone(reference, watchStartNanoTime);
}
});
}
- watchExecutor: 执行内存泄露检测的executor
- debuggerControl :用于查询是否正在调试中,调试中不会执行内存泄露检测
- queue : 用于判断弱引用所持有的对象是否已被GC。
- gcTrigger: 用于在判断内存泄露之前,再给一次GC的机会
- headDumper: 用于在产生内存泄露室执行dump 内存heap
- heapdumpListener: 用于分析前面产生的dump文件,找到内存泄露的原因
- excludedRefs: 用于排除某些系统bug导致的内存泄露
- retainedKeys: 持有那些呆检测以及产生内存泄露的引用的key。
所以最后的核心函数是在ensureGone这个runnable里面。要理解其工作原理,就得从keyedWeakReference说起
从watch函数中,可以看到,每次检测对象内存是否泄露时,我们都会生成一个KeyedReferenceQueue,这个类其实就是一个WeakReference,只不过其额外附带了一个key和一个name
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}
在构造时我们需要传入一个ReferenceQueue,这个ReferenceQueue是直接传入了WeakReference中
WeakReference与ReferenceQueue联合使用,如果弱引用关联的对象被回收,则会把这个弱引用加入到ReferenceQueue中;通过这个原理,可以看出removeWeaklyReachableReferences()执行后,会对应删除KeyedWeakReference的数据。如果这个引用继续存在,那么就说明没有被回收。
为了确保最大保险的判定是否被回收,一共执行了两次回收判定,包括一次手动GC后的回收判定。两次都没有被回收,很大程度上说明了这个对象的内存被泄漏了,但并不能100%保证;因此LeakCanary是存在极小程度的误差的。
(2)、检测的流程:
- 移除不可达引用,如果当前引用不存在了,则不继续执行
- 手动触发GC操作,gcTrigger中封装了gc操作的代码
- 再次移除不可达引用,如果引用不存在了,则不继续执行
- 如果两次判定都没有被回收,则开始分析这个引用,最终生成HeapDump信息
(3)、监测时机
什么时候去检测能判定内存泄露呢?这个可以看AndroidWatchExecutor的实现
public final class AndroidWatchExecutor implements Executor {
private void executeDelayedAfterIdleUnsafe(final Runnable runnable) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
backgroundHandler.postDelayed(runnable, DELAY_MILLIS);
return false;
}
});
}
}
这里又看到一个比较少的用法,IdleHandler,IdleHandler的原理就是在messageQueue因为空闲等待消息时给使用者一个hook。那AndroidWatchExecutor会在主线程空闲的时候,派发一个后台任务,这个后台任务会在DELAY_MILLIS时间之后执行。LeakCanary设置的是5秒。
6、总结:
LeakCanary对于内存泄漏的检测非常有效,但也并不是所有的内存泄漏都能检测出来。
<1>无法检测出Service中的内存泄漏问题
<2>如果最底层的MainActivity一直未走onDestroy生命周期(它在Activity栈的最底层),无法检测出它的调用栈的内存泄漏。
所以说LeakCanary针对Activity/Fragment的内存泄漏检测非常好用,但是对于以上检测不到的情况,还得配合Android Monitor + MAT 来分析。