Android主流三方库源码分析:Leakcanary,面试字节跳动的Android工程师该怎么准备

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(defa ultReachabilityInspectorClasses());

}

return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,

heapDumpBuilder);

}

复制代码

可以看到,RefWatcherBuilder包含了以下7个组成部分:

  • 1、excludedRefs : 记录可以被忽略的泄漏路径

  • 2、heapDumpListener : 转储堆信息到hprof文件,并在解析完 hprof 文件后进行回调,最后通知 DisplayLeakService 弹出泄漏提醒

  • 3、debuggerControl : 判断是否处于调试模式,调试模式中不会进行内存泄漏检测。为什么呢?因为在调试过程中可能会保留上一个引用从而导致错误信息上报

  • 4、heapDumper : 堆信息转储者,负责dump 内存泄漏处的 heap 信息到 hprof 文件

  • 5、watchExecutor : 线程控制器,在 onDestroy() 之后并且在主线程空闲时执行内存泄漏检测

  • 6、gcTrigger : 用于 GC,watchExecutor 首次检测到可能的内存泄漏,会主动进行 GC,GC 之后会再检测一次,仍然泄漏的判定为内存泄漏,最后根据heapDump信息生成相应的泄漏引用链

  • 7、reachabilityInspectorClasses : 用于要进行可达性检测的类列表。

最后,会使用建造者模式将这些组成部分构建成一个新的RefWatcher并将其返回。

我们继续看回到AndroidRefWatcherBuilder的注释3处的 LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true)这行代码。

9、LeakCanaryInternals#setEnabledAsync()


public static void setEnabledAsync(Context context, final Class<?> componentClass,

final boolean enabled) {

final Context appContext = context.getApplicationContext();

AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {

@Override public void run() {

setEnabledBlocking(appContext, componentClass, enabled);

}

});

}

复制代码

在这里直接使用了AsyncTask内部自带的THREAD_POOL_EXECUTOR线程池进行阻塞式地显示DisplayLeakActivity。

然后我们再继续看AndroidRefWatcherBuilder的注释4处的代码。

10、ActivityRefWatcher#install()


public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {

Application application = (Application) context.getApplicationContext();

// 1

ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

// 2

application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);

}

复制代码

可以看到,在注释1处创建一个自己的activityRefWatcher实例,并在注释2处调用了application的registerActivityLifecycleCallbacks()方法,这样就能够监听activity对应的生命周期事件了。继续看看activityRefWatcher.lifecycleCallbacks里面的操作。

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =

new ActivityLifecycleCallbacksAdapter() {

@Override public void onActivityDestroyed(Activity activity) {

refWatcher.watch(activity);

}

};

public abstract class ActivityLifecycleCallbacksAdapter

implements Application.ActivityLifecycleCallbacks {

}

复制代码

很明显,这里实现并重写了Application的ActivityLifecycleCallbacks的onActivityDestroyed()方法,这样便能在所有Activity执行完onDestroyed()方法之后调用 refWatcher.watch(activity)这行代码进行内存泄漏的检测了

我们再看到注释5处的FragmentRefWatcher.Helper.install(context, refWatcher)这行代码,

11、FragmentRefWatcher.Helper#install()


public interface FragmentRefWatcher {

void watchFragments(Activity activity);

final class Helper {

private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =

“com.squareup.leakcanary.internal.SupportFragmentRefWatcher”;

public static void install(Context context, RefWatcher refWatcher) {

List fragmentRefWatchers = new ArrayList<>();

// 1

if (SDK_INT >= O) {

fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));

}

// 2

try {

Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);

Constructor<?> constructor =

fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);

FragmentRefWatcher supportFragmentRefWatcher =

(FragmentRefWatcher) constructor.newInstance(refWatcher);

fragmentRefWatchers.add(supportFragmentRefWatcher);

} catch (Exception ignored) {

}

if (fragmentRefWatchers.size() == 0) {

return;

}

Helper helper = new Helper(fragmentRefWatchers);

// 3

Application application = (Application) context.getApplicationContext();

application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);

}

}

复制代码

这里面的逻辑很简单,首先在注释1处将Android标准的Fragment的RefWatcher类,即AndroidOfFragmentRefWatcher添加到新创建的fragmentRefWatchers中。在注释2处使用反射将leakcanary-support-fragment包下面的SupportFragmentRefWatcher添加进来,如果你在app的build.gradle下没有添加下面这行引用的话,则会拿不到此类,即LeakCanary只会检测Activity和标准Fragment这两种情况

debugImplementation ‘com.squareup.leakcanary:leakcanary-support-fragment:1.6.2’

复制代码

继续看到注释3处helper.activityLifecycleCallbacks里面的代码。

private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =

