LeakCanary,由Square开源的一款轻量第三方内存走漏检测工具。能够在不影响程序正常运转的状况下,动态搜集程序存在的内存走漏问题。小的内存走漏可能不会直接导致程序溃散,但跟着数量增多,量变引起质变,形成内存溢出,程序溃散。由于LeakCanary功用强大且部署简略的特点,深受大家喜爱。
简略运用
新版别2.x比较1.x的差异不只仅是开发言语改为kotlin,也不需求手动进行初始化了,只需求在主模块中的build.gradle中添加如下依靠,即可完结初始化。
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}
经过这儿的引证办法可见,LeakCanary只对debug版别apk起作用,考虑到其对内存的检测就很耗费系统资源,默许只允许debug包运用也很正常,当然,不只仅是这单个原因,后续会细讲。添加完依靠再运转程序后,就会一起运转leakCanary。退出后,手机桌面会自动生成leakcanary的图标。此时点开图标,假如有内存走漏状况产生,里边就会提示相应信息。界面如下:

为什么会产生内存走漏
长生命周期的目标持有了短生命周期的引证导致本应该被收回的内存无法收回。为了维持多使命环境的正常运转,Android系统会为每个运用的堆大小设置硬性上限。跟着内存走漏的不断累积,APP会逐渐耗费完内存,导致内存溢出,终究引发OOM。
常见内存走漏场景
单例
public class SingletonActivityContext {
private static SingletonActivityContext instance;
private Context context;
private SingletonActivityContext(Context context){
this.context = context;
}
public static SingletonActivityContext getInstance(Context context){
if (instance == null ){
instance = new SingletonActivityContext(context);
}
return instance;
}
}
单例的static变量由于static特性,使得其生命周期跟咱们运用生命周期是相同长的,这时分假如运用不当,很简单形成内存走漏。例如上述代码,假如传入Context是Activity的话,则当Activity退出的时分,其内存并不会被收回。由于代码中的单例目标持有了activity的引证,会导致activity想被内存收回的时分无法被收回。其处理办法,便是留意传入Context是Application即可:

非静态内部类创立静态实例形成的内部走漏
public class StaticLeakActivity extends Activity {
public static innerStaticClass mData = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (mData == null){
mData = new innerStaticClass();
}
}
private class innerStaticClass{
}
}
例如,上述代码,能够防止innerStaticClass目标的重复创立,但这样也会形成内存走漏。由于在这儿,innerStaticClass默许会持有外部StaticLeakActivity的引证,由于其被static修饰,导致innerStaticClass这个变量的生命周期跟咱们运用的生命周期相同长,这就致使StaticLeakActivity想被内存收回的时分无法被收回。
Handler
handler形成内存走漏的场景非常普遍,许多时分,咱们为了防止ANR,不在主线程做耗时操作,这时分处理网络使命或许处理网络回调的时分,咱们都需求借助handler来处理。handler内部跟Message、MessageQueue相互关联在一起,假如handler发送音讯的时分,Message没有被处理完结,这个Message以及发送该Message的目标就将被咱们的线程所一向持有,这儿的handler实际上便是个TLS变量(生命周期与整个Activity生命周期不共同),因而这种完结办法很难保证跟Activity生命周期共同,这样就很简单导致内存走漏。
public class HandlerLeakActivity extends Activity {
private final Handler mLeakHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLeakHandler.postDelayed(new Runnable() {
@Override
public void run() {
}
},1000*60);
finish();
}
}
对应处理办法很简略:1、将Handler声明为静态的(防止在Activity内运用静态类),这样Handler的生命周期就与activity生命周期无关了;2、经过弱引证的办法引入Activity,在Handler内部持有外部类HandlerLeakActivity弱引证,防止将Activity作为Context直接传进去。
线程所形成的内存走漏
在平常开发过程中,线程形成的内存走漏是常见的,由于平常开发工作中,经常会有敞开线程操作,假如这个时分你界说了其间的AsyncTask或许Runnable,界说成非静态内部类的话,则当Activity想要被收回的时分,由于线程使命持有了Activity的隐式引证,会使得Activity被毁掉的时分由于使命没有完结而导致无法被收回,由此导致内存走漏。
public class ThreadLeakActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
testThreadLeak();
}
private void testThreadLeak() {
new Thread((Runnable) () -> {
SystemClock.sleep(1000);
}).start();
}
}
处理办法:能够将Runnable界说为静态内部类,这样能够防止Activity内存资源走漏,一起,能够在onDestory毁掉时,调用AsyncTask的cancel()等。
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(1000);
}
}
此状况与上述Handler形成的内存走漏状况相似,原因相同,因而都是把Handler或许Runnable界说成静态内部类,然后不持有外部类的引证,这样就能使咱们的外部类Activity被收回。
WebView
webView形成内存走漏的原因也很简略:webView内部的一些线程持有activity目标,导致activity无法开释。反映在正常用户的操作上便是,用户反复进出webView页面,页面占用内存不断升高,终究触发GC。 有以下两种防止办法:
一、不在xml中界说Webview(这样会引证Activity),而是在需求的时分在Activity中创立,并且上下文目标运用getApplicationgContext();
二、给运用WebView的Activity独自敞开一个新进程,经过AIDL来做数据交互。
检测内存走漏的计划
有问题,就有处理办法。对于内存走漏问题,也是八仙过海、各显神通。字节有Liko、快手有KOOM,也有人独自运用profile等工具。而LeakCanary便是很多计划中的一种:
LeakCanary: It automatically watches destroyed activities and fragments, triggers a heap dump, runs Shark Android and then displays the result.
LeakCanary的图标是一只鸟,也是其单词的直接含义—金丝雀(金丝雀对有害气体具有必定程度的敏感性,因而常常在矿场里检测矿井中气体)。
发动机遇
LeakCanary在更新至2.x版别后,最大的一个不同便是不用在Application的onCreate中对其进行初始化处理了。


