在分析Leak Canary原理以前,咱们先来简单了解WeakReference和ReferenceQueue的做用,为何要了解这些知识呢?Leak Canary其实内部就是使用这个机制来监控对象是否被回收了,固然Leak Canary的监控仅仅针对Activity和Fragment,因此这块有引入了ActivityLifecycleCallBack,后面会说,这里的回收是指JVM在合适的时间触发GC,并将回收的WeakReference对象放入与之关联的ReferenceQueue中表示GC回收了该对象,Leak Canary经过上卖弄的检测返现有些对象的生命周期本该已经结束了,可是任然在占用内存,这时候就断定是已经泄露了,那么下一步就是开始解析析headump文件,分析引用链,至此就结束了,其中须要注意的是这是WeakReference.get方法获取到的对象是null,因此Leak Canary使用了继承WeakReference.类,并把传入的对象做为成员变量保存起来,这样当GC发生时虽然把WeakReference中引用的对象置为了null也不会把WeakReference中咱们拓展的类的成员变量置为null,这样咱们就能够作其余的操做,好比:Leak Canary中把WeakReference存放在Set集合中,在恰当的时候须要移除Set中的WeakReference的引用,这个机制Glide中的内存缓存 也是使用了该机制,关于WeakReference和ReferenceQueue机制就很少说网上有不少能够了解一下。java
/**
* 监控对象被回收,由于若是被回收就会就如与之关联的队列中
*/
private void monitorClearedResources() {
Log.e("tag", "start monitor");
try {
int n = 0;
WeakReference k;
while ((k = (WeakReference) referenceQueue.remove()) != null) {
Log.e("tag", (++n) + "回收了:" + k + " object: " + k.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private ReferenceQueue<WeakRefrencesBean> referenceQueue = new ReferenceQueue<>();
class WeakRefrencesBean {
private String name;
public WeakRefrencesBean(String name) {
this.name = name;
}
}
new Thread() {
@Override
public void run() {
monitorClearedResources();
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
new WeakReference<WeakRefrencesBean>(new WeakRefrencesBean("aaaa"), referenceQueue);
}
}
}.start();
复制代码
输出的日志:android
1回收了:java.lang.ref.WeakReference@21f8376e object: null
2回收了:java.lang.ref.WeakReference@24a74e0f object: null
3回收了:java.lang.ref.WeakReference@39efe9c object: null
4回收了:java.lang.ref.WeakReference@4ee20a5 object: null
3回收了:java.lang.ref.WeakReference@bf45c7a object: null
4回收了:java.lang.ref.WeakReference@b94bc2b object: null
5回收了:java.lang.ref.WeakReference@33eb6888 object: null
复制代码
上面是一个监控对象回收,由于若是对象被回收就把该对象加入如与之关联的队列中,接着开启线程制造触发GC,并开启线程监控对象回收,Leak Canary也是利用这个机制完成对一些对象本该生命周期已经结束,还常驻内存,就算触发GC也不会回收,Leak Canary就判断为泄漏,针对于内存泄漏,咱们知道有些对象是不能被GC回收的,JVM虚拟机的回收就是可达性算法,就是从GC Root开始检测,若是不可达那么就会被第一次标志,再次GC就会被回收。git
步骤无非就是: 一、安装,实际上就是作一些初始化的操做; 二、检测时机,好比:回调onActivityDestroyed方法开始检测; 三、UI的展现;github
Leak Canary的地方就是 LeakCanary.install(this)方法开始,代码以下:算法
通常咱们使用Leak Canaryu都是在Application中调用:缓存
public class ExampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
setupLeakCanary();
}
protected void setupLeakCanary() {
enabledStrictMode();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
...
}
复制代码
在install方法以前有个判断,这个判断是用来判断是不是在LeakCanary的堆统计进程(HeapAnalyzerService),也就是咱们不能在咱们的App进程中初始化LeakCanary,代码以下:app
/**
* 当前进程是不是运行{@link HeapAnalyzerService}的进程中,这是一个与普通应用程序进程不一样的进程。
*/
public static boolean isInAnalyzerProcess(@NonNull Context context) {
Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
// 这里只须要为每一个进程计算一次。
if (isInAnalyzerProcess == null) {
//把Context和HeapAnalyzerService服务做为参数传进isInServiceProcess方法中
isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
}
return isInAnalyzerProcess;
}
复制代码
在isInAnalyzerProcess方法中有调用了isInServiceProcess方法,代码以下:dom
public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
} catch (Exception e) {
CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
return false;
}
//主进程
String mainProcess = packageInfo.applicationInfo.processName;
//构造进程
ComponentName component = new ComponentName(context, serviceClass);
ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, PackageManager.GET_DISABLED_COMPONENTS);
} catch (PackageManager.NameNotFoundException ignored) {
// Service is disabled.
return false;
}
//判断当前HeapAnalyzerService服务进程名和主进程名是否相等,若是相等直接返回false,由于LeakCanary不能再当前进程中运行
if (serviceInfo.processName.equals(mainProcess)) {
CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
// Technically we are in the service process, but we're not in the service dedicated process.
return false;
}
int myPid = android.os.Process.myPid();
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.RunningAppProcessInfo myProcess = null;
List<ActivityManager.RunningAppProcessInfo> runningProcesses;
try {
runningProcesses = activityManager.getRunningAppProcesses();
} catch (SecurityException exception) {
// https://github.com/square/leakcanary/issues/948
CanaryLog.d("Could not get running app processes %d", exception);
return false;
}
if (runningProcesses != null) {
for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
if (process.pid == myPid) {
myProcess = process;
break;
}
}
}
if (myProcess == null) {
CanaryLog.d("Could not find running process for %d", myPid);
return false;
}
return myProcess.processName.equals(serviceInfo.processName);
}
复制代码
实际上LeakCanary最终会调用LeakCanaryInternals.isInServiceProcess方法,经过PackageManager、ActivityManager以及android.os.Process来判断当前进程是否为HeapAnalyzerService运行的进程,由于咱们不能在咱们的App进程中初始化LeakCanary。ide
接下来咱们开始LeakCanary真正的实现,从LeakCanary.install(this)方法开始,代码以下:ui
public static RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
复制代码
实际上这一步返回的RefWatcher的实现类AndroidRefWatcher,主要是作些关乎初始化的操做,这些不展开讲,直接进入buildAndInstall()方法中,代码以下:
public RefWatcher buildAndInstall() {
//install()方法只能一次调用,屡次调用将抛出异常
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//初始化RefWatcher,这个东西是用来检查内存泄漏的,包括解析堆转储文件这些东西
RefWatcher refWatcher = build();
//若是RefWatcher尚未初始化,就会进入这个分支
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
//setEnabledAsync最终调用了packageManager.setComponentEnabledSetting,
// 将Activity组件设置为可用,即在manifest中enable属性。
// 也就是说,当咱们运行LeakCanary.install(this)的时候,LeakCanary的icon才显示出来
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
//ActivityRefWatcher.install和FragmentRefWatcher.Helper.install的功能差很少,注册了生命周期监听。
// 不一样的是,前者用application监听Activity的生命周期,后者用Activity监听也就是Activity回调onActivityCreated方法,
// 而后获取FragmentManager调用registerFragmentLifecycleCallbacks方法注册,监听fragment的生命周期,
// 并且用到了leakcanary-support-fragment包,兼容了v4的fragment。
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
复制代码
在buildAndInstall方法中有几点:
RefWatcher类是用来监控对象的引用是否可达,当引用变成不可达,那么就会触发堆转储(HeapDumper),来看看RefWatcherBuilder.build方法的具体实现,代码以下:
public final RefWatcher build() {
//若是已经初始化了,直接返回RefWatcher.DISABLED表示已经初始化了
if (isDisabled()) {
return RefWatcher.DISABLED;
}
if (heapDumpBuilder.excludedRefs == null) {
heapDumpBuilder.excludedRefs(defaultExcludedRefs());
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
//建立堆转储对象
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
//返回的是HeapDumper.NONE,HeapDumper内部实现类,
heapDumper = defaultHeapDumper();
}
//建立监控线程池
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
//默认返回 NONE
watchExecutor = defaultWatchExecutor();
}
//默认的Gc触发器
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
//建立把参数构造RefWatcher
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
复制代码
如上代码知道,其实是为了建立RefWatcher实例,和一些在检测中的环境初始化,好比线程池、GC触发器等等。
回到最初的biuldInstall方法中,知道监控Activity和Fragment是查不到的因此这里就只分析Activity相关的,也就是ActivityRefWatcher.install方法,代码以下:
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//注册ActivityLifecycleCallbacks监听每个Activity的生命周期
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
复制代码
能够知道这里是使用的装饰模式,使用ActivityRefWatcher对RefWatcher作了包装,接着注册ActivityLifecycleCallbacks监听每个Activity的生命周期的onActivityDestroyed方法,这也就是检测泄漏开始的地方,而在onActivityDestroyed方法方法中会调用refWatcher.watch方法把activity做为参数传进去,代码以下:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityDestroyed(Activity activity) {
//当Activity被销毁了,那么应该检测是否内存泄漏
refWatcher.watch(activity);
}
};
复制代码
能够看到在Activity销毁时会回调onActivityDestroyed方法,而后把该activity做为参数传递给refWatcher.watch(activity)方法,watch方法代码以下:
public void watch(Object watchedReference, String referenceName) {
..........
final long watchStartNanoTime = System.nanoTime();
//给该引用生成UUID
String key = UUID.randomUUID().toString();
//给该引用的UUID保存至Set中,强引用
retainedKeys.add(key);
//KeyedWeakReference 继承至WeakReference,因为KeyedWeakReference若是回收了,那么当中的对象经过get返回的是null,
// 因此须要保存key和name做为标识,Glide也是此作法,KeyedWeakReference实现WeakReference
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
//开始检测
ensureGoneAsync(watchStartNanoTime, reference);
}
复制代码
在watch方法中有以下几点:
ensureGoneAsync方法代码以下:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override
public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
复制代码
在ensureGoneAsync方法中直接执行线程池(AndroidWatchExecutor),而这个线程池就是在刚开始的时候LeakCanary.install方法中建立RefWatcher的子类AndroidRefWatcher的时候建立的,接着看看ensureGone方法,代码以下:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//gc 开始的时间
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//从Set中移除不能访问引用,意思就是GC以后该引用对象是否加入队列了,若是已经加入队列说明不会形成泄漏的风险
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
//尝试GC
gcTrigger.runGc();
//从Set中移除不能访问引用,意思就是GC以后该引用对象是否加入队列了,若是已经加入队列说明不会形成泄漏的风险
removeWeaklyReachableReferences();
//到这一步说明该对象按理来讲声明周期是已经结束了的,可是经过前面的GC却不能回收,说明已经形成了内存泄漏,那么解析hprof文件,获得该对象的引用链,也就是要触发堆转储
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
//开始解释堆转储文件
heapdumpListener.analyze(heapDump);
}
return DONE;
}
复制代码
在ensureGone方法中有以下几点:
在前面说不少检测GC回收是怎么作到的呢,接下来看看removeWeaklyReachableReferences方法,代码以下:
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
//WeakReferences会在它们指向的对象变得没法访问时排队。 这是在完成或垃圾收集实际发生以前。
//队列不为null,说明该对象被收了,加入此队列
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
复制代码
这里直接使用一个while循环从队列取出元素进行判断,这里的queue.poll()是不会阻塞的,因此为何LeakCanary会作两次验证的缘由,为何LeakCanary不使用queue.remove()方法呢?你想一想queue.remove()方法是阻塞当前线程,从前面知道每次Activity或者Fragment销毁回调生命周期方法都会建立一个KeyedWeakReference实例,也就是说若是不泄露就一直阻塞当前线程,这样反而对形成没必要要的开销,我也是猜猜而已。