[关闭]
@946898963 2021-02-23T14:25:10.000000Z 字数 23965 阅读 1540

LeakCanary源码整体分析

Android源码分析


全新 LeakCanary 2 ! 完全基于 Kotlin 重构升级 !

LeakCanary是由Square开源的针对Android和Java的内存泄漏检测工具。

使用

LeakCanary的集成过程很简单,首先在build.gradle文件中添加依赖:

  1. dependencies {
  2. debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
  3. releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
  4. }

debug和release版本中使用的是不同的库。LeakCanary运行时会经常执行GC操作,在release版本中会影响效率。android-no-op版本中基本没有逻辑实现,用于release版本。

然后实现自己的Application类:

  1. public class ExampleApplication extends Application {
  2. @Override public void onCreate() {
  3. super.onCreate();
  4. //判断是否在后台进程中
  5. if (LeakCanary.isInAnalyzerProcess(this)) {
  6. // This process is dedicated to LeakCanary for heap analysis.
  7. // You should not init your app in this process.
  8. return;
  9. }
  10. LeakCanary.install(this);
  11. // Normal app init code...
  12. }
  13. }

这样就集成完成了。当LeakCanary检测到内存泄露时,会自动弹出Notification通知开发者发生内存泄漏的Activity和引用链,以便进行修复。

源码分析

LeakCanary.isInAnalyzerProcess(this)

因为LeakCanary会开启一个进程Service用来分析每次产生的内存泄露,而安卓的应用每次开启进程都会调用Applicaiton的onCreate方法,因此我们有必要预先判断此次Application启动是不是在analyze service启动的。

LeakCanary.java

  1. public static boolean isInAnalyzerProcess(Context context) {
  2. return LeakCanaryInternals.isInServiceProcess(context, HeapAnalyzerService.class);
  3. }

LeakCanaryInternals.java

  1. public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
  2. PackageManager packageManager = context.getPackageManager();
  3. PackageInfo packageInfo;
  4. try {
  5. packageInfo = packageManager.getPackageInfo(context.getPackageName(), 4);
  6. } catch (Exception var14) {
  7. CanaryLog.d(var14, "Could not get package info for %s", new Object[]{context.getPackageName()});
  8. return false;
  9. }
  10. String mainProcess = packageInfo.applicationInfo.processName;
  11. ComponentName component = new ComponentName(context, serviceClass);
  12. ServiceInfo serviceInfo;
  13. try {
  14. serviceInfo = packageManager.getServiceInfo(component, 0);
  15. } catch (NameNotFoundException var13) {
  16. return false;
  17. }
  18. if(serviceInfo.processName.equals(mainProcess)) {
  19. CanaryLog.d("Did not expect service %s to run in main process %s", new Object[]{serviceClass, mainProcess});
  20. return false;
  21. } else {
  22. int myPid = Process.myPid();
  23. ActivityManager activityManager = (ActivityManager)context.getSystemService("activity");
  24. RunningAppProcessInfo myProcess = null;
  25. List runningProcesses = activityManager.getRunningAppProcesses();
  26. if(runningProcesses != null) {
  27. Iterator var11 = runningProcesses.iterator();
  28. while(var11.hasNext()) {
  29. RunningAppProcessInfo process = (RunningAppProcessInfo)var11.next();
  30. if(process.pid == myPid) {
  31. myProcess = process;
  32. break;
  33. }
  34. }
  35. }
  36. if(myProcess == null) {
  37. CanaryLog.d("Could not find running process for %d", new Object[]{Integer.valueOf(myPid)});
  38. return false;
  39. } else {
  40. return myProcess.processName.equals(serviceInfo.processName);
  41. }
  42. }
  43. }

判断Application是否是在service进程里面启动,最直接的方法就是判断当前进程名和Service所属的进程是否相同。当前进程名的获取方式是使用ActivityManager的getRunningAppProcessInfo方法,找到进程pid与当前进程pid相同的进程,然后从中拿到processName.service所属的进程名。获取service应处进程的方法是用PackageManager的getPackageInfo方法。

接下来分析入口函数LeakCanary.install(this):

LeakCanary.install

LeakCanary.java

  1. /**
  2. * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
  3. * references (on ICS+).
  4. */
  5. public static RefWatcher install(Application application) {
  6. return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
  7. .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
  8. .buildAndInstall();
  9. }

