LeakCanary 源码解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: LeakCanary 是由 Square 开源的针对 Android 和 Java 的内存泄漏检测工具。

使用


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

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}
复制代码


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


然后实现自己的 Application 类:

public class ExampleApplication extends Application {
  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}
复制代码


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


源码分析


从入口函数 LeakCanary.install(this) 开始分析:


LeakCanary.install

LeakCanary.java

/**
 * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
 * references (on ICS+).
 */
public static RefWatcher install(Application application) {
  return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}
复制代码


LeakCanary.refWatcher

LeakCanary.java

/** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
public static AndroidRefWatcherBuilder refWatcher(Context context) {
  return new AndroidRefWatcherBuilder(context);
}
复制代码


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


AndroidRefWatcherBuilder.listenerServiceClass

AndroidRefWatcherBuilder.java

public AndroidRefWatcherBuilder listenerServiceClass(
    Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
复制代码


RefWatcherBuilder.java

/** @see HeapDump.Listener */
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
  this.heapDumpListener = heapDumpListener;
  return self();
}
复制代码


DisplayLeakService.java

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


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


RefWatcherBuilder.excludedRefs

RefWatcherBuilder.java

public final T excludedRefs(ExcludedRefs excludedRefs) {
  this.excludedRefs = excludedRefs;
  return self();
}
复制代码


AndroidExcludedRefs.java

/**
 * This returns the references in the leak path that can be ignored for app developers. This
 * doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs
 * in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app
 * developers except by resorting to serious hacks, so we remove the noise caused by those leaks.
 */
public static ExcludedRefs.Builder createAppDefaults() {
  return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
}
public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
  ExcludedRefs.Builder excluded = ExcludedRefs.builder();
  for (AndroidExcludedRefs ref : refs) {
    if (ref.applies) {
      ref.add(excluded);
      ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
    }
  }
  return excluded;
}
复制代码


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


AndroidRefWatcherBuilder.buildAndInstall

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

AndroidRefWatcherBuilder.java

/**
 * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
 */
public RefWatcher buildAndInstall() {
  RefWatcher refWatcher = build();
  if (refWatcher != DISABLED) {
    LeakCanary.enableDisplayLeakActivity(context);
    ActivityRefWatcher.install((Application) context, refWatcher);
  }
  return refWatcher;
}
复制代码


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

RefWatcherBuilder.build

RefWatcherBuilder.java

/** Creates a {@link RefWatcher}. */
 public final RefWatcher build() {
   if (isDisabled()) {
     return RefWatcher.DISABLED;
   }
   ExcludedRefs excludedRefs = this.excludedRefs;
   if (excludedRefs == null) {
     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();
   }
   return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
           excludedRefs);
 }
复制代码


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

  • watchExecutor : 线程控制器,在 onDestroy() 之后并且主线程空闲时执行内存泄漏检测
  • debuggerControl : 判断是否处于调试模式,调试模式中不会进行内存泄漏检测
  • gcTrigger : 用于 GCwatchExecutor 首次检测到可能的内存泄漏,会主动进行 GCGC 之后会再检测一次,仍然泄漏的判定为内存泄漏,进行后续操作
  • heapDumper : dump 内存泄漏处的 heap 信息,写入 hprof 文件
  • heapDumpListener : 解析完 hprof 文件并通知 DisplayLeakService 弹出提醒
  • excludedRefs : 排除可以忽略的泄漏路径


LeakCanary.enableDisplayLeakActivity

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

public static void enableDisplayLeakActivity(Context context) {
  setEnabled(context, DisplayLeakActivity.class, true);
}
复制代码


最后执行到 LeakCanaryInternals#setEnabledBlocking

