文章目录
核心知识点
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时的一些参数 |
注意两点:
- AndroidRefWatcherBuilder与RefWatcherBuilder使用了builder模式,可以看下是怎么用泛型实现Builder模式的继承结构的。
- 参数里边的默认实现,都是在接口定义中使用匿名内部类定义了一个默认的的实现,这种写法可以参考,比如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