LeakCanary.refWatcher

LeakCanary.java

  1. /** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
  2. public static AndroidRefWatcherBuilder refWatcher(Context context) {
  3. return new AndroidRefWatcherBuilder(context);
  4. }

refWatcher()方法新建了一个AndroidRefWatcherBuilder 对象,该对象继承于RefWatcherBuilder类,配置了一些默认参数,利用建造者构建一个RefWatcher对象。

AndroidRefWatcherBuilder.listenerServiceClass

AndroidRefWatcherBuilder.java

  1. public AndroidRefWatcherBuilder listenerServiceClass(
  2. Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  3. return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  4. }

RefWatcherBuilder.java

  1. /** @see HeapDump.Listener */
  2. public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
  3. this.heapDumpListener = heapDumpListener;
  4. return self();
  5. }

DisplayLeakService.java

  1. /**
  2. * Logs leak analysis results, and then shows a notification which will start {@link
  3. * DisplayLeakActivity}.
  4. *
  5. * You can extend this class and override {@link #afterDefaultHandling(HeapDump, AnalysisResult,
  6. * String)} to add custom behavior, e.g. uploading the heap dump.
  7. */
  8. public class DisplayLeakService extends AbstractAnalysisResultService {}

listenerServiceClass()方法绑定了一个后台服务DisplayLeakService,这个服务主要用来分析内存泄漏结果并发送通知。你可以继承并重写这个类来进行一些自定义操作,比如上传分析结果等。

RefWatcherBuilder.excludedRefs

RefWatcherBuilder.java

  1. public final T excludedRefs(ExcludedRefs excludedRefs) {
  2. this.excludedRefs = excludedRefs;
  3. return self();
  4. }

AndroidExcludedRefs.java

  1. /**
  2. * This returns the references in the leak path that can be ignored for app developers. This
  3. * doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs
  4. * in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app
  5. * developers except by resorting to serious hacks, so we remove the noise caused by those leaks.
  6. */
  7. public static ExcludedRefs.Builder createAppDefaults() {
  8. return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
  9. }
  10. public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
  11. ExcludedRefs.Builder excluded = ExcludedRefs.builder();
  12. for (AndroidExcludedRefs ref : refs) {
  13. if (ref.applies) {
  14. ref.add(excluded);
  15. ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
  16. }
  17. }
  18. return excluded;
  19. }

excludedRefs()方法定义了一些对于开发者可以忽略的路径,意思就是即使这里发生了内存泄漏,LeakCanary也不会弹出通知。这大多是系统Bug导致的,无需用户进行处理。

AndroidRefWatcherBuilder.buildAndInstall

最后调用buildAndInstall()方法构建RefWatcher实例并开始监听Activity的引用:

AndroidRefWatcherBuilder.java

  1. /**
  2. * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
  3. */
  4. public RefWatcher buildAndInstall() {
  5. RefWatcher refWatcher = build();
  6. if (refWatcher != DISABLED) {
  7. LeakCanary.enableDisplayLeakActivity(context);
  8. ActivityRefWatcher.install((Application) context, refWatcher);
  9. }
  10. return refWatcher;
  11. }

看一下主要的build()和install()方法:

RefWatcherBuilder.build

RefWatcherBuilder.java

  1. /** Creates a {@link RefWatcher}. */
  2. public final RefWatcher build() {
  3. if (isDisabled()) {
  4. return RefWatcher.DISABLED;
  5. }
  6. ExcludedRefs excludedRefs = this.excludedRefs;
  7. if (excludedRefs == null) {
  8. excludedRefs = defaultExcludedRefs();
  9. }
  10. HeapDump.Listener heapDumpListener = this.heapDumpListener;
  11. if (heapDumpListener == null) {
  12. heapDumpListener = defaultHeapDumpListener();
  13. }
  14. DebuggerControl debuggerControl = this.debuggerControl;
  15. if (debuggerControl == null) {
  16. debuggerControl = defaultDebuggerControl();
  17. }
  18. HeapDumper heapDumper = this.heapDumper;
  19. if (heapDumper == null) {
  20. heapDumper = defaultHeapDumper();
  21. }
  22. WatchExecutor watchExecutor = this.watchExecutor;
  23. if (watchExecutor == null) {
  24. watchExecutor = defaultWatchExecutor();
  25. }
  26. GcTrigger gcTrigger = this.gcTrigger;
  27. if (gcTrigger == null) {
  28. gcTrigger = defaultGcTrigger();
  29. }
  30. return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
  31. excludedRefs);
  32. }

