leakcanary小实验
调研leakcanary参考链接如下:
https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
https://github.com/square/leakcanary
leakcanary是一个android内存泄漏监测工具,原理利用了弱引用队列。工作机制如下:
工作流程
1.RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
2.然后在后台线程检查引用是否被清除,如果没有,调用GC。
3.如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
4.在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
5.得益于唯一的 reference key, HeapAnalyzer 找到KeyedWeakReference,定位内存泄露。
6.HeapAnalyzer 计算 到 GC roots的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
7.引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。
在使用过程中配置你的build.gradle(注意是你的app的不是全局的)
dependencies{
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
}
实验代码如下:
静态对象引发的内存泄漏(自定义对象的监测)
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.squareup.leakcanary.*;
class Cat {
String name;
Cat(String _name){
name=_name;
}
}
class Box {
Cat hiddenCat;
}
class schrodinger{
static Box hisbox;
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Box box = new Box();
Cat schrodingerCat = new Cat("caffe");
box.hiddenCat = schrodingerCat;
schrodinger.hisbox = box;
RefWatcher ref = myapplication.getRefWatcher(this);
ref.watch(schrodingerCat);
}
}
leakcanary中部分代码分析:
watch方法:
@Synchronized fun watch(
watchedReference: Any,
referenceName: String
) {
removeWeaklyReachableReferences()
//移除弱引用
val key = UUID.randomUUID()
.toString()
//新观察对象的唯一的key
val watchUptimeMillis = clock.uptimeMillis()
//当前时间
val reference =
KeyedWeakReference(watchedReference, key, referenceName, watchUptimeMillis, queue)
//建立弱引用
if (referenceName != "") {
CanaryLog.d(
"Watching instance of %s named %s with key %s", reference.className,
referenceName, key
)
} else {
CanaryLog.d(
"Watching instance of %s with key %s", reference.className, key
)
}
watchedReferences[key] = reference
//一个 map 可以用 key 关联到 弱引用
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
泄漏监测:
fun detectLeaks(): Result {
val leakDetectionTime = SystemClock.uptimeMillis()
val watchDurationMillis = LeakSentry.config.watchDurationMillis
val instrumentation = getInstrumentation()
val context = instrumentation.targetContext
val refWatcher = LeakSentry.refWatcher
if (!refWatcher.hasWatchedReferences) {
return NoAnalysis
}
instrumentation.waitForIdleSync()
if (!refWatcher.hasWatchedReferences) {
return NoAnalysis
}
runGc()//第一次GC
if (!refWatcher.hasWatchedReferences) {
return NoAnalysis
}
// Waiting for any delayed UI post (e.g. scroll) to clear. This shouldn't be needed, but
// Android simply has way too many delayed posts that aren't canceled when views are detached.
SystemClock.sleep(2000)
if (!refWatcher.hasWatchedReferences) {
return NoAnalysis
}
// Aaand we wait some more.
// 4 seconds (2+2) is greater than the 3 seconds delay for
// FINISH_TOKEN in android.widget.Filter
SystemClock.sleep(2000)
val endOfWatchDelay = watchDurationMillis - (SystemClock.uptimeMillis() - leakDetectionTime)
if (endOfWatchDelay > 0) {
SystemClock.sleep(endOfWatchDelay)
}
runGc()//第二次GC
if (!refWatcher.hasRetainedReferences) {
return NoAnalysis
}
//开始dump...后续省略
}