網站首頁 編程語言 正文
前言
平時我們都有用到LeakCanary來分析內存泄露的情況,這里可以來看看LeakCanary是如何實現的,它的內部又有哪些比較有意思的操作。
LeakCanary的使用
官方文檔:square.github.io/leakcanary/…
引用方式
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
}
可以看到LeakCanary的新版本中依賴非常簡單,甚至不需要你做什么就可以直接使用。
LeakCanary原理
LeakCanary的封裝主要是利用ContentProvider,LeakCanary檢測內存泄漏主要是監聽Activity和Fragment、view的生命周期,配合弱引用和ReferenceQueue。
源碼淺析
初始化
首先debugImplementation只是在Debug的包會依賴,在正式包不會把LeakCanary的內容打進包中。
LeakCanary的初始化是使用了ContentProvider,ContentProvider的onCreate會在Application的onCreate之前,它把ContentProvider寫在自己的AndroidMainifest中,打包時會進行合并,所以這整個過程都不需要接入端做初始化操作。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.squareup.leakcanary.objectwatcher" > <uses-sdk android:minSdkVersion="14" /> <application> <provider android:name="leakcanary.internal.MainProcessAppWatcherInstaller" android:authorities="${applicationId}.leakcanary-installer" android:enabled="@bool/leak_canary_watcher_auto_install" android:exported="false" /> </application> </manifest>
這是它在AndroidManifest所定義的,打包的時候會合并所有的AndroidManifest
這就是它自動初始化的操作,也比較明顯了,不用過多解釋。
使用
先看看它要監測什么,因為LeakCanary 2.x的代碼都是kotlin寫的,所以這里得分析kotlin,如果不熟悉kt的朋友,我只能說盡量講慢一些,因為我想看舊版本的能不能用java來分析,但是簡單看了下源碼上是有一定的差別,所以還是要分析2.x。
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
從這里看到他主要分析Activity、Fragment和Fragment的View、RootView、Service。
看Activity的監聽ActivityWatcher
監聽Activity調用Destroy時會調用reachabilityWatcher的expectWeaklyReachable方法。
這里可以看看舊版本的做法(正好以前有記錄)
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
舊版本是調用refWatcher的watch,雖然代碼不同,但是思想一樣,再看看舊版本的Fragment
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
refWatcher.watch(fragment);
}
};
這里監聽了Fragment和Fragment的View,所以相比于新版本,舊版本只監聽Activity、Fragment和Fragment的View
再回到新版本,分析完Activity的監聽之后看看Fragment的
最終Destroy之后也是調用到reachabilityWatcher的expectWeaklyReachable。然后看看RootViewWatcher的操作
private val listener = OnRootViewAddedListener { rootView ->
val trackDetached = when(rootView.windowType) {
PHONE_WINDOW -> {
when (rootView.phoneWindow?.callback?.wrappedCallback) {
is Activity -> false
is Dialog -> {
......
}
else -> true
}
}
POPUP_WINDOW -> false
TOOLTIP, TOAST, UNKNOWN -> true
}
if (trackDetached) {
rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
val watchDetachedView = Runnable {
reachabilityWatcher.expectWeaklyReachable(
rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
)
}
......
})
}
}
最終也是調用到reachabilityWatcher的expectWeaklyReachabl。最后再看看Service的。
這邊因為只是做淺析,不是源碼詳細分析,所以我這邊就不去一個個分析是如何調用到銷毀的這個方法的,我們通過上面的方法得到一個結論,Activity、Fragment和Fragment的View、RootView、Service,他們幾個,在銷毀時都會調用到reachabilityWatcher的expectWeaklyReachabl。所以這些地方就是檢測對象是否泄漏的入口。
然后我們來看看expectWeaklyReachable方法
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
// 先從queue中移除一次已回收對象
removeWeaklyReachableObjects()
// 生成隨機數當成key
val key = UUID.randomUUID().toString()
val watchUptimeMillis = clock.uptimeMillis()
// 創建弱引用關聯ReferenceQueue
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
......
// 把reference和key 添加到一個Map中
watchedObjects[key] = reference
// 下一步
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
你們運氣真好,我正好以前也有記錄舊版本的refWatcher的watch方法
public void watch(Object watchedReference, String referenceName) {
......
// 生成隨機數當成key
String key = UUID.randomUUID().toString();
// 把key 添加到一個Set中
this.retainedKeys.add(key);
// 創建弱引用關聯ReferenceQueue
KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
// 下一步
this.ensureGoneAsync(watchStartNanoTime, reference);
}
通過對比發現,模板的流程是一樣的,但是細節不一樣,以前是用Set,現在是用Map,這就是我覺得不能拿舊版本代碼來分析的原因。
文章寫到這里,突然想到一個很有意思的東西,你要是面試時,面試官看過新版本的代碼,你看的是舊版本的代碼,結果如果問到一些比較深入的細節,你答出來的和他所理解的不同,那就尷尬了,所以面試時得先說清楚你是看過舊版本的代碼
看到用一個弱引用生成一個key和對象綁定起來。然后調用ensureGoneAsync方法
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
this.watchExecutor.execute(new Retryable() {
public Result run() {
return RefWatcher.this.ensureGone(reference, watchStartNanoTime);
}
});
}
execute里面會調用到waitForIdle方法。
我們再回到新版本的代碼中
checkRetainedExecutor.execute其實是會執行到這里(kt里面的是寫得簡單,但是不熟的話可以先別管怎么執行的,只要先知道反正執行到這個地方就行)
這里是做了一個延時發送消息的操作,延時5秒,具體代碼在這里
寫到這里我感覺有點慌了,因為如果不熟kt的朋友可能真會看困,其實如果看不懂這個代碼的話沒關系,只要我圈出來的地方,我覺是大概能看懂的,然后流程我會說,我的意思是沒必要深入去看每一行是什么意思,我們的目的是找出大概的流程(用游戲的說法,我們是走主線任務,不是要全收集)
延遲5秒后會調回到前面的moveToRetained(key)。那不好意思各位,我又要拿舊版本來對比了,因為細節不同。
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// 使用IdleHandler來實現在閑時才去執行后面的流程
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
使用IdleHandler來完成閑時觸發,我不記得很早之前的版本是不是也用的IdleHandler,這里使用IdleHandler只能說有好有壞吧,好處是閑時觸發確實是一個很好的操作,不好的地方是如果一直有異步消息,就一直不會觸發后面的流程。
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
// 根據上下文去計算,這里是5秒
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
看到舊版本是先用IdelHanlder,在閑時觸發的情況下再去延時5秒,而新版本是直接延時5秒,不使用IdelHandler,我沒看過這塊具體的文檔描述,我猜是為了防止餓死,如果用IdelHanlder的話可能會出現一直不觸發的情況。
返回看新版本的moveToRetained
@Synchronized private fun moveToRetained(key: String) {
// 從ReferenceQueue中拿出對象移除
removeWeaklyReachableObjects()
// 經過上一步之后判斷Map中還有沒有這個key,有的話進入下一步操作
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
private fun removeWeaklyReachableObjects() {
// 從ReferenceQueue中拿出對象,然后從Map中移除
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
moveToRetained主要是從ReferenceQueue中找出弱引用對象,然后移除Map中相應的弱引用對象。弱引用+ReferenceQueue的使用,應該不用多說吧,如果弱引用持有的對象被回收,弱引用會添加到ReferenceQueue中。所以watchedObjects代表的是應該將要被回收的對象,queue表示已經被回收的對象,這步操作就是從queue中找出已經回收的對象,然后從watchedObjects移除相應的對象,剩下的的就是應該被回收卻沒被回收的對象。如果對象被正常回收,那這整個流程就走完了,如果沒被回收,會執行到onObjectRetained(),之后就是Dump操作了,之后的就是內存分析、彈出通知那堆操作了,去分析內存的泄漏這些,因為內容比較多,這篇先大概就先到這里。
總結
淺析,就是只做了簡單分析LeakCanary的整個工作過程和工作原理。
原理就是用弱引用和ReferenceQueue去判斷應該被回收的對象是否已經被回收。大致的工作流程是:監聽Activity、Fragment和Fragment的View、RootView、Service對象的銷毀,然后將這些對象放入“應該被回收”的容器中,然后5秒后通過弱引用和ReferenceQueue去判斷對象是否已被回收,如果被回收則從容器中刪除對應的對象,否則進行內存分析。
至于是如何判斷不同對象的銷毀和如何分析內存情況找出泄漏的引用鏈,這其中也是細節滿滿,但是我個人LeakCanary應該是看過兩三次源碼了,從一開始手動初始化,到舊版本java的實現方式,到現在用kt去實現,能發現它的核心思想其實是一樣的,只不過在不斷的優化一些細節和不斷的擴展可以監測的對象。
原文鏈接:https://juejin.cn/post/7167026728596946980
相關推薦
- 2022-09-17 python中pandas常用命令詳解_python
- 2022-11-08 go按行讀取文件的三種實現方式匯總_Golang
- 2022-05-13 Headless Chrom自動化工具詳解
- 2022-07-07 python中列表對象pop()方法的使用說明_python
- 2023-01-07 Python使用TextRank算法提取關鍵詞_python
- 2022-12-13 pandas中merge()函數的用法解讀_python
- 2023-10-15 動態演示操作系統進程調度算法,FCFS, RR, SPN, SRT, HRRN
- 2022-08-13 Android自定義ProgressBar實現漂亮的進度提示框_Android
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支