build()方法利用建造者模式构建 RefWatcher实例,看一下其中的主要参数:

LeakCanary.enableDisplayLeakActivity

接下来就是最核心的install()方法,这里就开始观察Activity的引用了。在这之前还执行了一步操作,LeakCanary.enableDisplayLeakActivity(context); :

  1. public static void enableDisplayLeakActivity(Context context) {
  2. setEnabled(context, DisplayLeakActivity.class, true);
  3. }

最后执行到 LeakCanaryInternals#setEnabledBlocking :

  1. public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
  2. boolean enabled) {
  3. ComponentName component = new ComponentName(appContext, componentClass);
  4. PackageManager packageManager = appContext.getPackageManager();
  5. int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
  6. // Blocks on IPC.
  7. packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
  8. }

这里启用了DisplayLeakActivity并且显示应用图标。注意,这是指的不是你自己的应用图标,是一个单独的LeakCanary的应用,用于展示内存泄露历史的,入口函数是 DisplayLeakActivity,在 AndroidManifest.xml中可以看到默认情况下android:enabled="false" :

  1. <activity
  2. android:theme="@style/leak_canary_LeakCanary.Base"
  3. android:name=".internal.DisplayLeakActivity"
  4. android:process=":leakcanary"
  5. android:enabled="false"
  6. android:label="@string/leak_canary_display_activity_label"
  7. android:icon="@mipmap/leak_canary_icon"
  8. android:taskAffinity="com.squareup.leakcanary.${applicationId}"
  9. >
  10. <intent-filter>
  11. <action android:name="android.intent.action.MAIN"/>
  12. <category android:name="android.intent.category.LAUNCHER"/>
  13. </intent-filter>
  14. </activity>

ActivityRefWatcher.install

ActivityRefWatcher.java

  1. public static void install(Application application, RefWatcher refWatcher) {
  2. new ActivityRefWatcher(application, refWatcher).watchActivities();
  3. }
  4. public void watchActivities() {
  5. // Make sure you don't get installed twice.
  6. stopWatchingActivities();
  7. application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  8. }
  9. public void stopWatchingActivities() {
  10. application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
  11. }

watchActivities()方法中先解绑生命周期回调注册lifecycleCallbacks,再重新绑定,避免重复绑定。lifecycleCallbacks监听了Activity的各个生命周期,在onDestroy()中开始检测当前Activity 的引用。

  1. private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
  2. new Application.ActivityLifecycleCallbacks() {
  3. @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
  4. }
  5. @Override public void onActivityStarted(Activity activity) {
  6. }
  7. @Override public void onActivityResumed(Activity activity) {
  8. }
  9. @Override public void onActivityPaused(Activity activity) {
  10. }
  11. @Override public void onActivityStopped(Activity activity) {
  12. }
  13. @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
  14. }
  15. @Override public void onActivityDestroyed(Activity activity) {
  16. ActivityRefWatcher.this.onActivityDestroyed(activity);
  17. }
  18. };
  19. void onActivityDestroyed(Activity activity) {
  20. refWatcher.watch(activity);
  21. }

下面着重分析RefWatcher是如何检测Activity的。

RefWatcher.watch

调用RefWatcher#watch检测Activity。

RefWatcher.java

  1. /**
  2. * Identical to {@link #watch(Object, String)} with an empty string reference name.
  3. *
  4. * @see #watch(Object, String)
  5. */
  6. public void watch(Object watchedReference) {
  7. watch(watchedReference, "");
  8. }
  9. /**
  10. * Watches the provided references and checks if it can be GCed. This method is non blocking,
  11. * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
  12. * with.
  13. *
  14. * @param referenceName An logical identifier for the watched object.
  15. */
  16. public void watch(Object watchedReference, String referenceName) {
  17. if (this == DISABLED) {
  18. return;
  19. }
  20. checkNotNull(watchedReference, "watchedReference");
  21. checkNotNull(referenceName, "referenceName");
  22. final long watchStartNanoTime = System.nanoTime();
  23. String key = UUID.randomUUID().toString();
  24. retainedKeys.add(key);
  25. final KeyedWeakReference reference =
  26. new KeyedWeakReference(watchedReference, key, referenceName, queue);
  27. ensureGoneAsync(watchStartNanoTime, reference);
  28. }