new ActivityLifecycleCallbacksAdapter() {

@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

for (FragmentRefWatcher watcher : fragmentRefWatchers) {

watcher.watchFragments(activity);

}

}

};

复制代码

可以看到,在Activity执行完onActivityCreated()方法之后,会调用指定watcher的watchFragments()方法,注意,这里的watcher可能有两种,但不管是哪一种,都会使用当前传入的activity获取到对应的FragmentManager/SupportFragmentManager对象,调用它的registerFragmentLifecycleCallbacks()方法,在对应的onDestroyView()和onDestoryed()方法执行完后,分别使用refWatcher.watch(view)和refWatcher.watch(fragment)进行内存泄漏的检测,代码如下所示。

@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {

View view = fragment.getView();

if (view != null) {

refWatcher.watch(view);

}

}

@Override

public void onFragmentDestroyed(FragmentManagerfm, Fragment fragment) {

refWatcher.watch(fragment);

}

复制代码

注意,下面到真正关键的地方了,接下来分析refWatcher.watch()这行代码。

12、RefWatcher#watch()

public void watch(Object watchedReference, String referenceName) {

if (this == DISABLED) {

return;

}

checkNotNull(watchedReference, “watchedReference”);

checkNotNull(referenceName, “referenceName”);

final long watchStartNanoTime = System.nanoTime();

// 1

String key = UUID.randomUUID().toString();

// 2

retainedKeys.add(key);

// 3

final KeyedWeakReference reference =

new KeyedWeakReference(watchedReference, key, referenceName, queue);

// 4

ensureGoneAsync(watchStartNanoTime, reference);

}

复制代码

注意到在注释1处使用随机的UUID保证了每个检测对象对应 key 的唯一性。在注释2处将生成的key添加到类型为CopyOnWriteArraySet的Set集合中。在注释3处新建了一个自定义的弱引用KeyedWeakReference,看看它内部的实现。

13、KeyedWeakReference


final class KeyedWeakReference extends WeakReference {

public final String key;

public final String name;

KeyedWeakReference(Object referent, String key, String name,

ReferenceQueue referenceQueue) {

// 1

super(checkNotNull(referent, “referent”), checkNotNull(referenceQueue, “referenceQueue”));

this.key = checkNotNull(key, “key”);

this.name = checkNotNull(name, “name”);

}

}

复制代码

可以看到,在KeyedWeakReference内部,使用了key和name标识了一个被检测的WeakReference对象。在注释1处,将弱引用和引用队列 ReferenceQueue 关联起来,如果弱引用reference持有的对象被GC回收,JVM就会把这个弱引用加入到与之关联的引用队列referenceQueue中。即 KeyedWeakReference 持有的 Activity 对象如果被GC回收,该对象就会加入到引用队列 referenceQueue 中

接着我们回到RefWatcher.watch()里注释4处的ensureGoneAsync()方法。

14、RefWatcher#ensureGoneAsync()


private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {

// 1

watchExecutor.execute(new Retryable() {

@Override public Retryable.Result run() {

// 2

return ensureGone(reference watchStartNanoTime);

}

});

}

复制代码

在ensureGoneAsync()方法中,在注释1处使用 watchExecutor 执行了注释2处的 ensureGone 方法,watchExecutor 是 AndroidWatchExecutor 的实例。

下面看看watchExecutor内部的逻辑。

15、AndroidWatchExecutor


public final class AndroidWatchExecutor implements WatchExecutor {

public AndroidWatchExecutor(long initialDelayMillis) {

mainHandler = new Handler(Looper.getMainLooper());

HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);

handlerThread.start();

// 1

backgroundHandler = new Handler(handlerThread.getLooper());

this.initialDelayMillis = initialDelayMillis;

maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;

}

@Override public void execute(@NonNull Retryable retryable) {

// 2

if (Looper.getMainLooper().getThread() == Thread.currentThread()) {

waitForIdle(retryable, 0);

} else {

postWaitForIdle(retryable, 0);

}

}

}

复制代码

在注释1处AndroidWatchExecutor的构造方法中,注意到这里使用HandlerThread的looper新建了一个backgroundHandler,后面会用到。在注释2处,会判断当前线程是否是主线程,如果是,则直接调用waitForIdle()方法,如果不是,则调用postWaitForIdle(),来看看这个方法。

private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {

mainHandler.post(new Runnable() {

@Override public void run() {

waitForIdle(retryable, failedAttempts);

}

});

}

复制代码

很清晰,这里使用了在构造方法中用主线程looper构造的mainHandler进行post,那么waitForIdle()最终也会在主线程执行。接着看看waitForIdle()的实现。

private void waitForIdle(final Retryable retryable, final int failedAttempts) {

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {

@Override public boolean queueIdle() {

postToBackgroundWithDelay(retryable, failedAttempts);

return false;

}

});

}

