Android LeakCanary 使用 · 原理詳解

一、簡介

LeakCanary 是 Square 公司開源的 Android 內存泄漏檢測工具,通過自動化監控和堆轉儲分析,幫助開發者快速定位內存泄漏根源。其核心設計輕量高效,已成為 Android 開發中必備的調試工具。


二、使用方式

1. 集成步驟

在項目的?build.gradle?文件中添加依賴:

---
dependencies {debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12' 
}
---

2. 自動初始化機制

通過 ContentProvider 實現無感初始化:

class MainProcessAppWatcherInstaller : ContentProvider() {override fun onCreate(): Boolean {val application = context!!.applicationContext as ApplicationAppWatcher.manualInstall(application) // 核心初始化入口return true}
}

優勢:無需修改 Application 代碼,系統自動完成初始化流程


3. 監控對象

默認監控范圍:LeakCanary 自動檢測以下對象的泄漏:

  • 已銷毀的 Activity 實例

  • Fragment 及其關聯視圖對象

  • 已清理的 ViewModel 實例

  • Service?實例

手動監控:開發者可以對任意對象進行主動監控:

val watchedObject = MyObject()
AppWatcher.objectWatcher.watch(watchedObject,"MyObject is leaking"
)

4. 查看泄漏報告

通知欄提醒:檢測到泄漏時,LeakCanary 會生成通知欄提示。

詳細報告內容

  • 泄漏對象的引用鏈(從對象到 GC Root 的路徑)。

  • 泄漏原因分類(如靜態變量、未解綁監聽器等)。