watch()方法的参数是Object ,LeakCanary并不仅仅是针对Android 的,它可以检测任何对象的内存泄漏,原理都是一致的。

这里出现了几个新面孔,先来了解一下各自是什么:

  1. final class KeyedWeakReference extends WeakReference<Object> {
  2. public final String key;
  3. public final String name;
  4. KeyedWeakReference(Object referent, String key, String name,
  5. ReferenceQueue<Object> referenceQueue) {
  6. super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
  7. this.key = checkNotNull(key, "key");
  8. this.name = checkNotNull(name, "name");
  9. }
  10. }

这里有个小知识点,弱引用和引用队列ReferenceQueue联合使用时,如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。即KeyedWeakReference持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue中。

接着看看具体的内存泄漏判断过程:

RefWatcher.ensureGoneAsync

  1. private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  2. watchExecutor.execute(new Retryable() {
  3. @Override public Retryable.Result run() {
  4. return ensureGone(reference, watchStartNanoTime);
  5. }
  6. });
  7. }

通过watchExecutor执行检测操作,这里的watchExecutor是 AndroidWatchExecutor对象。

  1. @Override protected WatchExecutor defaultWatchExecutor() {
  2. return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
  3. }

DEFAULT_WATCH_DELAY_MILLIS 为 5 s。

  1. public AndroidWatchExecutor(long initialDelayMillis) {
  2. mainHandler = new Handler(Looper.getMainLooper());
  3. HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
  4. handlerThread.start();
  5. backgroundHandler = new Handler(handlerThread.getLooper());
  6. this.initialDelayMillis = initialDelayMillis;
  7. maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
  8. }

看看其中用到的几个对象:

  1. @Override public void execute(Retryable retryable) {
  2. if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
  3. waitForIdle(retryable, 0);
  4. } else {
  5. postWaitForIdle(retryable, 0);
  6. }
  7. }
  8. void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
  9. mainHandler.post(new Runnable() {
  10. @Override public void run() {
  11. waitForIdle(retryable, failedAttempts);
  12. }
  13. });
  14. }
  15. void waitForIdle(final Retryable retryable, final int failedAttempts) {
  16. // This needs to be called from the main thread.
  17. Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
  18. @Override public boolean queueIdle() {
  19. postToBackgroundWithDelay(retryable, failedAttempts);
  20. return false;
  21. }
  22. });
  23. }

在具体的execute()过程中,不管是waitForIdle还是postWaitForIdle,最终还是要切换到主线程中执行。要注意的是,这里的IdleHandler到底是什么时候去执行?

我们都知道Handler是循环处理MessageQueue中的消息的,当消息队列中没有更多消息需要处理的时候,且声明了IdleHandler接口,这是就会去处理这里的操作。即指定一些操作,当线程空闲的时候来处理。当主线程空闲时,就会通知后台线程延时5秒执行内存泄漏检测工作。

  1. void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
  2. long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
  3. long delayMillis = initialDelayMillis * exponentialBackoffFactor;
  4. backgroundHandler.postDelayed(new Runnable() {
  5. @Override public void run() {
  6. Retryable.Result result = retryable.run();
  7. if (result == RETRY) {
  8. postWaitForIdle(retryable, failedAttempts + 1);
  9. }
  10. }
  11. }, delayMillis);
  12. }

下面是真正的检测过程,AndroidWatchExecutor在执行时调用ensureGone()方法:

RefWatcher.ensureGone

  1. Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
  2. long gcStartNanoTime = System.nanoTime();
  3. long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
  4. removeWeaklyReachableReferences();
  5. if (debuggerControl.isDebuggerAttached()) {
  6. // The debugger can create false leaks.
  7. return RETRY;
  8. }
  9. if (gone(reference)) {
  10. return DONE;
  11. }
  12. gcTrigger.runGc();
  13. removeWeaklyReachableReferences();
  14. if (!gone(reference)) {
  15. long startDumpHeap = System.nanoTime();
  16. long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
  17. File heapDumpFile = heapDumper.dumpHeap();
  18. if (heapDumpFile == RETRY_LATER) {
  19. // Could not dump the heap.
  20. return RETRY;
  21. }
  22. long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
  23. heapdumpListener.analyze(
  24. new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
  25. gcDurationMs, heapDumpDurationMs));
  26. }
  27. return DONE;
  28. }

