简介
- LeakCanary是Square公司研发的一个可视化的内存泄漏分析工具
LeakCanary2.x
- 完全使用Kotlin重写;
- 使用新的Heap分析工具Shark,替换了之前的haha,按官方的说法,内存占用减少了10倍
- 泄露类型分组
使用
添加依赖
- 最新的LeakCanary只需引入依赖,不需要初始化代码,就能执行内存泄漏检测;
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
如何确定内存泄露的对象
- 原理是我们常用的 WeakReference,其参数构造函数支持传入一个 ReferenceQueue,当其关联的对象回收时,会将 WeakReference 加入 ReferenceQueue 中。LeakCanary 的做法是继承 ReferenceQueue,增加一个值为 UUID 的属性 key,同时将每个需要监测的对象 WeakReference 以此 UUID 作为键加入一个 map 中。这样,在 GC 过后,removeWeaklyReachableObjects 方法通过遍历 ReferenceQueue,通过 key 值删除 map 中已回收的对象,剩下的对象就基本可以确定发生了内存泄露。
为什么 LeakCanary 不能用于线上
- 每次内存泄漏以后,都会生成并解析 hprof 文件,容易引起手机卡顿等问题
- 多次调用 GC,可能会对线上性能产生影响
- hprof 文件较大,信息回捞成问题
源码解析
AppWatcherInstaller
- 为什么只需添加依赖,不用在Application中初始化了呢?
- 我在之前的文章Android Jetpack系列--5. App Startup使用详解有详细介绍过借助ContentProvider自动调用初始化接口,从而避免显示的初始化;
- 我们来看看AppWatcherInstaller的代码,果然是通过继承ContentProvider来获取应用的 Context,并在 onCreate 中执行初始化逻辑;
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
。。。
}
- 注册在Manifest文件中的ContentProvider会在应用启动时,由ActivityThread创建并初始化;
- 哎?AppWatcherInstaller后面怎么还有个$MainProcess,通过看样子是个内部类呢,回到AppWatcherInstaller中发现原来有两个子类
/**
* 使用 leakcanary-android 模块时,会默认使用这个,表示在当前 App 进程中使用
*/
internal class MainProcess : AppWatcherInstaller()
/**
* 当使用 leakcanary-android-process 模块代替 leakcanary-android 模块时,则会使用这个类
*/
internal class LeakCanaryProcess : AppWatcherInstaller()
AppWatcher
- 上面也说了LeakCanary是通过AppWatcherInstaller继承ContentProvider获取应用的Context,并在onCreate中初始化, 那么我们就来看看onCreate中调用的唯一的初始化方法AppWatcher.manualInstall做了些什么吧
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List = 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 {
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对象加到观察列表,
- 那么这个reachabilityWatcher又是什么呢?是通过appDefaultWatchers传过来的,appDefaultWatchers的方法入参有给他设置一个默认值objectWatcher,代码如下
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true }
)
- 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)
//添加到集合中
watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
- 可以看到最终是封装成一个KeyedWeakReference对象并添加到集合中,它继承于 WeakReference,弱引用是不会阻止 GC 回收对象的,同时可以在构造函数中传递一个 ReferenceQueue,用于对象被 GC 后存放的队列。
- 上面代码的最后通过checkRetainedExecutor.execute执行了moveToRetained,checkRetainedExecutor最终是通过mainHandler.postDelayed执行的moveToRetained,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() }
}
}
InternalLeakCanary
- 上面代码中的onObjectRetainedListeners是哪来的呢?还得回到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()
}
}
- 其中heapDumpTrigger在上面的invoke方法中有初始化,代码如下
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
)
- GcTrigger 通过调用 Runtime.getRuntime().gc() 方法触发虚拟机进行 GC 操作
interface GcTrigger {
fun runGc()
object Default : GcTrigger {
override fun runGc() {
Runtime.getRuntime()
.gc()
enqueueReferences()
System.runFinalization()
}
private fun enqueueReferences() {
try {
Thread.sleep(100)
} catch (e: InterruptedException) {
throw AssertionError()
}
}
}
}
- AndroidHeapDumper的dumpHeap方法通过调用 Debug.dumpHprofData() 方法从虚拟机中 dump hprof 文件,代码如下
internal class AndroidHeapDumper(
context: Context,
private val leakDirectoryProvider: LeakDirectoryProvider
) : HeapDumper {
private val context: Context = context.applicationContext
override fun dumpHeap(): DumpHeapResult {
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump
val waitingForToast = FutureResult()
showToast(waitingForToast)
if (!waitingForToast.wait(5, SECONDS)) {
SharkLog.d { "Did not dump heap, too much time waiting for Toast." }
return NoHeapDump
}
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Notifications.canShowNotification) {
val dumpingHeap = context.getString(R.string.leak_canary_notification_dumping)
val builder = Notification.Builder(context)
.setContentTitle(dumpingHeap)
val notification = Notifications.buildNotification(context, builder, LEAKCANARY_LOW)
notificationManager.notify(R.id.leak_canary_notification_dumping_heap, notification)
}
val toast = waitingForToast.get()
return try {
val durationMillis = measureDurationMillis {
Debug.dumpHprofData(heapDumpFile.absolutePath)
}
if (heapDumpFile.length() == 0L) {
SharkLog.d { "Dumped heap file is 0 byte length" }
NoHeapDump
} else {
HeapDump(file = heapDumpFile, durationMillis = durationMillis)
}
} catch (e: Exception) {
SharkLog.d(e) { "Could not dump heap" }
// Abort heap dump
NoHeapDump
} finally {
cancelToast(toast)
notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
}
}
...
}
- 其中使用的是 android.os.Debug 类的 Debug.dumpHprofData(heapDumpFile.absolutePath),导出一个Hprof文件;
- AndroidHeapDumper的dumpHeap方法会在HeapDumpTrigger.dumpHeap中作为when语句块的条件调用,代码如下
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
when (val heapDumpResult = heapDumper.dumpHeap()) {
is NoHeapDump -> {
if (retry) {
SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
scheduleRetainedObjectCheck(
delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
)
} else {
SharkLog.d { "Failed to dump heap, will not automatically retry" }
}
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_dump_failed
)
)
}
is HeapDump -> {
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}
- 如果AndroidHeapDumper.dumpHeap的结果是HeapDump,会将结果转交给HeapAnalyzerService完成结果展示
HeapAnalyzerService
- 那么来看一下HeapAnalyzerService.runAnalysis的实现,是启动了一个前台的service
fun runAnalysis(
context: Context,
heapDumpFile: File,
heapDumpDurationMillis: Long? = null,
heapDumpReason: String = "Unknown"
) {
val intent = Intent(context, HeapAnalyzerService::class.java)
intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
intent.putExtra(HEAPDUMP_REASON_EXTRA, heapDumpReason)
heapDumpDurationMillis?.let {
intent.putExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, heapDumpDurationMillis)
}
startForegroundService(context, intent)
}
- HeapAnalyzerService继承了ForegroundService,ForegroundService又继承自IntentService,既然归根结底是IntentService,那么就要看看onHandleIntent方法是如何处理任务的
override fun onHandleIntent(intent: Intent?) {
onHandleIntentInForeground(intent)
}
- ForegroundService.onHandleIntent中调用了抽象方法onHandleIntentInForeground,而HeapAnalyzerService中提供了具体的实现,代码如下
override fun onHandleIntentInForeground(intent: Intent?) {
if (intent == null || !intent.hasExtra(HEAPDUMP_FILE_EXTRA)) {
SharkLog.d { "HeapAnalyzerService received a null or empty intent, ignoring." }
return
}
// Since we're running in the main process we should be careful not to impact it.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
val heapDumpReason = intent.getStringExtra(HEAPDUMP_REASON_EXTRA)
val heapDumpDurationMillis = intent.getLongExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, -1)
val config = LeakCanary.config
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
val fullHeapAnalysis = when (heapAnalysis) {
is HeapAnalysisSuccess -> heapAnalysis.copy(
dumpDurationMillis = heapDumpDurationMillis,
metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)
)
is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
}
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
}
- 其中调用了analyzeHeap方法,其中HeapAnalyzer使用Shark来解析heapDumpFile
private fun analyzeHeap(
heapDumpFile: File,
config: Config
): HeapAnalysis {
val heapAnalyzer = HeapAnalyzer(this)
val proguardMappingReader = try {
ProguardMappingReader(assets.open(PROGUARD_MAPPING_FILE_NAME))
} catch (e: IOException) {
null
}
return heapAnalyzer.analyze(
heapDumpFile = heapDumpFile,
leakingObjectFinder = config.leakingObjectFinder,
referenceMatchers = config.referenceMatchers,
computeRetainedHeapSize = config.computeRetainedHeapSize,
objectInspectors = config.objectInspectors,
metadataExtractor = config.metadataExtractor,
proguardMapping = proguardMappingReader?.readProguardMapping()
)
}
- onHandleIntentInForeground方法的最后通过config.onHeapAnalyzedListener .onHeapAnalyzed(fullHeapAnalysis)触发相关通知显示以及点击后的 LeakActivity 数据展示。
//LeakCanary.Config
val onHeapAnalyzedListener: OnHeapAnalyzedListener = DefaultOnHeapAnalyzedListener.create(),
//DefaultOnHeapAnalyzedListener
companion object {
fun create(): OnHeapAnalyzedListener =
DefaultOnHeapAnalyzedListener { InternalLeakCanary.application }
}
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(heapAnalysis.toString(), 120)}" }
val db = LeaksDbHelper(application).writableDatabase
val id = HeapAnalysisTable.insert(db, heapAnalysis)
db.releaseReference()
val (contentTitle, screenToShow) = when (heapAnalysis) {
is HeapAnalysisFailure -> application.getString(
R.string.leak_canary_analysis_failed
) to HeapAnalysisFailureScreen(id)
is HeapAnalysisSuccess -> {
val retainedObjectCount = heapAnalysis.allLeaks.sumBy { it.leakTraces.size }
val leakTypeCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
application.getString(
R.string.leak_canary_analysis_success_notification, retainedObjectCount, leakTypeCount
) to HeapDumpScreen(id)
}
}
if (InternalLeakCanary.formFactor == TV) {
showToast(heapAnalysis)
printIntentInfo()
} else {
showNotification(screenToShow, contentTitle)
}
}
private fun showNotification(
screenToShow: Screen,
contentTitle: String
) {
val pendingIntent = LeakActivity.createPendingIntent(
application, arrayListOf(HeapDumpsScreen(), screenToShow)
)
val contentText = application.getString(R.string.leak_canary_notification_message)
Notifications.showNotification(
application, contentTitle, contentText, pendingIntent,
R.id.leak_canary_notification_analysis_result,
LEAKCANARY_MAX
)
}
参考
- LeakCanary源码
- LeakCanary 内存泄漏原理完全解析
- Android 内存泄露工具 leakcanary 2.6 浅析
- LeakCanary2 源码分析
我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章