好的项目离不开健壮的代码,对于想要写出健壮的代码,解决内存泄漏是必须的。
对于LeakCanary,对于大多人是不陌生的,也就是检测内存泄漏的工具。可能在代码中我们会这样引入LeakCanary:
//检查leakCanary和APP是否在同一个进程,如果是同一个进程就返回,不在同一个进程,就注册。
//因为不再同一个进程,不会对APP进程造成消极影响(如:APP进程变慢或者out of memory)
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);
简简单单的两句话,背后的原理却是一大堆。
在开始源码之前,先说几个知识点:
强引用,软引用,弱引用,GC线程扫描它所管辖的内存区域时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
垃圾回收器是一个优先级很低的线程,即使有弱引用的存在,也不一定会执行。
我们手动调用GC,不一定成功调用垃圾回收器,因为我们仅仅是建议JVM执行GC,最终执不执行,还是得看JVM的最终决策。
LeakCanary是在主进程进行内存检测,并且是在主进程空闲的时候进行的。 这就是为什么会出现我们关闭Activity了,过了一段时间,才显示内存泄漏了。
Android4.0之后,新加了ActivityLifecycleCallbacks,作用就是,同一进程下,每个Activity的的生命周期都会进入到ActivityLifecycleCallbacks回调的相应方法中。
以上知识点是我们需要知道的。接下来看源码:
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
方法返回的是RefWatcher ,RefWatcher 是分析内存泄露的核心类,里面提供了相应的方法用户分析内存泄漏。其中refWatcher(application)返回的是AndroidRefWatcherBuilder对象,listenerServiceClass方法里面的DisplayLeakService参数是:通知给开发者显示内存泄漏的详细信息,其继承自IntentService, 不理解的可以看这个:
Android进阶2:线程和线程池(3)—— IntentService原理解析
然后就是链式调用了excludedRefs方法,作用是忽略系统底层的内存泄漏,也就是说不是我们开发者造成的内存泄漏。最后调用了buildAndInstall方法,这个方法最终返回的是RefWatcher对象:
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
执行ActivityRefWatcher.install,看下:
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
调用了watchActivities(), 继续深究:
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
//给Application注册监听事件;
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
到这里我们就明白了原来就是LeakCanary内部实际上用的也就是ActivityLifecycleCallbacks,那么,想一下lifecycleCallbacks对象的实现,既然我们需要检测内存泄漏,那么肯定是在Activity ‘销毁’ 之后,才能够检测的,那么名义上的销毁,调用的方法也就是onActivityDestroyed,所以我们猜想LeakCanary的检测内存泄漏的逻辑是写在onActivityDestroyed中的。
接下来看下源码,验证一下:
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);
}
};
通过源码看,和我们的猜想一样的,检测内存泄漏的逻辑确实是在onActivityDestroyed方法中的。
继续,看下onActivityDestroyed方法,注意传递的参数是当前销毁的Activity对象。其最终调用的还是RefWatcher的watch方法,
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中
retainedKeys.add(key);
//将Activity和唯一标识以及ReferenceQueue作为参数,生成弱引用KeyedWeakReference
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//开始内存泄漏检测
ensureGoneAsync(watchStartNanoTime, reference);
}
watchedReference 也就是我们传入的Activity对象,上面源码主要是先生成了唯一标识key,然后将Activity和唯一标识以及ReferenceQueue作为参数生成弱引用对象reference 。ReferenceQueue的作用是:在适当的时候检测到对象的可达性发生改变后,垃圾回收器就将已注册的引用对象添加到此队列中。后面代码里使用一个弱引用连接到你需要检测的对象,然后使用ReferenceQueue来监测这个弱引用可达性的改变;说白了ReferenceQueue就是一个弱引用队列。然后垃圾回收器会自动的回收此队列中的对象。KeyedWeakReference 本质也就是将我们的Activity封装了一层,变成了唯一的一个弱引用对象。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
//开启子线程进行内存分析
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
可以看出,实际上LeakCanary分析内存是在子线程中的。
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//删除弱引用
removeWeaklyReachableReferences();
//是否处于debugger状态,如果是就返回
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 看是否此弱引用对象还存在
if (gone(reference)) {
//如果不存在,就返回,表明没有内存泄漏
return DONE;
}
//如果还存在,就手动调用GC,
gcTrigger.runGc();
//再次删除弱引用
removeWeaklyReachableReferences();
//再次判断此弱引用对象是否存在
if (!gone(reference)) {
//如果还存在,就分析Dump内存快照。
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);
//分析analyze方法;
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
从上述代码可以看出,LeakCanary检测是否内存泄漏的步骤是:
先删除弱引用
判断弱引用是否还存在,如果不存在,说明已经没有内存泄漏;如果存在,就执行3步骤。
手动调用GC ,然后再次删除弱引用
再次判断弱引用是否还存在,如果不存在,就表明没有内存泄漏;如果存在,就开始深层次的分析,这里还是疑似内存泄漏,因为手动触发GC,仅仅是建议JVM触发垃圾回收,真正执不执行还是得看JVM。
注意这里判断弱引用是否存在的标识就是:上述源码生成的唯一标识key。
最后如果进行到第四步骤,也就是深度分析,此时升读分析的也就是Hprof文件。 这里的heapdumpListener也就是ServiceHeapDumpListener对象。关于为什么是ServiceHeapDumpListener对象,可以看下上面源码的install方法内的listenerServiceClass方法:
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
继续深入:
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
//单独开一个进程,开始深度分析
//HeapAnalyzerService类的注释:此服务在单独的进程中运行,以避免放慢应用程序进程或使其运行
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
也就是说heapdumpListener.analyze最终调用的也就是ServiceHeapDumpListener的analyze方法。HeapAnalyzerService继承自IntentService, runAnalysis方法的内部做的也就是开启Service(也就是它自己),那么我们知道对于IntentService,暴露了onHandleIntent抽象方法,共开发者处理相关的业务逻辑,所以看下onHandleIntent内部的代码:
@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);
//然后调用checkForLeak方法分析,并返回分析结果
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
//
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
上述代码流程是先创建了一个堆分析对象,然后开始分析,最后返回分析结果,并调用AbstractAnalysisResultService.sendResultToListener发送结果,展示给开发者。值得注意的是listenerClassName参数也就是上面源码的DisplayLeakService对象,然后在AbstractAnalysisResultService的sendResultToListener方法内,通过反射获取DisplayLeakService对象:
public static void sendResultToListener(Context context, String listenerServiceClassName,
HeapDump heapDump, AnalysisResult result) {
Class<?> listenerServiceClass;
try {
//通过反射获取ServiceHeapDumpListener对象
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);
}
通过反射获取到DisplayLeakService对象,然后开启自己,
@Override protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
try {
//最终调用的还是DisplayLeakService的方法。
onHeapAnalyzed(heapDump, result);
} finally {
//noinspection ResultOfMethodCallIgnored
heapDump.heapDumpFile.delete();
}
}
看下DisplayLeakService的onHeapAnalyzed方法:
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
.......
//展示通知
showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
afterDefaultHandling(heapDump, result, leakInfo);
}
至此LeakCanary流程分析大致完成了。
接下来说下开发中对于ActivityLifecycleCallbacks的使用,在实际开发中,常常会需要埋点,我们项目中用的是友盟,后来公司运营需要一套自己的经营分析系统,也就是说友盟一套埋点数据,我们自己一套埋点数据,埋点需求:点击,页面停留时长等,这时候使用ActivityLifecycleCallbacks监听Activity的状态,就非常方便了。
来源:oschina
链接:https://my.oschina.net/u/920274/blog/3064715