再重复一次几个变量的含义,retainedKeys是一个Set集合,存储检测对象对应的唯一key值,queue是一个引用队列,存储被垃圾回收的对象。

主要过程有一下几步:

RefWatcher.emoveWeaklyReachableReferences()

  1. private void removeWeaklyReachableReferences() {
  2. // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
  3. // reachable. This is before finalization or garbage collection has actually happened.
  4. KeyedWeakReference ref;
  5. while ((ref = (KeyedWeakReference) queue.poll()) != null) {
  6. retainedKeys.remove(ref.key);
  7. }
  8. }

遍历引用队列queue,判断队列中是否存在当前Activity的弱引用,存在则删除retainedKeys中对应的引用的key值。

RefWatcher.gone()

  1. private boolean gone(KeyedWeakReference reference) {
  2. return !retainedKeys.contains(reference.key);
  3. }

判断retainedKeys中是否包含当前Activity引用的key值。

如果不包含,说明上一步操作中retainedKeys移除了该引用的key值,也就说上一步操作之前引用队列queue中包含该引用,GC处理了该引用,未发生内存泄漏,返回DONE,不再往下执行。

如果包含,并不会立即判定发生内存泄漏,可能存在某个对象已经不可达,但是尚未进入引用队列 queue。这时会主动执行一次GC操作之后再次进行判断。

gcTrigger.runGc()

  1. /**
  2. * Called when a watched reference is expected to be weakly reachable, but hasn't been enqueued
  3. * in the reference queue yet. This gives the application a hook to run the GC before the {@link
  4. * RefWatcher} checks the reference queue again, to avoid taking a heap dump if possible.
  5. */
  6. public interface GcTrigger {
  7. GcTrigger DEFAULT = new GcTrigger() {
  8. @Override public void runGc() {
  9. // Code taken from AOSP FinalizationTest:
  10. // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
  11. // java/lang/ref/FinalizationTester.java
  12. // System.gc() does not garbage collect every time. Runtime.gc() is
  13. // more likely to perfom a gc.
  14. Runtime.getRuntime().gc();
  15. enqueueReferences();
  16. System.runFinalization();
  17. }
  18. private void enqueueReferences() {
  19. // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
  20. // references to the appropriate queues.
  21. try {
  22. Thread.sleep(100);
  23. } catch (InterruptedException e) {
  24. throw new AssertionError();
  25. }
  26. }
  27. };
  28. void runGc();
  29. }

注意这里调用GC的写法,并不是使用System.gc。System.gc仅仅只是通知系统在合适的时间进行一次垃圾回收操作,实际上并不能保证一定执行。

主动进行GC之后会再次进行判定,过程同上。首先调用removeWeaklyReachableReferences()清除retainedKeys中弱引用的key值,再判断是否移除。如果仍然没有移除,判定为内存泄漏。

内存泄露结果处理

AndroidHeapDumper.dumpHeap

判定内存泄漏之后,调用heapDumper.dumpHeap()进行处理:

AndroidHeapDumper.java

  1. @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
  2. @Override public File dumpHeap() {
  3. File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
  4. if (heapDumpFile == RETRY_LATER) {
  5. return RETRY_LATER;
  6. }
  7. FutureResult<Toast> waitingForToast = new FutureResult<>();
  8. showToast(waitingForToast);
  9. if (!waitingForToast.wait(5, SECONDS)) {
  10. CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
  11. return RETRY_LATER;
  12. }
  13. Toast toast = waitingForToast.get();
  14. try {
  15. Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
  16. cancelToast(toast);
  17. return heapDumpFile;
  18. } catch (Exception e) {
  19. CanaryLog.d(e, "Could not dump heap");
  20. // Abort heap dump
  21. return RETRY_LATER;
  22. }
  23. }

leakDirectoryProvider.newHeapDumpFile()新建了hprof文件,然后调用Debug.dumpHprofData() 方法dump当前堆内存并写入刚才创建的文件。