public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
    boolean enabled) {
  ComponentName component = new ComponentName(appContext, componentClass);
  PackageManager packageManager = appContext.getPackageManager();
  int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
  // Blocks on IPC.
  packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
复制代码


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

<activity
    android:theme="@style/leak_canary_LeakCanary.Base"
    android:name=".internal.DisplayLeakActivity"
    android:process=":leakcanary"
    android:enabled="false"
    android:label="@string/leak_canary_display_activity_label"
    android:icon="@mipmap/leak_canary_icon"
    android:taskAffinity="com.squareup.leakcanary.${applicationId}"
    >
  <intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
  </intent-filter>
</activity>
复制代码


ActivityRefWatcher.install

ActivityRefWatcher.java

public static void install(Application application, RefWatcher refWatcher) {
  new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities() {
  // Make sure you don't get installed twice.
  stopWatchingActivities();
  application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
复制代码


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


private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
    new Application.ActivityLifecycleCallbacks() {
      @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
      }
      @Override public void onActivityStarted(Activity activity) {
      }
      @Override public void onActivityResumed(Activity activity) {
      }
      @Override public void onActivityPaused(Activity activity) {
      }
      @Override public void onActivityStopped(Activity activity) {
      }
      @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
      }
      @Override public void onActivityDestroyed(Activity activity) {
        ActivityRefWatcher.this.onActivityDestroyed(activity);
      }
    };
    void onActivityDestroyed(Activity activity) {
      refWatcher.watch(activity);
    }
复制代码

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


RefWatcher.watch

调用 RefWatcher#watch 检测 ActivityRefWatcher.java

/**
 * 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) {
    return;
  }
  checkNotNull(watchedReference, "watchedReference");
  checkNotNull(referenceName, "referenceName");
  final long watchStartNanoTime = System.nanoTime();
  String key = UUID.randomUUID().toString();
  retainedKeys.add(key);
  final KeyedWeakReference reference =
      new KeyedWeakReference(watchedReference, key, referenceName, queue);
  ensureGoneAsync(watchStartNanoTime, reference);
}
复制代码


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


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

  • retainedKeys : 一个 Set<String> 集合,每个检测的对象都对应着一个唯一的 key,存储在 retainedKeys
  • KeyedWeakReference :  自定义的弱引用,持有检测对象和对用的 key


final class KeyedWeakReference extends WeakReference<Object> {
  public final String key;
  public final String name;
  KeyedWeakReference(Object referent, String key, String name,
      ReferenceQueue<Object> referenceQueue) {
    super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
    this.key = checkNotNull(key, "key");
    this.name = checkNotNull(name, "name");
  }
}
复制代码


  • queue : ReferenceQueue 对象,和 KeyedWeakReference 配合使用

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

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

RefWatcher.ensureGoneAsync

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}
复制代码


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

@Override protected WatchExecutor defaultWatchExecutor() {
  return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
复制代码


DEFAULT_WATCH_DELAY_MILLIS 为 5 s。

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;
}
复制代码


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

  • mainHandler : 主线程消息队列
  • handlerThread : 后台线程,HandlerThread 对象,线程名为 LeakCanary-Heap-Dump
  • backgroundHandler : 上面的后台线程的消息队列
  • initialDelayMillis : 5 s,即之前的 DEFAULT_WATCH_DELAY_MILLIS


@Override public void execute(Retryable retryable) {
  if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
    waitForIdle(retryable, 0);
  } else {
    postWaitForIdle(retryable, 0);
  }
}
void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
  mainHandler.post(new Runnable() {
    @Override public void run() {
      waitForIdle(retryable, failedAttempts);
    }
  });
}
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;
    }
  });
}
复制代码


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


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


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);
}
复制代码

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


RefWatcher.ensureGone

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
  long gcStartNanoTime = System.nanoTime();
  long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
  removeWeaklyReachableReferences();
  if (debuggerControl.isDebuggerAttached()) {
    // The debugger can create false leaks.
    return RETRY;
  }
  if (gone(reference)) {
    return DONE;
  }
  gcTrigger.runGc();
  removeWeaklyReachableReferences();
  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);
    heapdumpListener.analyze(
        new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
            gcDurationMs, heapDumpDurationMs));
  }
  return DONE;
}
复制代码


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

主要过程有一下几步:

RefWatcher.emoveWeaklyReachableReferences()


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);
  }
}
复制代码


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


RefWatcher.gone()

private boolean gone(KeyedWeakReference reference) {
  return !retainedKeys.contains(reference.key);
}
复制代码


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

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


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


gcTrigger.runGc()

/**
 * Called when a watched reference is expected to be weakly reachable, but hasn't been enqueued
 * in the reference queue yet. This gives the application a hook to run the GC before the {@link
 * RefWatcher} checks the reference queue again, to avoid taking a heap dump if possible.
 */
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();
      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();
      }
    }
  };
  void runGc();
}
复制代码


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

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


内存泄露结果处理

AndroidHeapDumper.dumpHeap

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

AndroidHeapDumper.java


@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
@Override public File dumpHeap() {
  File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
  if (heapDumpFile == RETRY_LATER) {
    return RETRY_LATER;
  }
  FutureResult<Toast> waitingForToast = new FutureResult<>();
  showToast(waitingForToast);
  if (!waitingForToast.wait(5, SECONDS)) {
    CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
    return RETRY_LATER;
  }
  Toast toast = waitingForToast.get();
  try {
    Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
    cancelToast(toast);
    return heapDumpFile;
  } catch (Exception e) {
    CanaryLog.d(e, "Could not dump heap");
    // Abort heap dump
    return RETRY_LATER;
  }
}
复制代码


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

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


