LeakCanary 是检测内存泄漏的工具
LeakCanary使用
引入包 debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0'
就可以检测内存泄漏,不用进行初始化。
只在项目运行中出现,如果想要添加检测其他对象或者查看源码
引入包 implementation 'com.squareup.leakcanary:leakcanary-android:2.0'
可以观察你想观察的任何对象AppWatcher.INSTANCE.getObjectWatcher().watch();
引发的问题 - 为什么添加 debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0'
application中不用初始化就可以检测内存泄漏 ?是在那里进行初始化的?
LeakCanary初始化的原理
在LeakCannary 中的AndroidManifest.xml 中
<provider
android:name="leakcanary.internal.AppWatcherInstaller$LeakCanaryProcess"
android:authorities="${applicationId}.leakcanary-process.installer"
android:process=":leakcanary"
android:exported="false"/>
在 AppWatcherInstaller 类中的onCreate()中调用InternalAppWatcher.install()去初始化LeakCanary。
internal sealed class AppWatcherInstaller : ContentProvider() {
/**
* [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
*/
internal class MainProcess : AppWatcherInstaller()
/**
* When using the `leakcanary-android-process` artifact instead of `leakcanary-android`,
* [LeakCanaryProcess] automatically sets up the LeakCanary code
*/
internal class LeakCanaryProcess : AppWatcherInstaller() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
//provider的onCreate方法中,调用了install方法
InternalAppWatcher.install(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
}
}
fun install(application: Application) {
SharkLog.logger = DefaultCanaryLog()
SharkLog.d { "Installing AppWatcher" }
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalAppWatcher.application = application
val configProvider = { AppWatcher.config }
// 监控activity的内存泄漏
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
// 监控Fragment的内存泄漏
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
onAppWatcherInstalled(application)
}
app 在打包的时候,会merge-Mainfest 引入其他的androidMainfest 这样在自己的项目中就会合并成一个AndroidMainfest.xml 文件。
继续探索,ContentProvider是什么时候被创建的(什么时候调用onCreate函数)?
探索源码,在ActivityThread 中有
private void handleBindApplication(AppBindData data){
...
mInitialApplication = app;
// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
}
}
// Do this after providers, since instrumentation tests generally start their
// test thread at this point, and we don't want that racing.
try {
mInstrumentation.onCreate(data.instrumentationArgs);
}
...
}
ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
...
if (DEBUG_PROVIDER) Slog.v(
TAG, "Instantiating local provider " + info.name);
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
if (!mInstrumentation.onException(null, e)) {
throw new RuntimeException(
"Unable to get provider " + info.name
+ ": " + e.toString(), e);
}
return null;
}
}
...
}
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
mNoPerms = testing;
/*
* Only allow it to be set once, so after the content service gives
* this to us clients can't change it.
*/
if (mContext == null) {
mContext = context;
if (context != null) {
mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
Context.APP_OPS_SERVICE);
}
mMyUid = Process.myUid();
if (info != null) {
setReadPermission(info.readPermission);
setWritePermission(info.writePermission);
setPathPermissions(info.pathPermissions);
mExported = info.exported;
mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
setAuthorities(info.authority);
}
//调用 ContentProvider 的onCreate
ContentProvider.this.onCreate();
}
得到的结论:
LeakCanary在2.0以后的版本中不需要在application完成初始化任务,LeakCanary2.0以后利用了ContentProvider 在 Application 被创建之前被加载的原理,在ContentProvider的onCreate完成了初始化任务。
LeakCanary2.0 用kotlin 写的
LeakCanary 主要有两个配置文件 LeakCanary 和 AppWatcher
/**
* The entry point API for LeakCanary. LeakCanary builds on top of [AppWatcher]. AppWatcher
* notifies LeakCanary of retained instances, which in turns dumps the heap, analyses it and
* publishes the results.
*
* LeakCanary can be configured by updating [config].
*/
object LeakCanary {
/**
* 通过Config 设置LeakCanary的一些配置
*/
data class Config(
/**
* 是否堆转储 默认为true
*
*/
val dumpHeap: Boolean = true,
/**
* If [dumpHeapWhenDebugging] is false then LeakCanary will not dump the heap
* when the debugger is attached. The debugger can create temporary memory leaks (for instance
* if a thread is blocked on a breakpoint).
*
* Defaults to false.
*/
val dumpHeapWhenDebugging: Boolean = false,
/**
* When the app is visible, LeakCanary will wait for at least
* [retainedVisibleThreshold] retained instances before dumping the heap. Dumping the heap
* freezes the UI and can be frustrating for developers who are trying to work. This is
* especially frustrating as the Android Framework has a number of leaks that cannot easily
* be fixed.
*
* When the app becomes invisible, LeakCanary dumps the heap after
* [AppWatcher.Config.watchDurationMillis] ms.
*
* The app is considered visible if it has at least one activity in started state.
*
* A higher threshold means LeakCanary will dump the heap less often, therefore it won't be
* bothering developers as much but it could miss some leaks.
*
* Defaults to 5.
*/
val retainedVisibleThreshold: Int = 5,
/**
* 观察的对象list 也可以自定义观察的对象
*
*/
val referenceMatchers: List<ReferenceMatcher> = AndroidReferenceMatchers.appDefaults,
/**
* List of [ObjectInspector] that provide LeakCanary with insights about objects found in the
* heap. You can create your own [ObjectInspector] implementations, and also add
* a [shark.AppSingletonInspector] instance created with the list of internal singletons.
*
* Defaults to [AndroidObjectInspectors.appDefaults]
*/
val objectInspectors: List<ObjectInspector> = AndroidObjectInspectors.appDefaults,
/**
* Called on a background thread when the heap analysis is complete.
* If you want leaks to be added to the activity that lists leaks, make sure to delegate
* calls to a [DefaultOnHeapAnalyzedListener].
*
* Defaults to [DefaultOnHeapAnalyzedListener]
*/
val onHeapAnalyzedListener: OnHeapAnalyzedListener = DefaultOnHeapAnalyzedListener.create(),
/**
* Extracts metadata from a hprof to be reported in [HeapAnalysisSuccess.metadata].
* Called on a background thread during heap analysis.
*
* Defaults to [AndroidMetadataExtractor]
*/
val metatadaExtractor: MetadataExtractor = AndroidMetadataExtractor,
/**
* Whether to compute the retained heap size, which is the total number of bytes in memory that
* would be reclaimed if the detected leaks didn't happen. This includes native memory
* associated to Java objects (e.g. Android bitmaps).
*
* Computing the retained heap size can slow down the analysis because it requires navigating
* from GC roots through the entire object graph, whereas [shark.HeapAnalyzer] would otherwise
* stop as soon as all leaking instances are found.
*
* Defaults to true.
*/
val computeRetainedHeapSize: Boolean = true,
/**
* How many heap dumps are kept on the Android device for this app package. When this threshold
* is reached LeakCanary deletes the older heap dumps. As several heap dumps may be enqueued
* you should avoid going down to 1 or 2.
*
* Defaults to 7.
*/
val maxStoredHeapDumps: Int = 7,
/**
* LeakCanary always attempts to store heap dumps on the external storage if the
* WRITE_EXTERNAL_STORAGE is already granted, and otherwise uses the app storage.
* If the WRITE_EXTERNAL_STORAGE permission is not granted and
* [requestWriteExternalStoragePermission] is true, then LeakCanary will display a notification
* to ask for that permission.
*
* Defaults to false because that permission notification can be annoying.
*/
val requestWriteExternalStoragePermission: Boolean = false,
/**
* When true, [objectInspectors] are used to find leaks instead of only checking instances
* tracked by [KeyedWeakReference]. This leads to finding more leaks and shorter leak traces.
* However this also means the same leaking instances will be found in every heap dump for a
* given process life.
*
* Defaults to false.
*/
val useExperimentalLeakFinders: Boolean = false
)
/**
* The current LeakCanary configuration. Can be updated at any time, usually by replacing it with
* a mutated copy, e.g.:
*
* ```
* LeakCanary.config = LeakCanary.config.copy(computeRetainedHeapSize = true)
* ```
*/
@Volatile
var config: Config = if (AppWatcher.isInstalled) Config() else InternalLeakCanary.noInstallConfig
set(newConfig) {
val previousConfig = field
field = newConfig
logConfigChange(previousConfig, newConfig)
}
private fun logConfigChange(
previousConfig: Config,
newConfig: Config
) {
SharkLog.d {
val changedFields = mutableListOf<String>()
Config::class.java.declaredFields.forEach { field ->
field.isAccessible = true
val previousValue = field[previousConfig]
val newValue = field[newConfig]
if (previousValue != newValue) {
changedFields += "${field.name}=$newValue"
}
}
"Updated LeakCanary.config: Config(${if (changedFields.isNotEmpty())
changedFields.joinToString(", ") else "no changes"})"
}
}
/**
* Returns a new [Intent] that can be used to programmatically launch the leak display activity.
*/
fun newLeakDisplayActivityIntent() = InternalLeakCanary.leakDisplayActivityIntent
/**
* Dynamically shows / hides the launcher icon for the leak display activity.
* Note: you can change the default value by overriding the leak_canary_add_launcher_icon
* boolean resource:
*
* ```
* <?xml version="1.0" encoding="utf-8"?>
* <resources>
* <bool name="leak_canary_add_launcher_icon">false</bool>
* </resources>
* ```
*/
fun showLeakDisplayActivityLauncherIcon(showLauncherIcon: Boolean) {
InternalLeakCanary.setEnabledBlocking(
"leakcanary.internal.activity.LeakLauncherActivity", showLauncherIcon
)
}
/**
* Immediately triggers a heap dump and analysis, if there is at least one retained instance
* tracked by [AppWatcher.objectWatcher]. If there are no retained instances then the heap will not
* be dumped and a notification will be shown instead.
*/
fun dumpHeap() = InternalLeakCanary.onDumpHeapReceived(forceDump = true)
}
AppWatcher 也有同样的配置文件
object AppWatcher {
data class Config(
/**
* Whether AppWatcher should watch objects (by keeping weak references to them).
*
* Default to true in debuggable builds and false is non debuggable builds.
*/
val enabled: Boolean = InternalAppWatcher.isDebuggableBuild,
/**
* Whether AppWatcher should automatically watch destroyed activity instances.
*
* Defaults to true.
*/
val watchActivities: Boolean = true,
/**
* Whether AppWatcher should automatically watch destroyed fragment instances.
*
* Defaults to true.
*/
val watchFragments: Boolean = true,
/**
* Whether AppWatcher should automatically watch destroyed fragment view instances.
*
* Defaults to true.
*/
val watchFragmentViews: Boolean = true,
/**
* How long to wait before reporting a watched object as retained.
*
* Default to 5 seconds.
*/
val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5)
)
/**
* The current AppWatcher configuration. Can be updated at any time, usually by replacing it with
* a mutated copy, e.g.:
*
* ```
* LeakCanary.config = LeakCanary.config.copy(enabled = false)
* ```
*/
@Volatile
var config: Config = if (isInstalled) Config() else Config(enabled = false)
set(newConfig) {
val previousConfig = field
field = newConfig
logConfigChange(previousConfig, newConfig)
}
private fun logConfigChange(
previousConfig: Config,
newConfig: Config
) {
SharkLog.d {
val changedFields = mutableListOf<String>()
Config::class.java.declaredFields.forEach { field ->
field.isAccessible = true
val previousValue = field[previousConfig]
val newValue = field[newConfig]
if (previousValue != newValue) {
changedFields += "${field.name}=$newValue"
}
}
"Updated AppWatcher.config: Config(${if (changedFields.isNotEmpty())
changedFields.joinToString(", ") else "no changes"})"
}
}
/**
* The [ObjectWatcher] used by AppWatcher to detect retained objects.
*/
val objectWatcher
get() = InternalAppWatcher.objectWatcher
/** @see [manualInstall] */
val isInstalled
get() = InternalAppWatcher.isInstalled
/**
* [AppWatcher] is automatically installed on main process start by
* [leakcanary.internal.AppWatcherInstaller] which is registered in the AndroidManifest.xml of
* your app. If you disabled [leakcanary.internal.AppWatcherInstaller] or you need AppWatcher
* or LeakCanary to run outside of the main process then you can call this method to install
* [AppWatcher].
*/
fun manualInstall(application: Application) = InternalAppWatcher.install(application)
}
LeakCanary检测内存泄漏原理
原理
是利用了WeakRefrence + RefrenceQueue的机制(仅被弱引用持有的对象,当对象被回收时,会存入到引用队列中),从引用队列中不断的获取对象,将已确认被GC的对象剔除,剩余未被回收的对象则定义为可能泄露的对象,当达到一定的判断条件时,通知用户内存泄露
监控activity:
在LeakCanary初始化的时候,主要通过在Application中注册registerActivityLifecycleCallbacks绑定Activity生命周期回调,并在Activity destory时,调用ObjectWatcher的watch方法进行检测
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) {
//activity destory 的时候,对Activity watch
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
}
companion object {
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =ActivityDestroyWatcher(objectWatcher, configProvider)
//在application中注册 ActivityLifecycleCallbacks
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
监控Fragment:
监控AndroidSupportFragmentDestroyWatcher类中,主要通过在Activity中注册FragmentLifecycleCallbacks回调,当触发onFragmentViewDestroyed回调时,通过ObjectWatcher#watch检查fragment.mView对象(即Fragment#onCreateView创建的View)。当触发onFragmentDestroyed回调时,检查fragment对象是否泄漏。
internal class AndroidSupportFragmentDestroyWatcher(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) : (Activity) -> Unit {
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null && configProvider().watchFragmentViews) {
objectWatcher.watch(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
}
}
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
if (configProvider().watchFragments) {
objectWatcher.watch(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
}
override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}
}
}
核心代码类 ObjectWatcher
class ObjectWatcher constructor(
private val clock: Clock,
private val checkRetainedExecutor: Executor,
/**
* References passed to [watch].
* 正在被观察的对象,此时还未泄漏
*/
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
//引用队列 配合弱引用 定位泄漏的对象。
private val queue = ReferenceQueue<Any>()
//泄漏监听
private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()
/**
* Watches the provided [watchedObject].
*
* @param description Describes why the object is watched.
*/
@Synchronized fun watch(watchedObject: Any,description: String) {
if (!isEnabled()) {
return
}
//清除watchedObjects中不存在泄露的弱引用对象
removeWeaklyReachableObjects()
val key = UUID.randomUUID().toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference =KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (description.isNotEmpty()) " ($description)" else "") +
" with key $key"
}
//将本次watch的对象加入到watchedObjects 的map中
watchedObjects[key] = reference
//通过checkRetainedExecutor,5秒后执行moveToRetained
// 5s 后进行
checkRetainedExecutor.execute {
//若依旧存在引用,则通知onObjectRetainedListeners
moveToRetained(key)
}
}
@Synchronized private fun moveToRetained(key: String) {
//清除无需观察的对象
removeWeaklyReachableObjects()
//通过key 去找引用,若存在引用,这证明5s后,本该回收的对象未能回收,可能存在内存泄露
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
private fun removeWeaklyReachableObjects() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
//queue是一个 ReferenceQueue队列,当一个对象被GC掉之后,会被加入到这个队列当中,即queue中存在的对象,都可以认为是不会存在内存泄露的对象
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
}
将要观察的对象放入 watchedObjects 这个map中( key值是生成的UID)
当对象销毁的时候,等待5秒(GC不是及时的,预留时间为5s,等待GC。MVP模式中,P等待网络请求,P持有Activity的引用,Activity销毁后,不能释放。可以手动配置这个时间为10s,10s后P回收,Activity回收,这时可认为长时间内不存在内存泄漏),去ReferenceQueue 中遍历,如果对象被回收,queue队列会存入这个引用,得到这个引用,从watchedObjects 移除。
在Activity 不可见的时候,会判断watchedObjects 保留的对象 如果大于0,会手动GC
在手动gc后发现
(1)如果不存在内存泄漏的对象,则不进行heap dump
(2)如果存在内存泄漏的对象个数小于 val retainedVisibleThreshold: Int = 5,,则不进行heap dump( config 配置 可以设置retainedVisibleThreshold)
就会dumpHeap 然后利用Shark(2.0以前是HAHA进行分析, 分析最短引用路径,进行通知。
在onObjectRetainedListeners.forEach { it.onObjectRetained() }
最终会调用HeapDumpTrigger#checkRetainedObjects
private fun checkRetainedObjects(reason: String) {
val config = configProvider()
// A tick will be rescheduled when this is turned back on.
if (!config.dumpHeap) {
SharkLog.d { "Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
return
}
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
// 执行一次GC
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 如果内存泄漏对象数量在阈值内,不生成dump文件分析
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
onRetainInstanceListener.onEvent(DebuggerIsAttached)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_debugger_attached
)
)
scheduleRetainedObjectCheck(
reason = "debugger is attached",
rescheduling = true,
delayMillis = WAIT_FOR_DEBUG_MILLIS
)
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(
reason = "previous heap dump was ${elapsedSinceLastDumpMillis}ms ago (< ${WAIT_BETWEEN_HEAP_DUMPS_MILLIS}ms)",
rescheduling = true,
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
dismissRetainedCountNotification()
// 创建dump文件,创建通知提示dump
dumpHeap(retainedReferenceCount, retry = true)
}