今天来写一个关于LeakCanary-1.5.0的一些心得;我们移动端性能方面第一个就想到使用LeakCanary;但是很多使用LeakCanary无法解析,以及LeakCanary在不同手机上面也会有不同的一些提示;我这边自己抽了一些时间,对LeakCanary的架构进行分析,以及代码的实现过程进行分析;不知道一篇能不能写完:下面先上一个图;
这个是自己画的LeakCanary的uml图;仅仅画了初始化的地方;这边会从这个图一步步解析LeakCanary的作用;首先我们先看到LeakCanary的初始化
1)install(Application application)初始化方法
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
其中new 一个AndroidRefWatcherBuilder对象,开始注册listenerServiceClass并且注册一个ExcludedRefs,最后进行组装;
其中buildAndInstall方法里面涉及到了Application注册Activity的生命周期;主要在生命周期的里面的onActivityDestroyed里面进行触发一个方法ActivityRefWatcher.this.onActivityDestroyed(activity);
2)我们开始分析,第一步new AndroidRefWatcherBuilder并且设置ApplicationContext;
3)设置listenerServiceClass;其中传入了DisplayLeakService.class;(专门处理heapDump文件)
DisplayLeakService的父类是AbstractAnalysisResultService,AbstractAnalysisResultService的父类是IntentService。其实走到这一步;大概能看出来做一个异步的耗时操作了;肯定是在onHandleIntent(Intent intent);并且sendResultToListener是进行发送耗时操作的方式;获取heapDump文件,进行分析;重写onHeapAnalyzed方法,具体在DisplayLeakService实现;(具体实现后面分析)
4) 获取AndroidExcludedRefs;创建ExcludedRefs对象;里面更多包含的是配置
先看一下createAndroidDefaults方法;其中里面默认实现了SOFT_REFERENCES, FINALIZER_WATCHDOG_DAEMON, MAIN, LEAK_CANARY_THREAD, EVENT_RECEIVER__MMESSAGE_QUEUE;
A) SOFT_REFERENCES:
a.注册软引用的类名
b.注册弱引用的类名
c.注册虚引用的类名
d.注册Finalizer的类名(GC: 从一个对象变得不可达开始,到执行它的finalizer方法,时间可能任意长)
f.注册FinalizerReference的类名(class类里面定义finalize方法,就会创建)
B)FINALIZER_WATCHDOG_DAEMON:
a.注册FinalizerWatchdogDaemon线程名称(回收的线程)
C)MAIN
a.注册主线程名称
D)LEAK_CANARY_THREAD
a.注册LeakCanary的工作线程名称
E)EVENT_RECEIVER__MMESSAGE_QUEUE
a.注册android.view.Choreographer$FrameDisplayEventReceiver以及mMessageQueue;应该是需要继承修改;后续在看
F)剩下的其他和机型;Api相关。是针对某一个Api遇到内存泄露作出的指定捕获,或者刨除;这边也是后续抽一个例子细看
这个类使用的是 EnumSet.allOf 该方法接受一个元素类型的参数elementType,并引用其元素将存储到集合中的类对象;
5)buildAndInstall()方法
调用这个方法的时候;判断是否是初始化默认的DISABLED;这个位子后续说;这边不是;所以走下面的代码;
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
}
return refWatcher;
}
走到enableDisplayLeakActivity方法;
public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}
里面开启类一个单核心线程
public static void setEnabled(Context context, final Class> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
executeOnFileIoThread(new Runnable() {
@Override public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
来处理setEnabledBlocking方法;
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);
}
其中packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);来控制启用 禁用 四大组件
由上面代码里面 对DisplayLeakActivity进行设置成----可用状态,并且不杀死APP(明显是在处理Activity的)
下面在走 installOnIcsPlus 方法;
其中SDK低于14的不进行初始化观察;
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();
}
其中使用Application进行注册Activity生命周期,所以Fragment的泄露希望是自己去调用
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方法;
刚刚在上面没有提到buildAndInstall方法里面的build;现在我们回过来讲一下;因为我们现在已经知道了,这个LeakCanary触发的时机;
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
//1
ExcludedRefs excludedRefs = this.excludedRefs;
if (excludedRefs == null) {
excludedRefs = defaultExcludedRefs();
}
//2
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
//3
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
//4
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
// 5
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
// 6
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);
}
我们先看到 :
1 是ExcludedRefs对象的赋值;从uml图里面可以看到子类AndroidRefWatcherBuilder和父类RefWatcherBuilder;在初始化的时候调用的就是对父类this.excludedRefs赋值;
2是AndroidRefWatcherBuilder子类里面调用父类的heapDumpListener方法赋值this.heapDumpListener
3是AndroidRefWatcherBuilder类里面重写子类的defaultDebuggerControl方法,里面获取是否是被调试状态:Debug.isDebuggerConnected()
4 是AndroidRefWatcherBuilder类里面重写子类的defaultHeapDumper方法 (下面细讲)
5 是AndroidRefWatcherBuilder类里面重写子类的defaultWatchExecutor方法,初始化AndroidWatchExecutor延迟5000毫秒
(专门开了一个工作线程处理)
6 是AndroidRefWatcherBuilder类里面 使用默认的GcTrigger(做GC操作的)
然后组装到RefWatcher里面去;下面我们开始讲解一下整个工作的流程;以及重点的实现部分
现在开走;实现的内存泄露检测流程
当Activity涉及到destory时
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
调用watch方法
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");//校验不为null
checkNotNull(referenceName, "referenceName");//校验不为null
final long watchStartNanoTime = System.nanoTime();//纳秒
String key = UUID.randomUUID().toString();//唯一标识码
retainedKeys.add(key);//缓存起来
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);//生成weak;配置一个key和name(name看起来是空值),加到队列里面去,继承WeakReference
ensureGoneAsync(watchStartNanoTime, reference);//实现runable的方法;
}
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {//watchExecutor上面说的设置一个工作线程;
//判断是否是主线程; 如果是的话,直接addIdleHandler(CPU空闲的时候会调用)然后执行run();(有重试机制)都是延时操作
// 如果不是主线程,先post回主线程,addIdleHandler(CPU空闲的时候会调用)然后执行run();(有重试机制)都是延时操作
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);//RETRY会进行重试
}
});
}
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();//获取纳秒
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);//计算出来耗时多久
removeWeaklyReachableReferences();//移除队列里面的值,以及对应的retainedKeys的name 重要1
if (debuggerControl.isDebuggerAttached()) {//判断是否在调试,如果是调试的返回重试
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {//如果retainedKeys没有缓存的reference的话,不做操作
return DONE;
}
gcTrigger.runGc();//表示有reference,进行GC操作 重要2
//Runtime.getRuntime().gc(); 发起GC操作
//Thread.sleep(100);//等待 100毫秒
//System.runFinalization();//运行处于挂起终止状态的所有对象的终止方法。
//调用该方法说明 Java 虚拟机做了一些努力运行已被丢弃对象的 finalize 方法,但是这些对象的 finalize 方法至今尚未运行。当控制权从方法调用中返回时,Java 虚拟机已经尽最大努力去完成所有未执行的终止方法。
removeWeaklyReachableReferences();//移除队列里面的值,以及对应的retainedKeys的name
if (!gone(reference)) {//如果这个时候,队列里面的reference被移除了,但是retainedKeys还是包含reference,进行下一步内存泄露分析,如果没有表示被回收了;重要 3
long startDumpHeap = System.nanoTime();//再来一个纳秒
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);//计算耗时
File heapDumpFile = heapDumper.dumpHeap();//生成heap dump文件,并且保存起来;看 AndroidHeapDumper类 重要4
if (heapDumpFile == RETRY_LATER) {//如果是重试,进行重试操作
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);//计算耗时
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));//开始分析,看ServiceHeapDumpListener 类 ->HeapAnalyzerService类 重要5
}
return DONE;
}
重点说明:(这个操作是线程空余状态进行)
重点1:
清空不存在的误差
重点2:
进行GC的操作,并且还是延迟了
重点3:
是利用缓存,以及队列的删除进行判断是否存在内存泄漏
重点4:
AndroidHeapDumper类:
@Override public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();//生成路径
// 看下面的DefaultLeakDirectoryProvider类newHeapDumpFile();生成路径不多说明
if (heapDumpFile == RETRY_LATER) {//如果稍后在进行重试;会返回一个标识
return RETRY_LATER;
}
FutureResult waitingForToast = new FutureResult<>();
showToast(waitingForToast);//先进行弹框出现内存泄露标志,这个也是cpu空闲时,缓存该toast
if (!waitingForToast.wait(5, SECONDS)) {//并发
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
Toast toast = waitingForToast.get();//获取toat
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());//Debug.dumpHprofData 生成heap dump文件,后面是路径;//目标找到了,这个是生成文件
cancelToast(toast);//取消弹窗
return heapDumpFile;//返回写入的路径
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;//失败了重试
}
}
里面生成了真正的dump的对象,以及解析的类
重点5:
ServiceHeapDumpListener 类 ->HeapAnalyzerService类
@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);//输入信息类
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);//TODO 1 检测内存泄漏,以及生成result
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
//TODO 2 DisplayLeakService 也是一样开启一个IntentService处理 onHeapAnalyzed方法里面
}
这个类操作之后,到DisplayLeakService 这个进行最后的数据小处理,以及弹通知操作
下面细讲一下TODO:
TODO 1:
HeapAnalyzer类:这个是我们非常关键的类。因为会涉及到我们判断哪一些不需要写入;不需要生成泄漏文件;所以上面的配置类很重要
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {//确定一下是否是内存泄露
long analysisStartNanoTime = System.nanoTime();//纳秒
if (!heapDumpFile.exists()) {//判断文件是否存在;不存在的话,进行返回失败操作
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);//MemoryMappedFileBuffer 将heapDumpFile传入,生成HprofBuffer,这个到haha的那个库,后续分析
HprofParser parser = new HprofParser(buffer);//转码,也是haha
Snapshot snapshot = parser.parse();//这几个转化后续在看,上面就是将文件转化成可以识别的对象
deduplicateGcRoots(snapshot);//生成GC root快照
Instance leakingRef = findLeakingReference(referenceKey, snapshot);//这边开始查找泄漏Instance
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {//如果没找到,表示没有内存泄漏
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);//有的话,开始继续查找
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
/**
* Pruning duplicates reduces memory pressure from hprof bloat added in Marshmallow.
*/
void deduplicateGcRoots(Snapshot snapshot) {
// THashMap has a smaller memory footprint than HashMap.
final THashMap uniqueRootMap = new THashMap<>();
final List gcRoots = (ArrayList) snapshot.getGCRoots();//获取根的gc root(可达性分析法)
for (RootObj root : gcRoots) {//开始遍历
String key = generateRootKey(root);//转化成字符串
if (!uniqueRootMap.containsKey(key)) {//排重操作,如果没有的话,就加进去
uniqueRootMap.put(key, root);
}
}
// Repopulate snapshot with unique GC roots.
gcRoots.clear();//清空
uniqueRootMap.forEach(new TObjectProcedure() {//haha里面的,这边应该是转化成字符串之类的,方便后续识别
@Override public boolean execute(String key) {
return gcRoots.add(uniqueRootMap.get(key));
}
});
}
private String generateRootKey(RootObj root) {
return String.format("%s@0x%08x", root.getRootType().getName(), root.getId());
}
private Instance findLeakingReference(String key, Snapshot snapshot) {//返回找到的referenceKey的KeyedWeakReference泄漏
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());//看是否包含 KeyedWeakReference这个对象名字(弱引用);对象无法被销毁导致的
List keysFound = new ArrayList<>();
for (Instance instance : refClass.getInstancesList()) {//haha里面,进行遍历
List values = classInstanceValues(instance);//获取 List
String keyCandidate = asString(fieldValue(values, "key"));
if (keyCandidate.equals(key)) {//看一下是否包含该key名称的,即referenceKey,有的话就是找到了该泄漏的
return fieldValue(values, "referent");
}
keysFound.add(keyCandidate);
}
throw new IllegalStateException(
"Could not find weak reference with key " + key + " in " + keysFound);
}
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
Instance leakingRef) {
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);//之前的配置类;
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);//ShortestPathFinder类
// False alarm, no strong reference path to GC Roots.
if (result.leakingNode == null) {
return noLeak(since(analysisStartNanoTime));
}
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
String className = leakingRef.getClassObj().getClassName();
// Side effect: computes retained size.
snapshot.computeDominators();
Instance leakingInstance = result.leakingNode.instance;
long retainedSize = leakingInstance.getTotalRetainedSize();
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}
TODO 2:
DisplayLeakService类,进行发送通知
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d(leakInfo);
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if (shouldSaveResult) {
heapDump = renameHeapdump(heapDump);//生成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);//发送Notification通知
afterDefaultHandling(heapDump, result, leakInfo);
}
以上是我们使用LeakCanary的基本操作流程;所以这些是我需要了解;后续我们再去分析haha这个库