回到RefWatcher.ensureGone()方法中,生成heapDumpFile文件之后,通过heapdumpListener分析。

ServiceHeapDumpListener.analyze

  1. heapdumpListener.analyze(
  2. new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
  3. gcDurationMs, heapDumpDurationMs));

这里的heapdumpListener是ServiceHeapDumpListener对象,接着进入ServiceHeapDumpListener.runAnalysis()方法。

  1. @Override public void analyze(HeapDump heapDump) {
  2. checkNotNull(heapDump, "heapDump");
  3. HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  4. }

HeapAnalyzerService.runAnalysis

HeapAnalyzerService.runAnalysis()方法中启动了它自己,传递了两个参数,DisplayLeakService 类名和要分析的 heapDump。启动自己后,在 onHandleIntent 中进行处理。

  1. /**
  2. * This service runs in a separate process to avoid slowing down the app process or making it run
  3. * out of memory.
  4. */
  5. public final class HeapAnalyzerService extends IntentService {
  6. private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
  7. private static final String HEAPDUMP_EXTRA = "heapdump_extra";
  8. public static void runAnalysis(Context context, HeapDump heapDump,
  9. Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  10. Intent intent = new Intent(context, HeapAnalyzerService.class);
  11. intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
  12. intent.putExtra(HEAPDUMP_EXTRA, heapDump);
  13. context.startService(intent);
  14. }
  15. public HeapAnalyzerService() {
  16. super(HeapAnalyzerService.class.getSimpleName());
  17. }
  18. @Override protected void onHandleIntent(Intent intent) {
  19. if (intent == null) {
  20. CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
  21. return;
  22. }
  23. String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
  24. HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
  25. HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
  26. AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
  27. AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  28. }
  29. }

heapAnalyzer.checkForLeak

checkForLeak方法中主要使用了Square公司的另一个库haha来分析 Android heap dump,得到结果后回调给 DisplayLeakService。

AbstractAnalysisResultService.sendResultToListener

  1. public static void sendResultToListener(Context context, String listenerServiceClassName,
  2. HeapDump heapDump, AnalysisResult result) {
  3. Class<?> listenerServiceClass;
  4. try {
  5. listenerServiceClass = Class.forName(listenerServiceClassName);
  6. } catch (ClassNotFoundException e) {
  7. throw new RuntimeException(e);
  8. }
  9. Intent intent = new Intent(context, listenerServiceClass);
  10. intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
  11. intent.putExtra(RESULT_EXTRA, result);
  12. context.startService(intent);
  13. }

同样在 onHandleIntent 中进行处理。

DisplayLeakService.onHandleIntent

  1. @Override protected final void onHandleIntent(Intent intent) {
  2. HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
  3. AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
  4. try {
  5. onHeapAnalyzed(heapDump, result);
  6. } finally {
  7. //noinspection ResultOfMethodCallIgnored
  8. heapDump.heapDumpFile.delete();
  9. }
  10. }

DisplayLeakService.onHeapAnalyzed

调用onHeapAnalyzed()之后,会将hprof文件删除。

DisplayLeakService.java

  1. @Override
  2. protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
  3. String leakInfo = leakInfo(this, heapDump, result, true);
  4. CanaryLog.d("%s", leakInfo);
  5. boolean resultSaved = false;
  6. boolean shouldSaveResult = result.leakFound || result.failure != null;
  7. if (shouldSaveResult) {
  8. heapDump = renameHeapdump(heapDump);
  9. resultSaved = saveResult(heapDump, result);
  10. }
  11. PendingIntent pendingIntent;
  12. String contentTitle;
  13. String contentText;
  14. if (!shouldSaveResult) {
  15. contentTitle = getString(R.string.leak_canary_no_leak_title);
  16. contentText = getString(R.string.leak_canary_no_leak_text);
  17. pendingIntent = null;
  18. } else if (resultSaved) {
  19. pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
  20. if (result.failure == null) {
  21. String size = formatShortFileSize(this, result.retainedHeapSize);
  22. String className = classSimpleName(result.className);
  23. if (result.excludedLeak) {
  24. contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
  25. } else {
  26. contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
  27. }
  28. } else {
  29. contentTitle = getString(R.string.leak_canary_analysis_failed);
  30. }
  31. contentText = getString(R.string.leak_canary_notification_message);
  32. } else {
  33. contentTitle = getString(R.string.leak_canary_could_not_save_title);
  34. contentText = getString(R.string.leak_canary_could_not_save_text);
  35. pendingIntent = null;
  36. }
  37. // New notification id every second.
  38. int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
  39. showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
  40. afterDefaultHandling(heapDump, result, leakInfo);
  41. }

