简介
LeakCanary是Square公司研发的一个可视化的内存泄漏分析工具
使用
添加依赖
最新的LeakCanary只需引入依赖,不需要初始化代码,就能执行内存泄漏检测;
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
运行效果
Demo
这里使用了一个简单的内存泄露Demo来展示LeakCanary的运行效果
MainActivity:
class MainActivity : AppCompatActivity() {
private var binding: ActivityMainBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view : View = binding!!.root
setContentView(view)
binding!!.mianButton.setOnClickListener(View.OnClickListener {
startActivity(Intent(this,TestActivity::class.java))
})
}
}
TestActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTestBinding.inflate(layoutInflater)
val view : View = binding!!.root
setContentView(view)
TestDataModel.get().activityList.add(this)
}
TestDataModel:
class TestDataModel {
val activityList = mutableListOf<Activity>()
companion object{
private var instance: TestDataModel? = null
get() {
if (field == null) {
field = TestDataModel()
}
return field
}
fun get(): TestDataModel{
return instance!!
}
}
}
这里用了一个非常简单的Demo,由MainActivity跳转转到TestActivity,在TestActivity中使用一个单例保存了TestActivity对象,再返回MainActivity,跳到TestActivity,由于单例的生命周期比Activity的声明周期更长,单例持有Activity的引用,Activity在ondestory后无法被回收,导致了Activity的泄露
日志
- 在日志上出现:LeakCanary is running and ready to detect memory leaks.这一句话时,表示LeakCanary已经集成成功,
- 在日志上出现:Found 3 objects retained, not dumping heap yet (app is visible & < 5 threshold).这一句话时,表示已经发现了3个保留对象,但是没有dumping heap,因为app可见时的阈值是5,这里补充一点,应用不可时的阈值是1,也就是如果将应用切换到后台(例如点击hong键),APP将立即进行dumping heap
手机显示
出现内存泄漏时,手机会出现LeakCanary的小鸟图标,同时通知栏会提示,点击通知栏进入详情,可以看到GC Root可达链路,看到最下面,TestActivity instance Leaking:YES,表示TestActivity发生内存泄露,往上看,Object数组 -> ArrayList -> TestDataModel.activityList-> TestDataModel.instance,此处就可以发现,TestActivity被一个单例所引用导致了泄露
LeaKCanary2与LeaKCanary1的区别
- LeakCanary2只需要Gradle依赖就可以使用,LeakCanary1还需要进行手动安:LeakCanary.install(this);
- LeakCanary2使用Kotlin进行重构
- Leakcanary2中使用Shark来解析hprof文件,相比于Leakcanary1使用的HaHa快8倍,并且内存占用还要少 10 倍,但查找泄漏路径的大致步骤无异
LeakCanary造成卡顿
源码分析
初始化
<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false"/>
</application>
leakcanary从2.0版本开始就不需要手动初始化了,其主要是通过ContentProvider来实现免初始化:
internal sealed class AppWatcherInstaller : ContentProvider() {
internal class MainProcess : AppWatcherInstaller()
internal class LeakCanaryProcess : AppWatcherInstaller()
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
override fun query(uri: Uri,strings: Array<String>?,s: String?,
strings1: Array<String>?,s1: String?): Cursor? {
return null
}
override fun getType(uri: Uri): String? {
return null
}
override fun insert(uri: Uri,contentValues: ContentValues?): Uri? {
return null
}
override fun delete(uri: Uri,s: String?,strings: Array<String>?): Int {
return 0
}
override fun update(uri: Uri,contentValues: ContentValues?,
s: String?,strings: Array<String>?): Int {
return 0
}
}
当然也可以通过修改leak_canary_watcher_auto_install属性来实现手动初始化:
<?xml version="1.0" encoding="utf-8"?>
<resources> <bool name="leak_canary_watcher_auto_install">false</bool>
</resources>
AppWatcher.manualInstall(this)
再看看AppWatcherInstaller中主要做了什么,AppWatcherInstaller继承至ContentProvider,但是我们可以看到query,getType,insert,delete,update等方法都是空实现,也就是这个类只是使用了ContentProvider特性,具体工作只是在onCreate中调用了AppWatcher.manualInstall(application)方法
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
}
}
mInstrumentation.callApplicationOnCreate(app);
在ActivityThread中的handleBindApplication方法中,可以看到启动app的时候ContentProvider是比Application的onCreate更早,以此是实现了leakcanary的自动初始化,至于Provide写在了LeakCanary的ManiFest文件中,如何被自己的应用是所使用,参考官方的清单文件管理:管理清单文件 | Android 开发者 | Android Developers
对象监听
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
//检查是否在主线程中
checkMainThread()
//如果已经初始化过则抛异常
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
//检查参数,保存延迟时间应大于零,默认是5秒
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
installCause = RuntimeException("manualInstall() first called here")
this.retainedDelayMillis = retainedDelayMillis
//测试模式开启log
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// 通过反射获取InternalLeakCanary单例对象
LeakCanaryDelegate.loadLeakCanary(application)
//注册观察对象
watchersToInstall.forEach {
it.install()
}
}
上面最重要的就是调用watchersToInstall.forEach进行观察对象的注册,其中watchersToInstall在方法入参中设有默认值为appDefaultWatchers(application):
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
//默认初始化以下四个监听器
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
appDefaultWatchers中默认设置了四个监听器, 可以监听包括Activity,Fragment, ViewModel, RootView, Service; 以ActivityWatcher为例:
class ActivityWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
通过调用registerActivityLifecycleCallbacks注册Activity 生命周期回调的监听,并在onActivityDestroyed回调中,通过调用reachabilityWatcher.expectWeaklyReachable将每个Activity对象加到观察列表,
Tips:此处by noOpDelegate()是使用接口代理的模式,只需要重写所需要用到的方法,避免产生大量的空方法
objectWatcher是一个ObjectWatcher的对象,那么就去这个类里面看看它的expectWeaklyReachable方法的实现吧
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
//将已经被 GC 的对象从 watchedObjects 集合中删除
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
//封装成一个KeyedWeakReference对象
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
//添加到集合中,此时,reference的key与watchedObjects中reference所对应的key一致
watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
可以看到最终是封装成一个KeyedWeakReference对象并添加到集合中,它继承于 WeakReference,弱引用是不会阻止 GC 回收对象的,同时可以在构造函数中传递一个 ReferenceQueue,用于对象被 GC 后存放的队列。再通过线程池执行moveToRetained方法
Tips:软引用、弱引用、虚引用的构造方法均可以传入一个ReferenceQueue与之关联。在引用所指的对象被回收后,引用本身将会被加入到ReferenceQueue之中
首先看看removeWeaklyReachableObjects()
private fun removeWeaklyReachableObjects() {
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
将已被回收的对象从观察对象列表中清除:从queue中取出已经被回收的引用,根据key将引用从观察对象列表中删除
再看看线程池
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true }
)
线程池在objectWatcher对象创建时初始化,用来延时执行moveToRetained方法,默认延时5秒
最后再看看moveToRetained
@Synchronized private fun moveToRetained(key: String) {
//删除已经 GC 的对象
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
//剩下的对象就可以认为是被保留(没办法 GC)的对象,回调通知事件
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
回到AppWatcher.manualInstall方法中,其中调用watchersToInstall.forEach之前,调用了LeakCanaryDelegate.loadLeakCanary(application)通过反射获取了InternalLeakCanary单例对象:
@Suppress("UNCHECKED_CAST")
val loadLeakCanary by lazy {
try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}
上面代码最终会调用实际会调用InternalLeakCanary.invoke()方法:
override fun invoke(application: Application) {
_application = application
checkRunningInDebuggableBuild()
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
addDynamicShortcut(application)
// We post so that the log happens after Application.onCreate()
mainHandler.post {
// https://github.com/square/leakcanary/issues/1981
// We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
// which blocks until loaded and that creates a StrictMode violation.
backgroundHandler.post {
SharkLog.d {
when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
is Nope -> application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
}
}
}
此方法主要是完成一些对象的初始化。
调用AppWatcher.objectWatcher.addOnObjectRetainedListener(this)添加一个监听,这里入参用的是this,也就是InternalLeakCanary,那么来看一下InternalLeakCanary中对OnObjectRetainedListener的onObjectRetained方法的实现
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
这里比较简单如果heapDumpTrigger已经初始化完成,就调用scheduleRetainedObjectCheck,scheduleRetainedObjectCheck中进行延时执行checkRetainedObjects,默认不延时,如果上一次没有执行完,不进行下一次执行,主要处理逻辑还是放在checkRetainedObjects中
private fun checkRetainedObjects() {
val config = configProvider()
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
onRetainInstanceListener.onEvent(DumpHappenedRecently)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
)
scheduleRetainedObjectCheck(
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
- 后台线程轮询当前还存活着的对象
- 如果存活的对象大于0,那就触发一次GC操作,回收掉没有泄露的对象
- GC完后,仍然存活着的对象数和预定的对象数相比较,如果多了就调用heapDumper.dumpHeap()方法把对象dump成文件,并交给HeapAnalyzerService去分析
- 根据存活情况展示通知
后续dump相关我就不细讲了
总结
最后借用前辈一张图作为总结