對於 Android Developer 來講,不少開源庫都是屬於開發必備的知識點,從使用方式到實現原理再到源碼解析,這些都須要咱們有必定程度的瞭解和運用能力。因此我打算來寫一系列關於開源庫源碼解析和實戰演練的文章,初定的目標是 EventBus、ARouter、LeakCanary、Retrofit、Glide、OkHttp、Coil 等七個知名開源庫,但願對你有所幫助 😇😇java
公衆號:字節數組android
系列文章導航:git
LeakCanary 是由 Square 公司開源的用於 Android 的內存泄漏檢測工具,能夠幫助開發者發現內存泄露狀況而且找出泄露源頭,有助於減小 OutOfMemoryError
狀況的發生。在目前的應用開發中也算做是性能優化的一個重要實現途徑,不少面試官在考察性能優化時都會問到 LeakCanary 的實現原理github
本文就基於其當前(2020/10/06)的最新一次提交來進行源碼分析,具體的 Git 版本節點是:9f62126e,來了解 LeakCanary 的總體運行流程和實現原理 😂😂面試
咱們常常說 LeakCanary 能檢測到應用內發生的內存泄露,那麼它到底具體支持什麼類型的內存泄露狀況呢?LeakCanary 官網有對此進行介紹:數組
LeakCanary automatically detects leaks of the following objects:性能優化
Activity
instancesFragment
instancesView
instancesViewModel
instances咱們也能夠從 LeakCanary 的 AppWatcher.Config
這個類找到答案。Config 類用於配置是否開啓內存檢測,從其配置項就能夠看出來 leakcanary 支持:Activity、Fragment、FragmentView、ViewModel 等四種類型markdown
data class Config(
/** * 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,
/** * Whether AppWatcher should automatically watch cleared [androidx.lifecycle.ViewModel] * instances. * * Defaults to true. */
val watchViewModels: Boolean = true,
/** * How long to wait before reporting a watched object as retained. * * Default to 5 seconds. */
val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5),
/** * Deprecated, this didn't need to be a part of the API. * Used to indicate whether AppWatcher should watch objects (by keeping weak references to * them). Currently a no-op. * * If you do need to stop watching objects, simply don't pass them to [objectWatcher]. */
@Deprecated("This didn't need to be a part of LeakCanary's API. No-Op.")
val enabled: Boolean = true
)
複製代碼
現在,咱們在項目中引入 LeakCanary 只須要添加以下依賴便可,無須任何的初始化行爲等附加操做,當應用啓動時 LeakCanary 就會自動啓動並開始監測了app
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
}
複製代碼
通常來講,像這類第三方庫都是須要由外部傳入一個 ApplicationContext
對象進行初始化並啓動的,LeakCanary 的 1.x 版本也是如此,但在 2.x 版本中,LeakCanary 將初始過程交由 AppWatcherInstaller
這個 ContentProvider
來自動完成dom
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
AppWatcher.manualInstall(application)
return true
}
···
}
複製代碼
因爲 ContentProvider
會在 Application
被建立以前就由系統調用其 onCreate()
方法來完成初始化,因此 LeakCanary 經過 AppWatcherInstaller
就能夠拿到 Context
來完成初始化並隨應用一塊兒啓動,經過這種方式就簡化了使用者的引入成本。並且因爲咱們的引用方式是 debugImplementation
,因此正式版本會自動移除對 LeakCanary 的全部引用,進一步簡化了引入成本
Jetpack 也包含了一個組件來實現經過 ContentProvider 來完成初始化的邏輯:AppStartup。在實現思路上二者很相似,可是若是每一個三方庫都經過自定義 ContentProvider
來實現初始化的話,那麼應用的啓動速度就會很感人了吧 :joy:,因此 Jetpack 官方推出的 AppStartup
應該是之後的主流纔對
AppWatcherInstaller
最終會將 Application
對象傳給 InternalAppWatcher
的 install(Application)
方法
/** * Note: this object must load fine in a JUnit environment */
internal object InternalAppWatcher {
···
val objectWatcher = ObjectWatcher(
clock = clock,
checkRetainedExecutor = checkRetainedExecutor,
isEnabled = { true }
)
fun install(application: Application) {
//檢測是否在 main 線程
checkMainThread()
//避免重複初始化
if (this::application.isInitialized) {
return
}
InternalAppWatcher.application = application
if (isDebuggableBuild) {
SharkLog.logger = DefaultCanaryLog()
}
//拿到默認配置,默認四種類型都進行檢測
val configProvider = { AppWatcher.config }
//檢測 Activity
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
//檢測 Fragment、FragmentView、ViewModel
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
onAppWatcherInstalled(application)
}
···
}
複製代碼
LeakCanary 具體進行內存泄露檢測的邏輯能夠分爲三類:
當中,ActivityDestroyWatcher
和 FragmentDestroyWatcher
都須要依靠 ObjectWatcher
來完成,由於 Activity、Fragment、FragmentView、ViewModel
本質上都屬於不一樣類型的 Object
咱們知道,當一個對象再也不被咱們引用時,若是該對象因爲代碼錯誤或者其它緣由致使遲遲沒法被系統回收,此時就是發生了內存泄露。那麼 LeakCanary 是怎麼知道應用是否發生了內存泄露呢?
這個能夠依靠引用隊列 ReferenceQueue
來實現。先來看個小例子
/** * 做者:leavesC * 時間:2020/10/06 14:26 * 描述: * GitHub:https://github.com/leavesC */
fun main() {
val referenceQueue = ReferenceQueue<Pair<String, Int>?>()
var pair: Pair<String, Int>? = Pair("name", 24)
val weakReference = WeakReference(pair, referenceQueue)
println(referenceQueue.poll()) //null
pair = null
System.gc()
//GC 後休眠一段時間,等待 pair 被回收
Thread.sleep(4000)
println(referenceQueue.poll()) //java.lang.ref.WeakReference@d716361
}
複製代碼
能夠看到,在 GC 事後 referenceQueue.poll()
的返回值變成了非 null,這是因爲 WeakReference
和 ReferenceQueue
的一個組合特性致使的:在聲明一個 WeakReference
對象時若是同時傳入了 ReferenceQueue
做爲構造參數的話,那麼當 WeakReference
持有的對象被 GC 回收時,JVM 就會把這個弱引用存入與之關聯的引用隊列之中。依靠這個特性,咱們就能夠實現內存泄露的檢測了
例如,當用戶按返回鍵退出 Activity 時,正常狀況下該 Activity 對象應該在不久後就被系統回收,咱們能夠監聽 Activity 的 onDestroy
回調,在回調時把 Activity 對象保存到和 ReferenceQueue
關聯的 WeakReference
中,在一段時間後(能夠主動觸發幾回 GC)檢測 ReferenceQueue
中是否有值,若是一直爲 null 的話就說明發生了內存泄露。LeakCanary 就是經過這種方法來實現的
ObjectWatcher
中就封裝了上述邏輯,這裏來看看其實現邏輯
ObjectWatcher
的起始方法是 watch(Any, String)
,該方法就用於監聽指定對象
/** * References passed to [watch]. * 用於保存要監聽的對象,mapKey 是該對象的惟一標識、mapValue 是該對象的弱引用 */
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
//KeyedWeakReference 關聯的引用隊列
private val queue = ReferenceQueue<Any>()
/** * Watches the provided [watchedObject]. * * @param description Describes why the object is watched. */
@Synchronized
fun watch(watchedObject: Any, description: String) {
if (!isEnabled()) {
return
}
removeWeaklyReachableObjects()
//爲 watchedObject 生成一個惟一標識
val key = UUID.randomUUID().toString()
val watchUptimeMillis = clock.uptimeMillis()
//建立 watchedObject 關聯的弱引用
val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
···
watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
複製代碼
watch()
方法的主要邏輯:
watchedObject
生成一個惟一標識 key,經過該 key 構建一個 watchedObject
的弱引用 KeyedWeakReference
,將該弱引用保存到 watchedObjects
中。ObjectWatcher
能夠前後監測多個對象,每一個對象都會先被存入到 watchedObjects
中checkRetainedExecutor
來指定檢測內存泄露的觸發時機,經過 moveToRetained
方法來判斷是否真的發生了內存泄露KeyedWeakReference
是一個自定義的 WeakReference
子類,包含一個惟一 key 來標識特定對象,也包含一個 retainedUptimeMillis
字段用來標記是否發生了內存泄露
class KeyedWeakReference(
referent: Any,
val key: String,
val description: String,
val watchUptimeMillis: Long,
referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
referent, referenceQueue
) {
/** * Time at which the associated object ([referent]) was considered retained, or -1 if it hasn't * been yet. * 用於標記 referent 是否還未被回收,是的話則值不爲 -1 */
@Volatile
var retainedUptimeMillis = -1L
companion object {
@Volatile
@JvmStatic
var heapDumpUptimeMillis = 0L
}
}
複製代碼
moveToRetained
方法就用於判斷指定 key 關聯的對象是否已經泄露,若是沒有泄露則移除對該對象的弱引用,有泄露的話則更新其 retainedUptimeMillis
值,以此來標記其發生了泄露,並同時經過回調 onObjectRetainedListeners
來分析內存泄露鏈
@Synchronized
private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
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.
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
//若是 ref 不爲 null,說明 ref 關聯的對象沒有發生內存泄露,那麼就移除對該對象的引用
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
複製代碼
理解了 ObjectWatcher
的流程後來看 ActivityDestroyWatcher
就會比較簡單了。ActivityDestroyWatcher
會向 Application
註冊一個 ActivityLifecycleCallbacks
回調,當收到每一個 Activity 執行了 onDestroy
的回調後,就會將將 Activity 對象轉交由 ObjectWatcher
來進行監聽
internal class ActivityDestroyWatcher private constructor(private val objectWatcher: ObjectWatcher, private val configProvider: () -> Config) {
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.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
複製代碼
作 Android 應用開發的應該都知道,如今 Google 提供的基礎依賴包分爲了 Support 和 AndroidX 兩種,Support 版本已經再也不維護,主流的都是使用 AndroidX 了。而 LeakCanary 爲了照顧老項目,就貼心的爲這兩種版本分別提供了 Fragment 的內存檢測功能
FragmentDestroyWatcher
能夠看作是一個分發器,它會根據外部環境的不一樣來選擇不一樣的檢測手段,其主要邏輯是:
這裏令我很疑惑的一個點就是:當系統版本大於等於 8.0 時,AndroidOFragmentDestroyWatcher 不就會和 AndroidSupportFragmentDestroyWatcher 或者 AndroidXFragmentDestroyWatcher 重複了嗎?這算咋回事:joy:
internal object FragmentDestroyWatcher {
private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidXFragmentDestroyWatcher"
// Using a string builder to prevent Jetifier from changing this string to Android X Fragment
@Suppress("VariableNaming", "PropertyName")
private val ANDROID_SUPPORT_FRAGMENT_CLASS_NAME =
StringBuilder("android.").append("support.v4.app.Fragment")
.toString()
private const val ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
"leakcanary.internal.AndroidSupportFragmentDestroyWatcher"
fun install( application: Application, objectWatcher: ObjectWatcher, configProvider: () -> AppWatcher.Config ) {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
)
}
//AndroidX
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}
//Support
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}
if (fragmentDestroyWatchers.size == 0) {
return
}
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) {
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
})
}
···
}
複製代碼
因爲 AndroidXFragmentDestroyWatcher
、AndroidSupportFragmentDestroyWatcher
、AndroidOFragmentDestroyWatcher
在邏輯上很相似,且就 AndroidXFragmentDestroyWatcher
同時提供了 ViewModel
內存泄露的檢測功能,因此這裏只看 AndroidXFragmentDestroyWatcher
就行
AndroidXFragmentDestroyWatcher
的主要邏輯是:
internal class AndroidXFragmentDestroyWatcher(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) : (Activity) -> Unit {
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentCreated( fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle? ) {
ViewModelClearedWatcher.install(fragment, objectWatcher, configProvider)
}
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)
ViewModelClearedWatcher.install(activity, objectWatcher, configProvider)
}
}
}
複製代碼
Fragment 和 FragmentView 走向 Destroyed
時,正常狀況下它們都是不會被複用的,應該會很快就被 GC 回收,且它們本質上都只是一種對象,因此直接使用 ObjectWatcher
進行檢測便可
和 Fragment、FragmentView 相比,ViewModel 就比較特殊了,因爲可能存在一個 Activity 和多個 Fragment 同時持有一個 ViewModel 實例的狀況,而 leakcanary 沒法知道 ViewModel 究竟是同時被幾個持有者所持有,因此沒法經過單獨一個 Activity 和 Fragment 的 Destroyed
回調來啓動對 ViewModel 的檢測。幸虧 ViewMode 也提供了 onCleared()
的回調事件,leakcanary 就經過該回調來知道 ViewModel 是何時須要被回收。對 ViewModel 的實現原理不清楚的同窗能夠看個人這篇文章:從源碼看 Jetpack(6)-ViewModel源碼詳解
ViewModelClearedWatcher
的主要邏輯是:
internal class ViewModelClearedWatcher(
storeOwner: ViewModelStoreOwner,
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) : ViewModel() {
private val viewModelMap: Map<String, ViewModel>?
init {
// We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
// however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
// does not have ViewModelStore#keys. All versions currently have the mMap field.
viewModelMap = try {
val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
mMapField.isAccessible = true
@Suppress("UNCHECKED_CAST")
mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
} catch (ignored: Exception) {
null
}
}
override fun onCleared() {
if (viewModelMap != null && configProvider().watchViewModels) {
viewModelMap.values.forEach { viewModel ->
objectWatcher.watch(
viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
)
}
}
}
companion object {
fun install(storeOwner: ViewModelStoreOwner, objectWatcher: ObjectWatcher, configProvider: () -> Config) {
val provider = ViewModelProvider(storeOwner, object : Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
ViewModelClearedWatcher(storeOwner, objectWatcher, configProvider) as T
})
provider.get(ViewModelClearedWatcher::class.java)
}
}
}
複製代碼
咱們不可能在 Activity 剛被回調了 onDestroy
方法就立刻來判斷 ReferenceQueue
中是否有值,由於 JVM 的 GC 時機是不肯定的,Activity 對象可能不會那麼快就被回收,因此須要延遲一段時間後再來檢測。而即便延遲檢測了,也可能會存在應用沒有發生內存泄露只是系統還未執行 GC 的狀況,因此就須要去主動觸發 GC,通過幾輪檢測後才能夠肯定當前應用是否的確發生了內存泄露
這裏就來看下具體的檢測流程
ObjectWatcher
對象包含了一個 Executor
參數:checkRetainedExecutor
。檢測操做的觸發時機就取決於向 checkRetainedExecutor
提交的任務在何時會被執行
class ObjectWatcher constructor(private val clock: Clock, private val checkRetainedExecutor: Executor,
/** * Calls to [watch] will be ignored when [isEnabled] returns false */
private val isEnabled: () -> Boolean = { true }
) {
···
/** * Watches the provided [watchedObject]. * * @param description Describes why the object is watched. */
@Synchronized
fun watch( watchedObject: Any, description: String ) {
if (!isEnabled()) {
return
}
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"
}
watchedObjects[key] = reference
//重點
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
//判斷 key 關聯的對象是否已經泄露
//是的話則將更新其 retainedUptimeMillis 值,以此來標記其發生了泄露
@Synchronized
private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
//重點,向外發出可能有內存泄露的通知
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
···
}
複製代碼
ObjectWatcher
對象又是在 InternalAppWatcher
裏初始化的,checkRetainedExecutor
在收到任務後會經過 Handler
來延時五秒執行
internal object InternalAppWatcher {
···
private val mainHandler by lazy {
Handler(Looper.getMainLooper())
}
private val checkRetainedExecutor = Executor {
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
val objectWatcher = ObjectWatcher(
clock = clock,
checkRetainedExecutor = checkRetainedExecutor,
isEnabled = { true }
)
···
}
複製代碼
ObjectWatcher
的 moveToRetained
方法又會經過 onObjectRetained
向外發出通知:當前可能發生了內存泄露。InternalLeakCanary
會收到這個通知,而後交由 HeapDumpTrigger
來進行檢測
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
private lateinit var heapDumpTrigger: HeapDumpTrigger
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
···
}
複製代碼
當 LeakCanary 斷定當前真的存在內存泄露時,就會進行 DumpHeap,找到泄露對象的引用鏈,而這個操做是比較費時費內存的,可能會直接致使應用頁面無響應,因此 LeakCanary 進行 DumpHeap 前會有許多前置檢查操做和前置條件,就是爲了儘可能減小 DumpHeap 次數以及在 DumpHeap 時儘可能減小對開發人員的干擾
heapDumpTrigger
的 scheduleRetainedObjectCheck()
方法的主要邏輯是:
internal class HeapDumpTrigger(
private val application: Application,
private val backgroundHandler: Handler,
private val objectWatcher: ObjectWatcher,
private val gcTrigger: GcTrigger,
private val heapDumper: HeapDumper,
private val configProvider: () -> Config
) {
···
fun scheduleRetainedObjectCheck( delayMillis: Long = 0L ) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
//若是當前已經在進行檢測了,則直接返回
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
private fun checkRetainedObjects() {
val iCanHasHeap = HeapDumpControl.iCanHasHeap()
val config = configProvider()
if (iCanHasHeap is Nope) {
if (iCanHasHeap is NotifyingNope) {
// Before notifying that we can't dump heap, let's check if we still have retained object.
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
val nopeReason = iCanHasHeap.reason()
val wouldDump = !checkRetainedCount(
retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
)
if (wouldDump) {
val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = uppercaseReason
)
}
} else {
SharkLog.d {
application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
return
}
//獲取當前還未回收的對象個數
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
//主動觸發 GC,儘可能嘗試回收對象,避免誤判
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold))
return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
//若是上一次 DumpHeap 離如今不足一分鐘,那麼就啓動一個定時任務,滿一分鐘後再次檢查
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()
//各個條件都知足了,已經能夠肯定發生了內存泄漏,去執行 DumpiHeap
dumpHeap(retainedReferenceCount, retry = true)
}
/** * 判斷當前是否符合 DumpHeap 的條件,符合的話返回 false * @param retainedKeysCount 當前還未回收的對象個數 * @param retainedVisibleThreshold 觸發 DumpHeap 的閾值 * 只有當 retainedKeysCount 大於等於 retainedVisibleThreshold 時纔會觸發 DumpHeap,默認值是 5 * @param nopeReason */
private fun checkRetainedCount( retainedKeysCount: Int, retainedVisibleThreshold: Int, nopeReason: String? = null ): Boolean {
//用於標記本次檢測相對上次,未回收的對象個數是否發生了變化
val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
lastDisplayedRetainedObjectCount = retainedKeysCount
if (retainedKeysCount == 0) {
if (countChanged) {
//若是 retainedKeysCount 爲 0,且值相對上次檢測減小了,則說明有對象被回收了
SharkLog.d { "All retained objects have been garbage collected" }
onRetainInstanceListener.onEvent(NoMoreObjects)
showNoMoreRetainedObjectNotification()
}
return true
}
//應用是否還在前臺
val applicationVisible = applicationVisible
val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod
···
if (retainedKeysCount < retainedVisibleThreshold) { //還未達到閾值
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
if (countChanged) {
onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
}
//在通知欄顯示當前未回收的對象個數
showRetainedCountNotification(
objectCount = retainedKeysCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
)
)
//retainedKeysCount 還未達到閾值,且當前「應用處於前臺」或者是「應用處於後臺但退到後臺的時間還未超出五秒」
//此時就啓動一個定時任務,在二十秒後從新再檢測一遍
scheduleRetainedObjectCheck(
delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
)
return true
}
}
return false
}
···
}
複製代碼
更後面的流程就涉及具體的 DumpHeap 操做了,這裏就再也不展開了,由於我也不太懂,後續有機會再單獨寫一篇文章來介紹了~~
除了 LeakCanary 默認支持的四種類型外,咱們還能夠主動檢測任意對象。例如,能夠檢測 Service:
class MyService : Service {
// ...
override fun onDestroy() {
super.onDestroy()
AppWatcher.objectWatcher.watch(
watchedObject = this,
description = "MyService received Service#onDestroy() callback"
)
}
}
複製代碼
LeakCanary 提供的默認配置項大多數狀況已經很適合咱們在項目中直接使用了,而若是咱們想要更改 LeakCanary 的默認配置項(例如不但願檢測 FragmentView),能夠在 Application 中進行更改:
class DebugExampleApplication : Application() {
override fun onCreate() {
super.onCreate()
AppWatcher.config = AppWatcher.config.copy(watchFragmentViews = false)
}
}
複製代碼
因爲 LeakCanary 的引用方式是 debugImplementation
,在 releas
環境下是引用不到 LeakCanary 的,因此爲了不在生成 release
包時須要主動來刪除這行配置項,須要將 DebugExampleApplication
放到 src/debug/java
文件夾中
能夠看出 Activity、Fragment、FragmentView、ViewModel 等四種類型的內存檢測都是須要依靠 ObjectWatcher
來完成的,由於這四種類型本質上都是屬於不一樣的對象。而 ObjectWatcher
須要依靠引用隊列 ReferenceQueue
來實現,所以 LeakCanary 的基本實現基礎就是來源於 Java 的原生特性
LeakCanary 的總體源碼講得也差很少了,後邊就再來寫一篇關於內存泄露的擴展閱讀 😂😂