根据分析结果,调用showNotification()方法构建了一个Notification向开发者通知内存泄漏。

  1. public static void showNotification(Context context, CharSequence contentTitle,
  2. CharSequence contentText, PendingIntent pendingIntent, int notificationId) {
  3. NotificationManager notificationManager =
  4. (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
  5. Notification notification;
  6. Notification.Builder builder = new Notification.Builder(context) //
  7. .setSmallIcon(R.drawable.leak_canary_notification)
  8. .setWhen(System.currentTimeMillis())
  9. .setContentTitle(contentTitle)
  10. .setContentText(contentText)
  11. .setAutoCancel(true)
  12. .setContentIntent(pendingIntent);
  13. if (SDK_INT >= O) {
  14. String channelName = context.getString(R.string.leak_canary_notification_channel);
  15. setupNotificationChannel(channelName, notificationManager, builder);
  16. }
  17. if (SDK_INT < JELLY_BEAN) {
  18. notification = builder.getNotification();
  19. } else {
  20. notification = builder.build();
  21. }
  22. notificationManager.notify(notificationId, notification);
  23. }

DisplayLeakService.afterDefaultHandling

最后还会执行一个空实现的方法 afterDefaultHandling:

  1. /**
  2. * You can override this method and do a blocking call to a server to upload the leak trace and
  3. * the heap dump. Don't forget to check {@link AnalysisResult#leakFound} and {@link
  4. * AnalysisResult#excludedLeak} first.
  5. */
  6. protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
  7. }

你可以重写这个方法进行一些自定义的操作,比如向服务器上传泄漏的堆栈信息等。

这样,LeakCanary 就完成了整个内存泄漏检测的过程。可以看到,LeakCanary 的设计思路十分巧妙,同时也很清晰,有很多有意思的知识点,像对于弱引用和 ReferenceQueue 的使用, IdleHandler 的使用,四大组件的开启和关闭等等,都很值的大家去深究。

相关知识

IdleHandler

你知道android的MessageQueue.IdleHandler吗?

android addIdleHandler 空闲线程 解析

监控应用中Activity的生命周期

Android开发 - ActivityLifecycleCallbacks使用方法初探

判断应用是否处于调试模式

判断apk是否运行在调试状态的方法Debug.isDebuggerConnected之原理

让系统进行GC

java System.gc()与Runtime.getRuntime().gc()有什么区别?

system.gc()和system.runFinalization()区别作用

System.gc()和Runtime.getRuntime().gc()的作用是相同的,只是建议系统进行垃圾回收,但系统进步进行垃圾回收是不确定的。

System.gc(); 告诉垃圾收集器打算进行垃圾收集,而垃圾收集器进不进行收集是不确定的
System.runFinalization(); 强制调用已经失去引用的对象的finalize方法

Runtime.getRuntime().gc()和Runtime.getRuntime().runFinalization()的区别和上面的区别是一样的。

生成内存快照文件

[Android]生成heap dump文件(.hprof)

[Android] 内存泄漏调试经验分享 (二)

Android内存泄露利器(hprof篇)

android.os.Debug.dumpHprofData(hprofPath) 生成当前的内存快照文件

控制应用图标的显示

Android - 安装应用(APP) 不显示图标

安装了应用后,隐藏和显示应用的launcher显示

  1. ComponentName component = new ComponentName(appContext, componentClass);
  2. PackageManager packageManager = appContext.getPackageManager();
  3. int newState = enabled?1:2;
  4. packageManager.setComponentEnabledSetting(component, newState, 1);

判断进程是否是前台进程

android 判断进程是否处于前台

主要参考链接:

LeakCanary 源码解析

LeakCanary

LeakCanary 内存泄露监测原理研究

深入理解 Android 之 LeakCanary 源码解析

LeakCanary源码分析

LeakCanary源码分析第三讲-HeapAnalyzerService详解

带你学开源项目:LeakCanary- 如何检测 Activity 是否泄漏

LeakCanary源码分析

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注