  • 可疑代碼位置高亮。


5. 自定義配置

在?Application?中修改默認行為:

class MyApp : Application() {override fun onCreate() {super.onCreate()LeakCanary.config = LeakCanary.config.copy(dumpHeap = true,              // 是否生成 hprof 文件retainedVisibleThreshold = 3,  // 觸發堆轉儲的閾值(默認5秒)referenceMatchers = listOf(    // 忽略特定引用IgnoredReferenceMatcher(pattern = "com.example.MyClass.staticField")))}
}

三、原理分析

1. 自動初始化與生命周期監控

1.1 ContentProvider 自動初始化

LeakCanary 通過?ContentProvider?實現零侵入初始化,核心邏輯在?MainProcessAppWatcherInstaller?中:

// 源碼路徑:leakcanary-object-watcher-android/src/main/java/leakcanary/internal/MainProcessAppWatcherInstaller.kt
class MainProcessAppWatcherInstaller : ContentProvider() {override fun onCreate(): Boolean {// 獲取 Application 實例val application = context!!.applicationContext as Application// 調用 AppWatcher 手動安裝AppWatcher.manualInstall(application)return true}// 其他方法空實現(query/insert 等)
}

觸發時機:Android 系統在應用啟動時自動初始化所有注冊的?ContentProvider,通過?onCreate()?觸發 LeakCanary 的初始化。

優勢:無需開發者手動在?Application?中調用代碼,實現完全自動化。


1.2 生命周期監控組件注冊

在?AppWatcher.manualInstall()?中注冊默認的監控組件:

// 源碼路徑:leakcanary-object-watcher-android/src/main/java/leakcanary/AppWatcher.kt
fun manualInstall(application: Application,watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {// 1. 初始化 InternalLeakCanaryLeakCanaryDelegate.loadLeakCanary(application)// 2. 注冊四大監控組件watchersToInstall.forEach { it.install() }
}private fun appDefaultWatchers(application: Application): List<InstallableWatcher> {return listOf(ActivityWatcher(application, objectWatcher),FragmentAndViewModelWatcher(application, objectWatcher),RootViewWatcher(objectWatcher),ServiceWatcher(objectWatcher))
}

四大核心監控組件

組件名稱監控目標觸發時機實現原理
ActivityWatcherActivityonDestroy()ActivityLifecycleCallbacks
FragmentWatcherFragment/ViewModelonViewDestroyed()FragmentManager 生命周期監聽
RootViewWatcherDecorViewonDetachedFromWindowView.OnAttachStateListener
ServiceWatcherServiceonDestroy()Service 生命周期回調

典型監控流程(以 Activity 為例)

// 源碼路徑:leakcanary-object-watcher-android/src/main/java/leakcanary/internal/ActivityWatcher.kt
class ActivityWatcher(private val application: Application,private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {override fun onActivityDestroyed(activity: Activity) {// 將 Activity 加入泄漏監控隊列reachabilityWatcher.expectWeaklyReachable(activity, "${activity::class.java.name} received Activity#onDestroy()")}}override fun install() {application.registerActivityLifecycleCallbacks(lifecycleCallbacks)}
}

核心邏輯:通過?registerActivityLifecycleCallbacks?監聽?onActivityDestroyed,觸發對象泄漏檢測。


2. 弱引用追蹤系統

對象監控三要素

  • KeyedWeakReference:攜帶唯一標識的弱引用。

  • ReferenceQueue:關聯的回收隊列。

  • ObjectWatcher:負責管理被監控對象。

// 源碼路徑:leakcanary-object-watcher/core/src/main/java/leakcanary/ObjectWatcher.kt
class ObjectWatcher {private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()private val queue = ReferenceQueue<Any>()fun watch(target: Any, description: String) {val key = UUID.randomUUID().toString()val reference = KeyedWeakReference(target, key, description, queue)watchedObjects[key] = referencescheduleRetainedCheck()}private fun scheduleRetainedCheck() {checkRetainedExecutor {removeWeaklyReachableObjects()checkRetainedCount()}}
}

KeyedWeakReference:自定義弱引用,關聯全局?ReferenceQueue,用于判斷對象是否被回收。

class KeyedWeakReference(referent: Any,val key: String,val description: String,val watchUptimeMillis: Long,queue: ReferenceQueue<Any>
) : WeakReference<Any>(referent, queue)
2.2 回收檢測流程

關鍵步驟,雙階段檢測流程

  1. 輪詢隊列:通過輪詢?ReferenceQueue,移除已被回收的?KeyedWeakReference

  2. 觸發泄漏檢測:若對象未被回收,調用?onObjectRetained()?通知監聽器。

private fun removeWeaklyReachableObjects() {do {val ref = queue.poll() as? KeyedWeakReferenceref?.let { watchedObjects.remove(it.key) }} while (ref != null)
}private fun checkRetainedCount() {if (watchedObjects.size >= config.retainedVisibleThreshold) {onLeakDetected()}
}// onLeakDetected() 是 ObjectWatcher 中的一個方法,用于在檢測到內存泄漏時觸發后續的處理邏輯。
// onLeakDetected() 方法會遍歷 onObjectRetainedListeners 列表,并調用每個監聽器的     
// onObjectRetained() 方法。
private fun onLeakDetected() {// 1. 觸發泄漏通知onObjectRetainedListeners.forEach { it.onObjectRetained() }
}// 源碼路徑:leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt
override fun onObjectRetained() {scheduleRetainedObjectCheck()
}

3. 堆轉儲觸發與堆分析

3.1 HeapDumpTrigger 調度

泄漏通知最終由?HeapDumpTrigger?處理:等待5s后,調用gc,如果持有的弱引用沒有被清除,則被監視器認為產生了一個內存泄露,LeakCanary會將其記錄到Logcat中;記錄保留對象的計數,達到閾值后,會調用轉儲堆;

泄漏判定流程

  1. 檢測潛在泄漏對象。

  2. 主動觸發?System.gc()

  3. 二次確認存活狀態。

  4. 生成?hprof?堆轉儲文件。

  5. 啟動 Shark 分析引擎。

// 源碼路徑:leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt
class HeapDumpTrigger(private val application: Application,private val backgroundHandler: Handler,private val objectWatcher: ObjectWatcher,private val gcTrigger: GcTrigger,private val configProvider: () -> Config
) {fun scheduleRetainedObjectCheck() {backgroundHandler.postDelayed({checkRetainedObjects()}, 0)}private fun checkRetainedObjects() {// 1. 檢查未被回收的對象數量val retainedCount = objectWatcher.retainedObjectCountif (retainedCount > 0) {// 2. 主動觸發 GCgcTrigger.runGc()// 3. 再次檢查,確認泄漏if (objectWatcher.retainedObjectCount >= configProvider().retainedVisibleThreshold) {// 4. 生成堆轉儲dumpHeap(retainedCount, reason = "Retained objects ≥ threshold")}}}
}

主動觸發 GC

object Default : GcTrigger {override fun runGc() {Runtime.getRuntime().gc()Thread.sleep(100)System.runFinalization()}
}
3.2 堆轉儲生成
private fun dumpHeap(retainedReferenceCount: Int, reason: String) {// 1. 創建堆轉儲文件val heapDumpFile = InternalLeakCanary.createLeakDirectoryProvider(application).newHeapDumpFile()// 2. 調用 Android API 生成 hprof 文件Debug.dumpHprofData(heapDumpFile.absolutePath)// 3. 發送分析任務InternalLeakCanary.sendEvent(HeapDump(heapDumpFile, reason))
}
3.3 堆分析引擎(Shark 庫)

堆分析由?BackgroundThreadHeapAnalyzer?在后臺線程執行:

// 源碼路徑:leakcanary-android-core/src/main/java/leakcanary/internal/BackgroundThreadHeapAnalyzer.kt
object BackgroundThreadHeapAnalyzer : EventListener {private val handlerThread = HandlerThread("HeapAnalyzer")override fun onEvent(event: Event) {if (event is HeapDump) {handlerThread.handler.post {// 使用 Shark 庫解析 hprofval result = SharkHelper.analyze(event.heapDumpFile)// 生成泄漏報告showResult(result)}}}
}

Shark 分析引擎四步法

  1. 流式解析:分塊讀取避免內存溢出。

  2. 索引構建:建立快速查找表。

  3. 泄漏追蹤:BFS 算法查找 GC Root 路徑。

  4. 結果聚合:生成可視化報告。

?4. 檢測流程


四、泄漏報告解讀與處理

1. 報告輸出渠道

  • 通知欄:點擊查看詳細堆棧。

  • Logcat:打印完整引用鏈信息。

  • Toast:提示內存泄漏發生。

  • 桌面報告文件/sdcard/Download/leakcanary-{package}/?目錄。

// 源碼路徑:leakcanary-android-core/src/main/java/leakcanary/EventListener.kt
object LogcatEventListener : EventListener {override fun onEvent(event: Event) {if (event is HeapAnalysisDone) {Log.d("LeakCanary", event.heapAnalysis.toString())}}
}object NotificationEventListener : EventListener {override fun onEvent(event: Event) {if (event is HeapAnalysisDone) {showNotification(event.heapAnalysis)}}
}

2. 典型報告結構

┬───
│ GC Root: 靜態變量 com.example.AppConfig.sInstance
│
├─ com.example.UserManager 實例
│    ↓ 靜態 UserManager.currentActivity
├─ com.example.MainActivity 實例
│    ↓ 匿名內部類持有外部引用
╰→ 泄漏點: MainActivity$1.class

3. 常見泄漏模式

泄漏類型典型場景解決方案
靜態引用單例持有 Activity 引用改用?WeakReference
匿名內部類Handler?未及時移除使用靜態內部類 + 弱引用
未解綁監聽注冊系統服務未反注冊生命周期配對解除
資源未關閉文件流/Cursor?未關閉使用?try-with-resources

五、高級優化策略

1. 生產環境配置

LeakCanary.config = LeakCanary.config.copy(dumpHeap = BuildConfig.DEBUG,analysisPeriodMillis = 120_000,referenceMatchers = listOf(IgnoredReferenceMatcher(className = "com.example.SDKManager",fieldName = "mContext"))
)

2. 性能優化技巧

  • 采樣檢測:隨機檢測部分關鍵對象。

  • 延時分析:空閑時段執行堆解析。

  • 白名單機制:過濾已知偽泄漏。


六、核心設計總結

  • 自動化監控:通過 Android 系統機制(ContentProviderLifecycleCallbacks)實現零侵入集成。

  • 精準判斷:弱引用 + 引用隊列確保對象回收狀態判斷的準確性。

  • 高效分析:Shark 庫的流式解析和最短路徑算法提升分析效率。

  • 靈活擴展:支持自定義監控對象、排除已知泄漏、調整檢測閾值。

通過源碼級分析,可深入理解 LeakCanary 的底層機制,快速定位復雜內存問題,提升應用穩定性。

推薦&參考:?

1.??Android StrictMode 使用與原理深度解析

2.?《Android應用性能優化全解析:常見問題與解決方案》

3.?Android體系課之--LeakCanary內存泄露檢測原理解析-阿里云開發者社區

4.?《RxJava 深度解析:工作原理、核心操作符與高效實踐指南》

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/72343.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/72343.shtml
英文地址,請注明出處:http://en.pswp.cn/web/72343.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

每日一題---dd愛框框(Java中輸入數據過多)

dd愛框框 實例&#xff1a; 輸入&#xff1a; 10 20 1 1 6 10 9 3 3 5 3 7 輸出&#xff1a; 3 5 這道題要解決Java中輸入的數過多時&#xff0c;時間不足的的問題。 應用這個輸入模板即可解決&#xff1a; Java中輸入大量數據 import java.util.*; import java.io.*;pu…

redis部署架構

一、redis多實例部署 實例1 安裝目錄&#xff1a;/app/6380 數據目錄&#xff1a;/app/6380/data 實例2 安裝目錄&#xff1a;/app/6381 數據目錄&#xff1a;/app/6381/data 1、創建實例安裝目錄 2、拷貝實例的配置文件 3、編輯實例的配置文件 第…

vscode python相對路徑的問題

vscode python相對路徑的問題 最近使用使用vscode連接wsl2寫python時&#xff0c;經常遇到找不到包中的方法的問題&#xff0c;最終發現vscode在執行python代碼時目錄不是從當前python文件開始算起&#xff0c;而是從當前工作區的目錄開始算起&#xff0c;比如說我打開的是/ho…

面試vue2開發時怎么加載編譯速度(webpack)

可以輸入命令獲取默認 webpack 設置 vue inspect > set.js 1.使用緩存 configureWebpack: {cache: {type: filesystem, // 使用文件系統緩存類型buildDependencies: {config: [__filename] // 緩存依賴&#xff0c;例如webpack配置文件路徑}}}, 2.啟用 vue-loader (測試明…

uv命令介紹(高性能Python包管理工具,旨在替代pip、pip-tools和virtualenv等傳統工具)

文章目錄 **主要功能**1. **快速安裝和管理 Python 包**2. **生成和管理鎖文件 (requirements.lock)**3. **創建虛擬環境**4. **與 poetry 兼容** **核心優勢**1. **極快的速度**&#xff1a;基于 Rust 實現&#xff0c;利用多線程和緩存大幅加速依賴解析。2. **輕量且獨立**&a…

企業數據管理的成本與效率革命

在數字經濟時代&#xff0c;企業每天產生的數據量正以指數級速度增長。IDC預測&#xff0c;到2025年全球數據總量將突破180 ZB。面對海量數據存儲需求和有限的IT預算&#xff0c;企業逐漸意識到&#xff1a;將每字節數據都存儲在昂貴的高性能存儲設備上&#xff0c;既不經濟也不…

深度學習-服務器訓練SparseDrive過程記錄

1、cuda安裝 1.1 卸載安裝失敗的cuda 參考&#xff1a;https://blog.csdn.net/weixin_40826634/article/details/127493809 注意&#xff1a;因為/usr/local/cuda-xx.x/bin/下沒有卸載腳本&#xff0c;很可能是apt安裝的&#xff0c;所以通過執行下面的命令刪除&#xff1a; a…

洛谷每日1題-------Day20__P1401 [入門賽 #18] 禁止在 int 乘 int 時不開 long long

題目描述 在比賽中&#xff0c;根據數據范圍&#xff0c;分析清楚變量的取值范圍&#xff0c;是非常重要的。int 類型變量與 int 類型變量相乘&#xff0c;往往可能超出 int 類型可以表示的取值范圍。 現在&#xff0c;給出兩個 int 類型變量 x,y 及其取值范圍&#xff0c;請…

3.15刷題

P6337 [COCI 2007/2008 #2] CRNE - 洛谷 #include<bits/stdc.h> using namespace std; int main(){int n;cin>>n;//橫加豎 最大。n/2,n/21if(n%20){cout<<(n/21)*(n/21);}else cout<<(n/22)*(n/21);return 0; }P6338 [COCI 2007/2008 #2] PRVA - 洛…

Browser Copilot 開源瀏覽器擴展,使用現有或定制的 AI 助手來完成日常 Web 應用程序任務。

一、軟件介紹 文末提供源碼和開源擴展程序下載 Browser Copilot 是一個開源瀏覽器擴展&#xff0c;允許您使用現有或定制的 AI 助手來幫助您完成日常 Web 應用程序任務。 目標是提供多功能的 UI 和簡單的框架&#xff0c;以實現和使用越來越多的 copilots&#xff08;AI 助手&…

selenium等待

通常代碼執行的速度?頁?渲染的速度要快,如果避免因為渲染過慢出現的?動化誤報的問題呢?可以使?selenium中提供的三種等待?法: 1. 隱式等待(Implicit Wait) 隱式等待適用于全局,它告訴 WebDriver 在查找元素時等待一定的時間,直到元素出現。 如果超時,WebDriver 不…

解鎖C++:指針與數組、字符串的深度探秘

目錄 一、指針與數組:親密無間的伙伴 1.1 指針是數組的 “快捷通道” 1.2 數組名與指針:微妙的差別 1.3 動態數組:指針大顯身手 二、指針與字符串:千絲萬縷的聯系 2.1 字符指針與 C 風格字符串 2.2 指針與 std::string 類 2.3 字符串常量與指針 三、指針在數組和字…

20250315-OpenAI-AgentSDK實驗

湊熱鬧。可以用GLM跑。 這里暫時用GLM底座“魔鬼修改”&#xff0c;代碼庫僅供參考&#xff08;共同進步吧&#xff09; openai-agents-python-glm: 基于GLM底座運行SDK&#xff0c;學習實驗SDK內的mAGT功能。https://gitee.com/leomk2004/openai-agents-python-glm 自言自語&a…

Qt QML實現彈球消磚塊小游戲

前言 彈球消磚塊游戲想必大家都玩過&#xff0c;很簡單的小游戲&#xff0c;通過移動擋板反彈下落的小球&#xff0c;然后撞擊磚塊將其消除。本文使用QML來簡單實現這個小游戲。 效果圖&#xff1a; 正文 代碼目錄結構如下&#xff1a; 首先是小球部分&#xff0c;邏輯比較麻…

04_Linux驅動_05_pinctrl子系統

以下代碼都在pinctrl相關的驅動函數和設備樹中 pinctrl-rockchip.c驅動&#xff0c;對應的是那個&#xff08;那些&#xff09;設備樹呢&#xff1f; 答案&#xff1a;通過.compatible "rockchip,rk3568-pinctrl"連接到rk3568.dtsi根節點下的pinctrl節點 一&#…

Python的那些事第四十五篇:繼承自Nose的測試框架Nose2

Nose2:繼承自Nose的測試框架 摘要 本文深入探討了Nose2這一繼承自Nose的測試框架。在軟件開發過程中,測試是確保代碼質量和穩定性的重要環節,而測試框架為測試工作的開展提供了有力支持。Nose2作為Nose的繼承者,在保留Nose優勢的基礎上進行了諸多改進和擴展,為Python測試…

如何通過 Airbyte 將數據攝取到 Elasticsearch

作者&#xff1a;來自 Elastic Andre Luiz Airbyte 是一個數據集成工具&#xff0c;可自動化并可擴展地將信息從各種來源傳輸到不同的目的地。它使你能夠從 API、數據庫和其他系統提取數據&#xff0c;并將其加載到 Elasticsearch 等平臺&#xff0c;以實現高級搜索和高效分析。…

RBAC 模型的簡單實現

RBAC 模型基本介紹 RBAC&#xff08;Role-Based Access Control&#xff0c;基于角色的訪問控制&#xff09;是一種廣泛應用的權限管理模型。它的核心思想是通過角色來管理權限&#xff0c;而不是直接分配權限給用戶。用戶被賦予一個或多個角色&#xff0c;而每個角色擁有不同…

數據結構---堆棧和列

一、堆棧 1.棧堆&#xff1a;具有一定操作約束的線性表&#xff1b;&#xff08;只在一端做插入刪除&#xff09; 2.棧的順序存儲結構&#xff1a; 由一個一維數組和一個記錄棧頂元素位置的變量組成。定義方式如下&#xff1a; 3.入棧操作&#xff1a; 注意&#xff1a;&…

2023 年全國職業院校技能大賽(中職組)移動應用與開發賽項 賽題第十套

2023 年全國職業院校技能大賽&#xff08;中職組&#xff09;移動應用與開發賽項 賽題第十套&#xff09; 移動應用與開發賽項競賽模塊 A&#xff1a;移動應用界面設計任務 1 環保中心界面設計&#xff08;7.5 分&#xff09;任務 2&#xff1a;首頁界面設計&#xff08;7.5 分…