复制代码

这里MessageQueue.IdleHandler()回调方法的作用是当 looper 空闲的时候,会回调 queueIdle 方法,利用这个机制我们可以实现第三方库的延迟初始化,然后执行内部的postToBackgroundWithDelay()方法。接下来看看它的实现。

private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {

long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);

// 1

long delayMillis = initialDelayMillis * exponentialBackoffFactor;

// 2

backgroundHandler.postDelayed(new Runnable() {

@Override public void run() {

// 3

Retryable.Result result = retryable.run();

// 4

if (result == RETRY) {

postWaitForIdle(retryable, failedAttempts + 1);

}

}

}, delayMillis);

}

复制代码

先看到注释4处,可以明白,postToBackgroundWithDelay()是一个递归方法,如果result 一直等于RETRY的话,则会一直执行postWaitForIdle()方法。在回到注释1处,这里initialDelayMillis 的默认值是 5s,因此delayMillis就是5s。在注释2处,使用了在构造方法中用HandlerThread的looper新建的backgroundHandler进行异步延时执行retryable的run()方法。这个run()方法里执行的就是RefWatcher的ensureGoneAsync()方法中注释2处的ensureGone()这行代码,继续看它内部的逻辑。

16、RefWatcher#ensureGone()


Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {

long gcStartNanoTime = System.nanoTime();

long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

// 1

removeWeaklyReachableReferences();

// 2

if (debuggerControl.isDebuggerAttached()) {

// The debugger can create false leaks.

return RETRY;

}

// 3

if (gone(reference)) {

return DONE;

}

// 4

gcTrigger.runGc();

removeWeaklyReachableReferences();

// 5

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;

}

复制代码

在注释1处,执行了removeWeaklyReachableReferences()这个方法,接下来分析下它的含义。

private void removeWeaklyReachableReferences() {

KeyedWeakReference ref;

while ((ref = (KeyedWeakReference) queue.poll()) != null) {

retainedKeys.remove(ref.key);

}

}

复制代码

这里使用了while循环遍历 ReferenceQueue ,并从 retainedKeys中移除对应的Reference。

再看到注释2处,当Android设备处于debug状态时,会直接返回RETRY进行延时重试检测的操作。在注释3处,我们看看gone(reference)这个方法的逻辑。

private boolean gone(KeyedWeakReference reference) {

return !retainedKeys.contains(reference.key);

}

复制代码

这里会判断 retainedKeys 集合中是否还含有 reference,若没有,证明已经被回收了,若含有,可能已经发生内存泄露(或Gc还没有执行回收)。前面的分析中我们知道了 reference 被回收的时候,会被加进 referenceQueue 里面,然后我们会调用removeWeaklyReachableReferences()遍历 referenceQueue 移除掉 retainedKeys 里面的 refrence

接着我们看到注释4处,执行了gcTrigger的runGc()方法进行垃圾回收,然后使用了removeWeaklyReachableReferences()方法移除已经被回收的引用。这里我们再深入地分析下runGc()的实现。

GcTrigger DEFAULT = new GcTrigger() {

@Override public void runGc() {

// Code taken from AOSP FinalizationTest:

// https://android.googlesource.com/platform/libc ore/+/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 perform a gc.

Runtime.getRuntime().gc();

enqueueReferences();

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();

}

}

};

复制代码

这里并没有使用System.gc()方法进行回收,因为system.gc()并不会每次都执行。而是从AOSP中拷贝一段GC回收的代码,从而相比System.gc()更能够保证垃圾回收的工作

最后我们分析下注释5处的代码处理。首先会判断activity是否被回收,如果还没有被回收,则证明发生内存泄露,进行if判断里面的操作。在里面先调用堆信息转储者heapDumper的dumpHeap()生成相应的 hprof 文件。这里的heapDumper是一个HeapDumper接口,具体的实现是AndroidHeapDumper。我们分析下AndroidHeapDumper的dumpHeap()方法是如何生成hprof文件的。

public File dumpHeap() {

File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

if (heapDumpFile == RETRY_LATER) {

return RETRY_LATER;

}

try {

Debug.dumpHprofData(heapDumpFile.getAbsolutePath());

return heapDumpFile;

} catch (Exception e) {

// Abort heap dump

return RETRY_LATER;

}

}

复制代码

这里的核心操作就是调用了Android SDK的API Debug.dumpHprofData() 来生成 hprof 文件

如果这个文件等于RETRY_LATER则表示生成失败,直接返回RETRY进行延时重试检测的操作。如果不等于的话,则表示生成成功,最后会执行heapdumpListener的analyze()对新创建的HeapDump对象进行泄漏分析。由前面对AndroidRefWatcherBuilder的listenerServiceClass()的分析可知,heapdumpListener的实现 就是ServiceHeapDumpListener,接着看到ServiceHeapDumpListener的analyze方法。

