「Leakcanary 源码分析」看这一篇就够了

image.png

「Leakcanary 」是我们经常用于检测内存泄漏的工具,简单的使用方式,内存泄漏的可视化,是我们开发中必备的工具之一。

分析源码之前

Leakcanary 大神的 github ,最好的老师。

一、使用

1、配置

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
  // Optional, if you use support library fragments:
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}

2、简单使用

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

超级简单的配置和使用方式。最后就会得出以下的事例说明。

image.png

二、准备工作

1、Reference

Reference 把内存分为 4 种状态,Active 、 Pending 、 Enqueued 、 Inactive。

  • Active 一般说来内存一开始被分配的状态都是 Active
  • Pending 快要放入队列(ReferenceQueue)的对象,也就是马上要回收的对象
  • Enqueued 对象已经进入队列,已经被回收的对象。方便我们查询某个对象是否被回收
  • Inactive 最终的状态,无法变成其他的状态。

2、ReferenceQueue

引用队列,在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中

3、如果检测一个对象是否被回收

需要采用 Reference + ReferenceQueue

  • 创建一个引用队列 queue
  • 创建 Reference 对象(通常用弱引用)并关联引用队列
  • 在 Reference 被回收的时候,Reference 会被添加到 queue 中
//创建一个引用队列  
ReferenceQueue queue = new ReferenceQueue();  

// 创建弱引用,此时状态为Active,并且Reference.pending为空,
// 当前Reference.queue = 上面创建的queue,并且next=null  
// reference 创建并关联 queue
WeakReference reference = new WeakReference(new Object(), queue);  

// 当GC执行后,由于是弱引用,所以回收该object对象,并且置于pending上,此时reference的状态为PENDING  
System.gc();  
  
// ReferenceHandler从 pending 中取下该元素,并且将该元素放入到queue中,
//此时Reference状态为ENQUEUED,Reference.queue = ReferenceENQUEUED 
  
// 当从queue里面取出该元素,则变为INACTIVE,Reference.queue = Reference.NULL  
Reference reference1 = queue.remove();  

在 Reference 类加载的时候,Java 虚拟机会会创建一个最大优先级的后台线程,这个线程的工作就是不断检测 pending 是否为 null,如果不为 null,那么就将它放到 ReferenceQueue。因为 pending 不为 null,就说明引用所指向的对象已经被 GC,变成了不也达。<br /><br />

4、ActivityLifecycleCallbacks

用于监听所有 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);
        }
      };

5、Heap Dump

Heap Dump也叫堆转储文件,是一个Java进程在某个时间点上的内存快照。

三、原理说明

1、监听 Activity 的生命周期。<br />2、在 onDestory 的时候,创建对应的 Actitity 的 Refrence 和 相应的 RefrenceQueue,启动后台进程去检测。<br />3、一段时间后,从 RefrenceQueue 中读取,如果有这个 Actitity 的 Refrence,那么说明这个 Activity 的 Refrence 已经被回收,但是如果 RefrenceQueue 没有这个 Actitity 的 Refrence 那就说明出现了内存泄漏。<br />4、dump 出 hprof 文件,找到泄漏路径。

分析源码

程序的唯一入口 LeakCanary.install(this);<br /><br />

1、install

DisplayLeakService 这个类负责发起 Notification 以及将结果记录下来写在文件里面。以后每次启动LeakAnalyzerActivity就从这个文件里读取历史结果,并展示给我们。

  public static RefWatcher install(Application application) {
    return install(application, DisplayLeakService.class);
  }
  public static RefWatcher install(Application application,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
   //如果在主线程 那么返回一个无用的 RefWatcher  详解 1.1
    if (isInAnalyzerProcess(application)) {
      return RefWatcher.DISABLED;
    }
    //把 DisplayLeakActivity 设置为可用  用于显示 DisplayLeakActivity 就是我们看到的那个分析界面
    enableDisplayLeakActivity(application);
   // 详解 1.2
    HeapDump.Listener heapDumpListener =
        new ServiceHeapDumpListener(application, listenerServiceClass);
    //详解 2
    RefWatcher refWatcher = androidWatcher(application, heapDumpListener);
    详解 3
    ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
    return refWatcher;
  }

1.1 isInAnalyzerProcess

因为 分析的进程是硬外一个独立进程 所以要判断是否是主进程,这个工作需要在 AnalyzerProcess 中进行。

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