Watcher和Activity的监测机遇


这儿,咱们先来看下ActivityDestroyWatcher.install()的源码:


如此,能够说,install()办法中注册了Activity生命周期监听,在监听到onDestroy()时,调用objectWatcher.watch()开端监测Activity。
到这儿了,能够发现,下一步的突破办法在watch()。咱们来调查一下此办法:

要留意的是③中的checkRetainedExecutor是传入参数,此处由InternalAppWatcher.kt中的checkRetainedExecutor做传入参数:












很明显,这儿咱们要先调查heapDumper.dumpHeap()中的内部完结:










Fragment的监测机遇



ViewModel的检测机遇
关于ViewModel的检测机遇,则要重视ViewModelClearedWatcher。ViewModelClearedWatcher承继自ViewModel,其自身是一个ViewModel,且完结了onCleared(),这个办法相似Activity的onDestroy(),在ViewModel毁掉的时分会履行。

RootView的检测
需求留意的是,这儿是RootView,即根View,不是一切View。LeakCanary默许检测一切的根View。详细代码就不贴了,都是一个套路。经过监听View的OnAttachStateChangeListener毁掉监听,当View和Window绑定和取消绑定的时会回调此办法。
流程图
以Activity的检测流程为例,其流程图如下:

总结一下
总结一下,LeakCanary流程中最经常被问到的两个问题:
1、怎么确认内存走漏目标 : WeakReference,其双参数结构函数支持传入一个ReferenceQueue,当其关联的目标收回时,会将WeakReference参加ReferenceQueue中。LeakCanary承继ReferenceQueue,将每个需求监测的目标WeakReference参加一个map中。在GC过后,经过removeWeaklyReachableObjects()遍历ReferenceQueue,经过key值删除map中已收回的目标,剩下的就能够确认产生了内存走漏。
2、怎么确认从GCroot到走漏目标的引证链 : 在确认内存走漏的目标后,checkRetainedObjects(),发动前台服务HeapAnalyzerService,这时分就能在告诉中看到了。而HeapAnalyzerService中则是调用了HeapAnalyzer的analyze办法进行堆内存剖析,此功用由Shark库完结。