17、ServiceHeapDumpListener#analyze()


@Override public void analyze(@NonNull HeapDump heapDump) {

checkNotNull(heapDump, “heapDump”);

HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);

}

复制代码

可以看到,这里执行了HeapAnalyzerService的runAnalysis()方法,为了避免降低app进程的性能或占用内存,这里将HeapAnalyzerService设置在了一个独立的进程中。接着继续分析runAnalysis()方法里面的处理。

public final class HeapAnalyzerService extends ForegroundService

implements AnalyzerProgressListener {

public static void runAnalysis(Context context, HeapDump heapDump,

Class<? extends AbstractAnalysisResultService> listenerServiceClass) {

ContextCompat.startForegroundService(context, intent);

}

@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {

// 1

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实质是一个类型为IntentService的ForegroundService,执行startForegroundService()之后,会回调onHandleIntentInForeground()方法。注释1处,首先会新建一个HeapAnalyzer对象,顾名思义,它就是根据RefWatcher生成的heap dumps信息来分析被怀疑的泄漏是否是真的。在注释2处,然后会调用它的checkForLeak()方法去使用haha库解析 hprof文件,如下所示:

public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,

@NonNull String referenceKey,

boolean computeRetainedSize) {

try {

listener.onProgressUpdate(READING_HEAP_DUMP_FILE);

// 1

HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);

// 2

HprofParser parser = new HprofParser(buffe

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

r);

listener.onProgressUpdate(PARSING_HEAP_DUMP);

Snapshot snapshot = parser.parse();

listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);

// 3

deduplicateGcRoots(snapshot);

listener.onProgressUpdate(FINDING_LEAKING_REF);

// 4

Instance leakingRef = findLeakingReference(referenceKey, snapshot);

// 5

if (leakingRef == null) {

return noLeak(since(analysisStartNanoTime));

}

// 6

return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);

} catch (Throwable e) {

return failure(e, since(analysisStartNanoTime));

}

}

复制代码

在注释1处,会新建一个内存映射缓存文件buffer。在注释2处,会使用buffer新建一个HprofParser解析器去解析出对应的引用内存快照文件snapshot。在注释3处,为了减少在Android 6.0版本中重复GCRoots带来的内存压力的影响,使用deduplicateGcRoots()删除了gcRoots中重复的根对象RootObj。在注释4处,调用了findLeakingReference()方法将传入的referenceKey和snapshot对象里面所有类实例的字段值对应的keyCandidate进行比较,如果没有相等的,则表示没有发生内存泄漏,直接调用注释5处的代码返回一个没有泄漏的分析结果AnalysisResult对象。如果找到了相等的,则表示发生了内存泄漏,执行注释6处的代码findLeakTrace()方法返回一个有泄漏分析结果的AnalysisResult对象。

最后,我们来分析下HeapAnalyzerService中注释3处的AbstractAnalysisResultService.sendResultToListener()方法,很明显,这里AbstractAnalysisResultService的实现类就是我们刚开始分析的用于展示泄漏路径信息的DisplayLeakService对象。在里面直接创建一个由PendingIntent构建的泄漏通知用于供用户点击去展示详细的泄漏界面DisplayLeakActivity。核心代码如下所示:

public class DisplayLeakService extends AbstractAnalysisResultService {

@Override

protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {

boolean resultSaved = false;

boolean shouldSaveResult = result.leakFound || result.failure != null;

if (shouldSaveResult) {

heapDump = renameHeapdump(heapDump);

// 1

resultSaved = saveResult(heapDump, result);

}

if (!shouldSaveResult) {

showNotification(null, contentTitle, contentText);

} else if (resultSaved) {

// 2

PendingIntent pendingIntent =

DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

showNotification(pendingIntent, contentTitle, contentText);

} else {

onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));

}

}

@Override protected final void onAnalysisResultFailure(String failureMessage) {

super.onAnalysisResultFailure(failureMessage);

String failureTitle = getString(R.string.leak_canary_result_failure_title);

showNotification(null, failureTitle, failureMessage);

}

复制代码

可以看到,只要当分析的堆信息文件保存成功之后,即在注释1处返回的resultSaved为true时,才会执行注释2处的逻辑,即创建一个供用户点击跳转到DisplayLeakActivity的延时通知。最后给出一张源码流程图用于回顾本篇文章中LeakCanary的运作流程:

Android主流三方库源码分析:Leakcanary,面试字节跳动的Android工程师该怎么准备

四、总结

上一篇:Android蓝牙通信


下一篇:语法基础:关键字final及static