把App 的进程 和 这个 Service 进程进行对比 。

  private static boolean isInServiceProcess(Context context,
      Class<? extends Service> serviceClass) {
    PackageManager packageManager = context.getPackageManager();
    PackageInfo packageInfo;
    try {
      packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
    } catch (Exception e) {
      Log.e("AndroidUtils", "Could not get package info for " + context.getPackageName(), e);
      return false;
    }
    String mainProcess = packageInfo.applicationInfo.processName;

    ComponentName component = new ComponentName(context, serviceClass);
    ServiceInfo serviceInfo;
    try {
      serviceInfo = packageManager.getServiceInfo(component, 0);
    } catch (PackageManager.NameNotFoundException ignored) {
      // Service is disabled.
      return false;
    }

    if (serviceInfo.processName.equals(mainProcess)) {
      Log.e("AndroidUtils",
          "Did not expect service " + serviceClass + " to run in main process " + mainProcess);
      // Technically we are in the service process, but we're not in the service dedicated process.
      return false;
    }

    int myPid = android.os.Process.myPid();
    ActivityManager activityManager =
        (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    ActivityManager.RunningAppProcessInfo myProcess = null;
   
    for (ActivityManager.RunningAppProcessInfo process : activityManager.getRunningAppProcesses()) {
      if (process.pid == myPid) {
        myProcess = process;
        break;
      }
    }
    if (myProcess == null) {
      Log.e("AndroidUtils", "Could not find running process for " + myPid);
      return false;
    }
 //把App 的进程 和 这个 Service 进程进行对比 
    return myProcess.processName.equals(serviceInfo.processName);
  }

1.2 ServiceHeapDumpListener

设置 DisplayLeakService 和 HeapAnalyzerService 的可用。<br />analyze 方法,开始分析 HeapDump。

public final class ServiceHeapDumpListener implements HeapDump.Listener {

  private final Context context;
  private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

  public ServiceHeapDumpListener(Context context,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    LeakCanary.setEnabled(context, listenerServiceClass, true);
    LeakCanary.setEnabled(context, HeapAnalyzerService.class, true);
    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);
  }
}

2、RefWatcher

  private final Executor watchExecutor;
  private final DebuggerControl debuggerControl;
  private final GcTrigger gcTrigger;
  private final HeapDumper heapDumper;
  private final Set<String> retainedKeys;
  private final ReferenceQueue<Object> queue;
  private final HeapDump.Listener heapdumpListener;
  • watchExecutor:执行内存泄漏检测的 Executor。
  • debuggerControl:用于查询是否在 debug 调试模式下,调试中不会执行内存泄漏检测。
  • gcTrigger:GC 开关,调用系统GC。
  • heapDumper:用于产生内存泄漏分析用的 dump 文件。即 dump 内存 head。
  • retainedKeys:保存待检测和产生内存泄漏的引用的 key。
  • queue:用于判断弱引用持有的对象是否被 GC。
  • heapdumpListener:用于分析 dump 文件,生成内存泄漏分析报告。

这里创建我们所需要的 RefWatcher。

  public static RefWatcher androidWatcher(Application app, HeapDump.Listener heapDumpListener) {
    DebuggerControl debuggerControl = new AndroidDebuggerControl();
    AndroidHeapDumper heapDumper = new AndroidHeapDumper(app);
    heapDumper.cleanup();
    return new RefWatcher(new AndroidWatchExecutor(), debuggerControl, GcTrigger.DEFAULT,
        heapDumper, heapDumpListener);
  }

3、ActivityRefWatcher

  public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
    if (SDK_INT < ICE_CREAM_SANDWICH) {
      // If you need to support Android < ICS, override onDestroy() in your base activity.
      return;
    }
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    activityRefWatcher.watchActivities();
  }
  //注册 lifecycleCallbacks
  public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }
  //ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks
  //就是 mActivityLifecycleCallbacks 的添加
  public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
        synchronized (mActivityLifecycleCallbacks) {
            mActivityLifecycleCallbacks.add(callback);
        }
    }
  // 注销 lifecycleCallbacks
   public void stopWatchingActivities() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
  }
  //  //就是 mActivityLifecycleCallbacks 的 移除
  public void unregisterActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
        synchronized (mActivityLifecycleCallbacks) {
            mActivityLifecycleCallbacks.remove(callback);
        }
    }

本质就是在 Activity 的 onActivityDestroyed 方法里 执行 refWatcher.watch(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);
  }

4、watch

  public void watch(Object watchedReference, String referenceName) {
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    if (debuggerControl.isDebuggerAttached()) {
      return;
    }
    //随机生成 watchedReference 的 key 保证其唯一性
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    //这个一个弱引用的子类拓展类 用于 我们之前所说的 watchedReference 和  queue 的联合使用
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    watchExecutor.execute(new Runnable() {
      @Override public void run() {
      //重要方法,确然是否 内存泄漏
        ensureGone(reference, watchStartNanoTime);
      }
    });
  }
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");
  }
}