ServiceHeapDumpListener.analyze

heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
复制代码


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

@Override public void analyze(HeapDump heapDump) {
  checkNotNull(heapDump, "heapDump");
  HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
复制代码


这里的 listenerServiceClass 指的是 DisplayLeakService.class,文章开头提到的 AndroidRefWatcherBuilder 中进行了配置。

@Override protected HeapDump.Listener defaultHeapDumpListener() {
  return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
复制代码


HeapAnalyzerService.runAnalysis

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

/**
 * This service runs in a separate process to avoid slowing down the app process or making it run
 * out of memory.
 */
public final class HeapAnalyzerService extends IntentService {
  private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
  private static final String HEAPDUMP_EXTRA = "heapdump_extra";
  public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }
  public HeapAnalyzerService() {
    super(HeapAnalyzerService.class.getSimpleName());
  }
  @Override protected void onHandleIntent(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);
    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
}
复制代码


heapAnalyzer.checkForLeak

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

AbstractAnalysisResultService.sendResultToListener

public static void sendResultToListener(Context context, String listenerServiceClassName,
    HeapDump heapDump, AnalysisResult result) {
  Class<?> listenerServiceClass;
  try {
    listenerServiceClass = Class.forName(listenerServiceClassName);
  } catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
  }
  Intent intent = new Intent(context, listenerServiceClass);
  intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
  intent.putExtra(RESULT_EXTRA, result);
  context.startService(intent);
}
复制代码


同样在 onHandleIntent 中进行处理。

DisplayLeakService.onHandleIntent

@Override protected final void onHandleIntent(Intent intent) {
  HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
  AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
  try {
    onHeapAnalyzed(heapDump, result);
  } finally {
    //noinspection ResultOfMethodCallIgnored
    heapDump.heapDumpFile.delete();
  }
}
复制代码


DisplayLeakService.onHeapAnalyzed

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

DisplayLeakService.java

@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
  String leakInfo = leakInfo(this, heapDump, result, true);
  CanaryLog.d("%s", leakInfo);
  boolean resultSaved = false;
  boolean shouldSaveResult = result.leakFound || result.failure != null;
  if (shouldSaveResult) {
    heapDump = renameHeapdump(heapDump);
    resultSaved = saveResult(heapDump, result);
  }
  PendingIntent pendingIntent;
  String contentTitle;
  String contentText;
  if (!shouldSaveResult) {
    contentTitle = getString(R.string.leak_canary_no_leak_title);
    contentText = getString(R.string.leak_canary_no_leak_text);
    pendingIntent = null;
  } else if (resultSaved) {
    pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
    if (result.failure == null) {
      String size = formatShortFileSize(this, result.retainedHeapSize);
      String className = classSimpleName(result.className);
      if (result.excludedLeak) {
        contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
      } else {
        contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
      }
    } else {
      contentTitle = getString(R.string.leak_canary_analysis_failed);
    }
    contentText = getString(R.string.leak_canary_notification_message);
  } else {
    contentTitle = getString(R.string.leak_canary_could_not_save_title);
    contentText = getString(R.string.leak_canary_could_not_save_text);
    pendingIntent = null;
  }
  // New notification id every second.
  int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
  showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
  afterDefaultHandling(heapDump, result, leakInfo);
}
复制代码


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

public static void showNotification(Context context, CharSequence contentTitle,
    CharSequence contentText, PendingIntent pendingIntent, int notificationId) {
  NotificationManager notificationManager =
      (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
  Notification notification;
  Notification.Builder builder = new Notification.Builder(context) //
      .setSmallIcon(R.drawable.leak_canary_notification)
      .setWhen(System.currentTimeMillis())
      .setContentTitle(contentTitle)
      .setContentText(contentText)
      .setAutoCancel(true)
      .setContentIntent(pendingIntent);
  if (SDK_INT >= O) {
    String channelName = context.getString(R.string.leak_canary_notification_channel);
    setupNotificationChannel(channelName, notificationManager, builder);
  }
  if (SDK_INT < JELLY_BEAN) {
    notification = builder.getNotification();
  } else {
    notification = builder.build();
  }
  notificationManager.notify(notificationId, notification);
}
复制代码


DisplayLeakService.afterDefaultHandling

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

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


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

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



相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
2天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
2天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
26天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
54 12
|
22天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
3天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
1月前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
1月前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
58 3
|
2月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
64 5

热门文章

最新文章

推荐镜像

更多