美文网首页
手把手带你分析LeanCancary源码

手把手带你分析LeanCancary源码

作者: 北疆小兵 | 来源:发表于2019-12-23 16:53 被阅读0次

简介

LeakCancary 是一个实时监控内存泄漏的开源框架,当检测到有内存泄漏时,会以通知的方式提示开发者当前发生了内存泄漏

原理

监听activity的生命周期,在Activity的onDestory方法中,开始监听activity对象, 通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收。其中最重要的两个对象为

Set<String> retainedKeys: 存放所有监控的Activity的key(值为通过uuid,唯一标识Activity)
ReferenceQueue<Object> queue:所有被回收的activity对象会存放到这个引用队列里面, 如果想要知道一个activity有没有内存泄漏,则只需要判断该activity在

具体使用步骤

1.在build.gradle中添加依赖

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

  1. 在Application中初始化
public class FastAndroidApplication extends Application {
      @Override
    public void onCreate() {
        super.onCreate();
        //监测内存泄漏
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return;
        }
        LeakCanary.install(this);
    }

至此,LeakCancary 的接入工作就完成了,是不是超简单?

源码解析

我们从入口函数开始分析

if (LeakCanary.isInAnalyzerProcess(this)) {
            return;
}
LeakCanary.install(this);

首先判断当前运行的进程是否是属于HeapAnalyzerService进程,如果是 则return调,防止应用本身Application的onCreate方法多次初始化。这里需要这么做的原因是:LeakCancary本身是运行在另外一个进程中的,这点我们可以从LeakCancary的AndroidManifaset.xml中可以看出是有单独设置process Named的。

<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>


接下来执行 LeakCanary.install

# LeakCanary.java

  public static @NonNull RefWatcher install(@NonNull Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

LeakCanary.refWatcher()

 public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

通过builder模式构建了AndroidRefWatcherBuilder对象,然后通过AndroidRefWatcherBuilder对象设置了listenerServiceClass()用于绑定DisplayLeakService服务,该服务用来分析和显示内存泄漏信息的通知

AndroidRefWatchBuilder.buildAndInstall

#AndroidRefWatchBuilder.java
public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

先调用AndroidRefWatchBuilder.buid() 构建一个RefWatcher对象

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

构建RefWatcher的参数有

  • watchExecutor:在activity 的onDetstroy方法中开始执行内存泄漏检测
  • DebuggerControl:判断是否处于debug模式,如果是则不检测
  • gcTrigger:执行gc
  • HeapDumper:dump内存泄漏处的heap信息,写入hprof文件
  • heapDumpListener:dump内存泄漏信息完后的回调
  • excludedRefs:排除可以忽略的泄漏路径

解析来是

 if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }

LeakCancaryInternal.java

# LeakCancaryInternal
  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并显示应用图标,这个图标是LeakCancary这个应用的图标

接下来是执行 :

ActivityRefWatcher.install(context, refWatcher);

# ActivityRefWatcher

public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };

在Activity的onDestroy方法中执行RefWatcher.watch(activity)

RefWatcher.java

# RefWatcher.java
  public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }
  
  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);
  }
  

这里需要注意几个变量:

  • retainedKeys:set集合,代表每一个监控对象的唯一表示,值为通过uuid产生
  • KeyedWeakReference:自定义的WeakReafernced对象,持有监控对象和其对应的key值
  • queue:ReferenceQueue对象,和 KeyedWeakReference 配合使用

这里有一个知识点:弱引用和引用队列配合时,当弱引用持有的对象被垃圾回收,java虚拟机会把这个弱引用加入到与之关联的引用队列中。也就是说当activity被回收时,activity对象的引用就会被添加到ReferenceQueue这个引用队列中。

接下来是具体的内存泄漏判断过程

efWatcher.ensureGoneAsync

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


这里的watchExecutor 实现类是AndroidWatchExecutor

AndroidWatchExecutor.execute()


# AndroidWatchExecutor.java
public void execute(@NonNull 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);
      }
    });
  }

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

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

这里是切换到主线程,当消息队列空闲时执行run方法, run方法实际执行的是RefWatcher中的ensureGone()

RefWatcher.ensureGone();

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

      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();

      heapdumpListener.analyze(heapDump);
    }
    return DONE;

  1. 首先通过removeWeaklyReachablereference来移除已经被回收的Activity引用()
  private void removeWeaklyReachableReferences() {

    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

遍历ReferenceQueue列表中的对象(这些对象已经被回收), 判断对象是否存在当前activity的弱引用,存在则删除retainedKeys中的activity的key 值; 如果activity没有被回收,则不会添加到ReferenceQueue,也就不会从retainedKeys中移除

2.通过gone(reference)来判断当前弱引用对应的Activity是否存在于retainedKeys?如果不存在,则说明通过第一步的操作,已经移除了该引用的key值,直接返回即可。
3.如果第二部没有返回,说明retainedKeys还存在当前activity的引用(也就是改activity没有被添加到ReferenceQueue,没有被回收),则调用GcTigger.runGc方法运行GC.

  1. gc完成后再执行第一步,
  2. 然后再判断第2步是否被回收了,如果还没回收(gone(reference)返回false), 则认为是已经内存泄漏了,则开始执行内存泄漏结果处理

AndroidHeapDumper.dumpHeap()

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

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

然后调用heapdumpListener.analyze(heapDump)分析刚刚生成的heapDumpwen文件

这里的heapdumpListener 是ServiceHeapDumpListener

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

调用HeapAnalyzerService开始启动HeapAnalyzerService 这个前台服务执行分析

HeapAnalyzerService.java

# heapAnalyzer.checkForLeak
  @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);

    HeapAnalyzer heapAnalyzer =
        new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

调用heapAnalyzer.checkForLeak 获取结果后,调用AbstractAnalysisResultService.sendResultToListener展示分析结果。具体是通过DisplayLeakService 来展示的。

public class DisplayLeakService extends AbstractAnalysisResultService {
    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();
  }

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



最后会执行afterDefaultHandling方法,在这里我们可以自定义一些操作,例如上报泄漏信息给服务器

总结

LeakCancary主要是利用了弱引用 WeakReference 和 引用队列 ReferenceQueue的知识,当WeakReference中引用的对象被回收时,该引用会被添加到ReferenceQueue中,如果没有被回收,则不会添加到ReferenceQueue中。 所以可以通过检测ReferenceQueue是否存在activity的引用来判断activity是否存在泄漏导致没有回收。

LeakCanacary 监控内存泄漏主要流程如下:

  1. 监听Activity的onDestroy方法,将Activity包装到WeakReference和ReferenceQueue中,并将该Activity对应的key(通过uuid产生),添加到set<String>retainedKeys中
  2. 便利ReferenceQueue队列,将存在于队列中的activity对象,从retainedKeys中移除
  3. 判断retainedKeys是否存在该activity引用的key (第一步通过uuid产生,如果不存在:则说明没有内存泄漏,终止流程;如果存在:执行第4步
  4. run gc
  5. 执行第2步
  6. 执行第3步,如果activity的key存在于set<String>retainedKeys,则认为存在内存泄漏
  7. 生成hprof文件
  8. 启动HeapAnalyzerService分析泄漏信息
  9. 启动DisplayService显示通知

参考 https://juejin.im/post/5a9d46d2f265da237d0280a3

相关文章

网友评论

      本文标题:手把手带你分析LeanCancary源码

      本文链接:https://www.haomeiwen.com/subject/bjxtoctx.html