5、ensureGone

  void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();

    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
  //把 queue 的引用 根据 key 从 retainedKeys 中引出 。
  //retainedKeys 中剩下的就是没有分析和内存泄漏的引用的 key
  removeWeaklyReachableReferences();
  //如果内存没有泄漏 或者处于 debug 模式那么就直接返回
    if (gone(reference) || debuggerControl.isDebuggerAttached()) {
      return;
    }
    //如果内存依旧没有被释放 那么在 GC 一次
    gcTrigger.runGc();
    //再次 清理下 retainedKeys
    removeWeaklyReachableReferences();
    //最后还有 就是说明内存泄漏了 
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//dump 出 Head 报告
      File heapDumpFile = heapDumper.dumpHeap();

      if (heapDumpFile == null) {
        // Could not dump the heap, abort.
        return;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
    //最后进行分析 这份 HeapDump  
    //LeakCanary 分析内存泄露用的是一个第三方工具 HAHA 别笑 真的是这个名字
    heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs,
              heapDumpDurationMs));
    }
  }
  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);
    }
  }
  
  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

6、haha

大家有兴趣可以分析下这个分析库的原理。在这里就不深入研究了。

最后把分析的引用链 写入文件中,发通知。

  @TargetApi(HONEYCOMB) @Override
  protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = leakInfo(this, heapDump, result);
    Log.d("LeakCanary", leakInfo);

    if (!result.leakFound || result.excludedLeak) {
      afterDefaultHandling(heapDump, result, leakInfo);
      return;
    }

    File leakDirectory = DisplayLeakActivity.leakDirectory(this);
    int maxStoredLeaks = getResources().getInteger(R.integer.__leak_canary_max_stored_leaks);
    File renamedFile = findNextAvailableHprofFile(leakDirectory, maxStoredLeaks);

    if (renamedFile == null) {
      // No file available.
      Log.e("LeakCanary",
          "Leak result dropped because we already store " + maxStoredLeaks + " leak traces.");
      afterDefaultHandling(heapDump, result, leakInfo);
      return;
    }

    heapDump = heapDump.renameFile(renamedFile);

    File resultFile = DisplayLeakActivity.leakResultFile(renamedFile);
    FileOutputStream fos = null;
    try {
      fos = new FileOutputStream(resultFile);
      ObjectOutputStream oos = new ObjectOutputStream(fos);
      oos.writeObject(heapDump);
      oos.writeObject(result);
    } catch (IOException e) {
      Log.e("LeakCanary", "Could not save leak analysis result to disk", e);
      afterDefaultHandling(heapDump, result, leakInfo);
      return;
    } finally {
      if (fos != null) {
        try {
          fos.close();
        } catch (IOException ignored) {
        }
      }
    }

    PendingIntent pendingIntent =
        DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

    String contentTitle =
        getString(R.string.__leak_canary_class_has_leaked, classSimpleName(result.className));
    String contentText = getString(R.string.__leak_canary_notification_message);

    NotificationManager notificationManager =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    Notification notification;
    if (SDK_INT < HONEYCOMB) {
      notification = new Notification();
      notification.icon = R.drawable.__leak_canary_notification;
      notification.when = System.currentTimeMillis();
      notification.flags |= Notification.FLAG_AUTO_CANCEL;
      notification.setLatestEventInfo(this, contentTitle, contentText, pendingIntent);
    } else {
      Notification.Builder builder = new Notification.Builder(this) //
          .setSmallIcon(R.drawable.__leak_canary_notification)
          .setWhen(System.currentTimeMillis())
          .setContentTitle(contentTitle)
          .setContentText(contentText)
          .setAutoCancel(true)
          .setContentIntent(pendingIntent);
      if (SDK_INT < JELLY_BEAN) {
        notification = builder.getNotification();
      } else {
        notification = builder.build();
      }
    }
    notificationManager.notify(0xDEAFBEEF, notification);
    afterDefaultHandling(heapDump, result, leakInfo);
  }

总结

其实沿着源码分析很容易让人无法自拔,所以我们更要跳出来看到本质。

1、监听 Activity 的生命周期,在 onDestory 方法里调用 RefWatcher 的 watch 方法。<br />watch 方法监控的是 Activity 对象<br />2、给Activyty 的 Reference 生成唯一性的 key 添加到 retainedKeys 。生成 KeyedWeakReference 对象 ,Activity 的弱引用和 ReferenceQueue 关联。执行 ensureGone 方法。<br />3、如果 retainedKeys 中没有 该 Reference 的 key 那么就说明没有内存泄漏。<br />4、如果有,那么 analyze 分析我们 HeadDump 文件。建立导致泄漏的引用链。<br />5、引用链传递给 APP 进程的 DisplayLeakService,以通知的形式展示出来。<br /><br /><br /><br />

最后

<br />「云开方见日,潮尽炉峰出。」