荐 【Android】LeakCanary原理解析
【Android】LeakCanary原理解析
学而不思则罔,思而不学则殆
源码
https://github.com/square/leakcanary/tags
下载的是1.6.1的版本,本来想Git clone的。但是国内的网络不行,clone超时,也想通过其他方式,就直接下载了一个ZIP的包,也方便。
概述
LeakCanary是Square公司为Android开发者提供的一个自动检测内存泄漏的工具,
LeakCanary本质上是一个基于MAT进行Android应用程序内存泄漏自动化检测的的开源工具,我们可以通过集成LeakCanary提供的jar包到自己的工程中,一旦检测到内存泄漏,LeakCanary就好dump Memory信息,并通过另一个进程分析内存泄漏的信息并展示出来,随时发现和定位内存泄漏问题,而不用每次在开发流程中都抽出专人来进行内存泄漏问题检测,极大地方便了Android应用程序的开发。
Leak使用可以大致分为以下四个方面:注册,观察,分析和展示
注册
注册调用非常简单,一句话就OK:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
但是中间干的事也不少,来看看注册过程中的时序图:
注册时序图
注册阶段主要是生成引用观察者{RefWatcher},然后注册到Application的生命周期方法中。当{onActivityCreated(Activity, Bundle)}回调被调用的时候,通过观察{Activity}引用是否全部回收。
注册类图
1.install
//LeakCanary.java
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
调用{refWatcher}生成一个{AndroidRefWatcherBuilder},{AndroidRefWatcherBuilder}这是一个采用构建者模式的构建类,主要是用来提供生成{RefWatcher}的参数,直接跳过中间设置参数的地方,直接看{buildAndInstall}方法;
2.buildAndInstall
类图如下:
//AndroidRefWatcherBuilder.java
public RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build(); //1.构建RefWatcher
if (refWatcher != DISABLED) {
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);//2.注册Activity监听
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);//3.注册Fragment监听
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
这个方法干了两件事,构建RefWatcher 和注册应用生命周期回调;其中注册监听回调分为Activity和Fragment。
1.build
通过调用父类{RefWatcherBuilder}的builde方法生成RefWathcer.采用的构建者模式;
//RefWatcherBuilder.java
public final RefWatcher build() {
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 = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
2.注册生命周期回调
//ActivityRefWatcher.java
public static void install(Context context, RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();//1.采用ApplicationContext
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);//2.创建ActivityRefWatcher
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);//3.注册回调
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
...
private final Application application;
private final RefWatcher refWatcher;
private ActivityRefWatcher(Application application, RefWatcher refWatcher) {
this.application = application;
this.refWatcher = refWatcher;
}
这里经过注册Activity的生命周期回调,整个注册过程就算结束了。当Activity执行onDestroyed方法时,就是调用onActivityDestroyed方法,接下来就进入到观察阶段了。
其中Fragment注册跟Activity差不多,这里就不在赘述。
接下来就进入观察阶段…
观察
先来一个观察阶段的时序图:
观察时序图
//ActivityRefWatcher.java
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
private final Application application;
private final RefWatcher refWatcher;
private ActivityRefWatcher(Application application, RefWatcher refWatcher) {
this.application = application;
this.refWatcher = refWatcher;
}
接收回调{onActivityDestroyed}的时候,调用RefWatcher的watch方法。把Activity加入到观察队列中。
那就来看看RefWatcher类是什么?图先整上。
RefWatcher类图
根据前面我们可以知道该:
//RefWatcherBuilder.java
public final RefWatcher build() {
...
//创建RefWatcher
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
1.watch
/**
* Identical to {@link #watch(Object, String)} with an empty string reference name.
*
* @see #watch(Object, String)
*/
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
* with.
*
* @param referenceName An logical identifier for the watched object.
*/
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) { //release版本中会生成一个DISABLED对象,直接返回
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString(); //1.随机生成key
retainedKeys.add(key); //2.加入到Set中,用来判断该对象是否被回收
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);//创建KeyedWeakReference
ensureGoneAsync(watchStartNanoTime, reference);
}
2.ensureGoneAsync
进入异步队列
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
WatchExecutor是一个接口,其默认实现是空实现。WatchExecutor和Retryable 的关系可以类比为线程池和Runnable的关系
public interface WatchExecutor {
WatchExecutor NONE = new WatchExecutor() {
@Override public void execute(Retryable retryable) {
}
};
void execute(Retryable retryable);
}
当然在Android的实现是{AndroidWatchExecutor},类图如下。
{AndroidWatchExecutor}中有两个Handler,一个是主线程MainHandler,一个是子线程ThreadHandler。
//AndroidWatchExecutor.java
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
@Override
public void execute(Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) { //主线程调用
waitForIdle(retryable, 0);
} else { //子线程调用
postWaitForIdle(retryable, 0);
}
}
子类实现分为主线程调用和子线程调用
子线程调用
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
可以看到这里子线程调用还是扔到了主线程的{waitForIdle}方法。
主线程调用
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
很奇怪,怎么是{addIdleHandler}而不是直接处理呢?因为addIdleHandler调用的时机是消息空闲的时候被调用,不能GC操作不能阻塞主线程UI的信息。先了解Handler 空闲消息的小伙伴可以看Andriod-消息机制Handler
由于这个方法{waitForIdle}是在主线程被调用,所以这里的MQ也是主线的MQ,queueIdle返回的false,该消息会在结束后移除空闲消息队列。
//MessageQueue.java
Message next() {
...
for (;;) {
...
//
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle(); //空闲消息执行
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) { //移除空闲消息
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
...
}
}
刚刚分析我们发现,主线程和子线程都会调用{postToBackgroundWithDelay}方法
postToBackgroundWithDelay
在主线程空闲的时候,执行空闲消息,将消息放到子线程{new HandlerThread(LEAK_CANARY_THREAD_NAME)}
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
这里才是真正调用的地方,调用{retryable.run()}。delayMillis的计算有一些控制重试次数和初始化延时的逻辑。
这是时候回到{2.ensureGoneAsync},会调用{ensureGone}
3.ensureGone
//RefWatcher.java
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime(); //1.GC开始时间
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //2.GC开始时间-观察开始时间
removeWeaklyReachableReferences(); //3.移除被回收对象的引用key
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) { //4.判断当前reference引用的对象是否已经被回收掉
return DONE;
}
gcTrigger.runGc(); //5.触发GC
removeWeaklyReachableReferences();//6.再一次移除被回收对象的引用key
if (!gone(reference)) { //7.再一次判断当前reference引用的对象是否已经被回收掉
long startDumpHeap = System.nanoTime();//8.开始dump时间
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);//9.计算GC花费的时间
File heapDumpFile = heapDumper.dumpHeap(); //10.dump文件
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY; //重试
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); //11.统计dump花费的时间
//12.构建HeapDump,很重要,后续分析全靠它
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
heapdumpListener.analyze(heapDump);//13.回调,主要是回调给服务,去分析泄漏原因
}
return DONE;
}
这里算是观察过程中的最重要的逻辑,可以仔细阅读一下。中间有用到弱引用回收队列,想了解的可以查看【Java】Java的四种引用
1.GC开始时间
2.GC开始时间-观察开始时间
3.移除被回收对象的引用key
4.判断当前reference引用的对象是否已经被回收掉
5.触发GC
6.再一次移除被回收对象的引用key
7.再一次判断当前reference引用的对象是否已经被回收掉
8.开始dump时间
9.计算GC花费的时间
10.dump文件
11.统计dump花费的时间
12.构建HeapDump,很重要,后续分析全靠它
13.回调,主要是回调给服务,去分析泄漏原因
这中间的相关类几乎都采用的接口的模式来实现,便于复用和理解。
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.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
如果KeyedWeakReference 引用的对象被回收了,KeyedWeakReference 对象就会被添加到ReferenceQueue队列中,然后在移除retainedKeys中的key.
gone
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
如果retainedKeys中不包含{reference.key},则说明这个引用对象已经被回收了。否则没有被回收。
runGc
默认实现:
public interface GcTrigger {
GcTrigger DEFAULT = new GcTrigger() {
@Override public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc(); //触发GC
enqueueReferences(); //等待100ms
System.runFinalization();
}
private void enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
void runGc();
}
dumpHeap
如果GC过后,retainedKeys中任然持有该引用的KEY,则说明这个对象没有被回收,发生了泄漏,dumo泄漏的情况。
实现在{AndroidHeapDumper}.
创建HeapDump
dump文件最终的文件是什么样子?
2020-08-23 15:32:37.666 28620-28644/com.example.leakcanary I/System.out: ensureGone heapDumpFile:/storage/emulated/0/Download/leakcanary-com.example.leakcanary/d6fa75e0-a70b-4c9d-9075-f901876318ae_pending.hprof
HeapDump heapDump = heapDumpBuilder
.heapDumpFile(heapDumpFile)
.referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
这里有三个时间:
时间 | 说明 |
---|---|
watchDurationMs | 观察到GC的时间间隔 |
gcDurationMs | GC花费的时间 |
heapDumpDurationMs | dump花费的时间 |
回调analyze
启动服务,分析和后面展示。
public final class ServiceHeapDumpListener implements HeapDump.Listener {
private final Context context;
private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;
public ServiceHeapDumpListener(final Context context,
final Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
this.context = checkNotNull(context, "context").getApplicationContext();
}
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); //启动服务
}
}
我们来看看HeapDump 对象是什么样子?
HeapDump{heapDumpFile=/storage/emulated/0/Download/leakcanary-com.example.leakcanary/0d1469fe-88b0-4722-bc73-0c346057e78f_pending.hprof
, referenceKey='b05c7f1d-c1b3-4d16-919a-d5c8e673c0a8'
, referenceName=''
, excludedRefs=| Field: android.os.Message.obj
| Field: android.os.Message.next
| Field: android.os.Message.target
| Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
| Thread:FinalizerWatchdogDaemon (always)
| Thread:main (always)
| Thread:LeakCanary-Heap-Dump (always)
| Class:java.lang.ref.WeakReference (always)
| Class:java.lang.ref.SoftReference (always)
| Class:java.lang.ref.PhantomReference (always)
| Class:java.lang.ref.Finalizer (always)
| Class:java.lang.ref.FinalizerReference (always)
, watchDurationMs=5021
, gcDurationMs=143
, heapDumpDurationMs=2606
, computeRetainedHeapSize=false
, reachabilityInspectorClasses=[class com.squareup.leakcanary.AndroidReachabilityInspectors$ViewInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$ActivityInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$DialogInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$ApplicationInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$FragmentInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$SupportFragmentInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$MessageQueueInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$MortarPresenterInspector]}
其中excludedRefs表示需要忽略的引用,也可以通过{createBuilder(EnumSet refs)}来自定义需要忽略的引用。其中{AndroidExcludedRefs}中定义各种Android需要忽略的引用,感兴趣的可以仔细看看。
很奇怪,看不懂,没事后面我们来分析,这个对象是怎么得出内存泄漏的呢?并找出泄漏路径的呢?
分析
先看一下其时序图:
分析过程时序图
应该能看出来,主要是在{HeapAnalyzer}
HeapAnalyzerService
public final class HeapAnalyzerService extends ForegroundService
implements AnalyzerProgressListener {
...
@Override
protected void onHandleIntentInForeground(@Nullable Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
//1。构建HeapAnalyzer
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
//2.开始分析
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);
//3.发送分析结果,展示
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
...
}
{HeapAnalyzerService}主要试分析heapDumpFile文件是否存在泄漏。最重要的是第二步。
HeapAnalyzer
//HeapAnalyzer.java
/**
* Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
* and then computes the shortest strong reference path from that instance to the GC roots.
*/
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey,
boolean computeRetainedSize) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
Instance leakingRef = findLeakingReference(referenceKey, snapshot); //判断是否泄漏路径
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) { //没有泄漏
return noLeak(since(analysisStartNanoTime));
}
//找到泄漏路径
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
其中{deduplicateGcRoots}和{findLeakingReference}方法才是重点。关于这两个方法的分析详见下回分解!!!
AbstractAnalysisResultService
AbstractAnalysisResultService主要作用是拿到前面的分析结果进行展示,篇幅有限,不在赘述。
展示
//LeakCanaryInternals.java
public static final String SAMSUNG = "samsung";
public static final String MOTOROLA = "motorola";
public static final String LENOVO = "LENOVO";
public static final String LG = "LGE";
public static final String NVIDIA = "NVIDIA";
public static final String MEIZU = "Meizu";
public static final String HUAWEI = "HUAWEI";
public static final String VIVO = "vivo";
public static void showNotification(Context context, CharSequence contentTitle,
CharSequence contentText, PendingIntent pendingIntent, int notificationId) {
Notification.Builder builder = new Notification.Builder(context)
.setContentText(contentText)
.setContentTitle(contentTitle)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
Notification notification = buildNotification(context, builder);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(notificationId, notification); //发送通知
}
本文地址:https://blog.csdn.net/yuzhangzhen/article/details/108108453
推荐阅读
-
解析android 流量监测的实现原理
-
荐 【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析
-
Android中实现「类方法指令抽取方式」加固方案原理解析
-
Android 操作系统获取Root权限 原理详细解析
-
荐 【Android】LeakCanary原理解析
-
Android 进阶实现性能优化之OOM与Leakcanary详解原理
-
Android 友盟推送原理解析
-
Android消息机制原理,仿写Handler Looper源码解析跨线程通信原理--之仿写模拟Handler(四)
-
Android多线程(二)消息处理机制---Handler、Message、Looper源码原理解析
-
Android消息机制(Handler原理)-完全解析