LeakCanary源码分析(基于1.6.2)

核心知识点

1.弱引用探测内存泄露

WeakReference(T referent, ReferenceQueue<? super T> q)
referent被gc回收时,会将包裹它的弱引用注册到ReferenceQueue中,在gc后判断ReferenceQueue有没有referent包裹的WeakReference,就可以判断是否被gc正常回收。

2. Debug.dumpHprofData(String fileName)获取运行时内存HPROF data

debug模式下调用 Debug.dumpHprofData(String fileName),可以将当前的内存情况的转储都指定的文件路径下。

3. squareup的haha 库解析HPROF文件

haha库是一个专门用来分析Android heap dumps的库,通过它来解析获取引用链。

一句话总结: 使用WeakReference和ReferenceQueue在某些时刻(Activity和Fragment的 onDestory())监测内存是否泄露,如果泄露就用Debug.dumpHprofData()获取HPROF文件,然后通过haha库解析获取泄露引用的引用链。

源码分析

初始化代码:

public class MyApplication 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.isInAnalyzerProcess(this)

LeakCanary.isInAnalyzerProcess(this)会调用 sInServiceProcess(context, HeapAnalyzerService.class)来判断是否跟HeapAnalyzerService在同一个进程中。

 public static boolean isInAnalyzerProcess(@NonNull Context context) {
   
    Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
    // This only needs to be computed once per process.
    if (isInAnalyzerProcess == null) {
   
      isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
      LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
    }
    return isInAnalyzerProcess;
  }

HeapAnalyzerService是一个IntentServcie,主要用来分析生成的hprof文件。我们看下HeapAnalyzerService的清单配置:

<service
        android:name=".internal.HeapAnalyzerService"
        android:process=":leakcanary"
        android:enabled="false"
        />

可以看到,这个服务运行在单独的“:leakcanary”进程中。如果你浏览下LeakCanary的其他android组件(DisplayLeakService,DisplayLeakActivity等)清单配置,会发现它们都是运行在这个进程中的。为什么HeapAnalyzerService要单独放在这个进程?因为内存泄露时,生成的hprof文件很大,解析的时候会耗费大量内存,如果放在app主进程中,可能会导致OOM。

另外,我们也注意到**android:enabled=“false”**这个属性设置,那说明默认情况下,HeapAnalyzerService这个service是不可用的,**那它什么时候打开呢?**带着这个疑问我们接着往下看:

public 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) {
   
      CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
      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.
      /****标记1*******.首次运行的时候,HeapAnalyzerService组件没有打开,所以到这里就返回了***/
      return false;
    }
    //HeapAnalyzerService激活后才会有下边的代码
    if (serviceInfo.processName.equals(mainProcess)) {
   /****标记2*******/
      CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, 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;
    List<ActivityManager.RunningAppProcessInfo> runningProcesses;
    try {
   
      runningProcesses = activityManager.getRunningAppProcesses();
    } catch (SecurityException exception) {
   
      // https://github.com/square/leakcanary/issues/948
      CanaryLog.d("Could not get running app processes %d", exception);
      return false;
    }
    if (runningProcesses != null) {
   
      for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
   
        if (process.pid == myPid) {
   
          myProcess = process;
          break;
        }
      }
    }
    if (myProcess == null) {
   
      CanaryLog.d("Could not find running process for %d", myPid);
      return false;
    }

    return myProcess.processName.equals(serviceInfo.processName);
  }

上边我们说过,HeapAnalyzerService组件默认是关闭的,所以一次执行的时候,到代码中1标识的位置就结束了。第二次执行的时候,才会往下走。此外,在2标记的的位置,如果你把HeapAnalyzerService的进程设置为跟主进程一样,LeakCanary依然可以工作,但要小心OOM。再往下就是判断HeapAnalyzerService服务进程是否跟主进程一样。

LeakCanary.install(this)

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

intall方法中,生成了AndroidRefWatcherBuilder对象,设置了一些参数,并最终调用了buildAndInstall()。
看下AndroidRefWatcherBuilder类

AndroidRefWatcherBuilder

AndroidRefWatcherBuilder继承自RefWatcherBuilder,RefWatcherBuilder持有一系列功能接口参数,这些参数在build()方法的的时候,最终传递给RefWatcher。RefWatcherBuilder有六个参数,这些参数及在android平台上的实现类对应关系及含义入:

参数类型 默认实现 android平台实现类 含义
HeapDump.Listener HeapDump.Listener.NONE ServiceHeapDumpListener 执行分析HeapDump,在android中是在启动DisplayLeakService在一个新的的进程中,执行解析dump
DebuggerControl DebuggerControl.NONE AndroidDebuggerControl 判断进程是否处于debug状态
HeapDumper HeapDumper.NONE AndroidHeapDumper dump heap到文件中
GcTrigger GcTrigger.DEFAULT GcTrigger.DEFAULT 主动调用GC
WatchExecutor WatchExecutor.NONE AndroidWatchExecutor 延迟调用GC的执行器;在android平台是内部有一个HandThread,通过 handler来做延迟操作
HeapDump.Builder HeapDump.Builder HeapDump.Builder 主要是构造执行dump时的一些参数

注意两点:

  1. AndroidRefWatcherBuilder与RefWatcherBuilder使用了builder模式,可以看下是怎么用泛型实现Builder模式的继承结构的。
  2. 参数里边的默认实现,都是在接口定义中使用匿名内部类定义了一个默认的的实现,这种写法可以参考,比如DebuggerControl的定义
public interface DebuggerControl {
   
  DebuggerControl NONE = new DebuggerControl() {
   
    @Override public boolean isDebuggerAttached() {
   
      return false;
    }
  };

  boolean isDebuggerAttached();
}

上边说的HeapDump.Builder(Builder模式),最终会构造一个HeapDump对象,看下他有哪些参数:

参数名称 类型 含义
heapDumpFile File hprof文件路径
referenceKey String 是一个UUID,用来唯一标识一个要监测的对象的
referenceName String 用户给监控对象自己定义的名字
excludedRefs ExcludedRefs 要排除的类集合;因为有些内存泄露,不是我们的程序导致的,而是系统的bug,这些bug我们无能为力,所以做了这样一个列表,把这些泄露问题排除掉,不会展示给我们
watchDurationMs long 执行RefWatcher.watch()之后,到最终监测到内存泄露花费的时间
gcDurationMs long 手动执行gc花费的时间
heapDumpDurationMs long 记录dump内存花费的时间
computeRetainedHeapSize boolean 是否计算持有的堆的大小
reachabilityInspectorClasses List<Class<? extends Reachability.Inspector>>

理解完这些参数后,再回到我们install()中调用的listenerServiceClass():

public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
      @NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
   
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

ServiceHeapDumpListener:先简单说下这个listsener的作用,当监测到内存泄露时,就会hprof文件回调给这个listener,在回调中它会启启动独立的进程,解析这这个文件,并将解析结果传给AbstractAnalysisResultService的实现类,也即install方法中传入的DisplayLeakService,DisplayLeakService会弹出系统通知提示发生了内存泄露,这个listener将整个流程串了起来。
再往下看最后调用的AndroidRefWatcherBuilder的buildAndInstall():

public @NonNull RefWatcher buildAndInstall() {
   
    if (LeakCanaryInternals.installedRefWatcher != null) {
   
      throw new UnsupportedOperationException
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值