各位戰士,歡迎回到第五天的戰場。昨天,我們親身體驗了一次艱苦的偵查任務:我們深入龐大的記憶體快照 (Heap Dump) 中,依靠人工分析,才成功揪出一個由靜態引用導致的 Activity
洩漏。這個過程雖然強大,但無疑是耗時、費力且難以頻繁執行的。
如果每一次程式碼提交,我們都要手動去抓取並分析一次 Heap Dump,那將是一場災難。我們需要一個更聰明的解決方案。我們需要一個「自動化哨兵」,一個能不知疲倦地在前線巡邏,一旦發現記憶體洩漏的蹤跡,就立刻拉響警報的裝置。
這個哨兵,就是 Android 開發者社群中最負盛名、幾乎人手必備的神器——LeakCanary。
LeakCanary 由 Square 公司開發,它的整合過程被設計得極其簡單,威力卻無比巨大。
得益於現代 Android Gradle Plugin 和函式庫的進步,整合 LeakCanary 現在只需要一步。
在你的 :app
模組的 build.gradle.kts
(或 build.gradle
) 檔案中,加入依賴:
// In app/build.gradle.kts
dependencies {
// ... 其他依賴
// 核心原則:只在 Debug 版本中引入 LeakCanary
// 使用 debugImplementation,確保它絕不會被打包進你的 Release APK
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
}
就這樣,部署完成了!
你無需在 Application
類別中添加任何初始化程式碼。LeakCanary 會利用 App Startup 函式庫自動完成註冊。
⚠️ 最高軍規:請務必、務必使用 debugImplementation
而不是 implementation
。這條指令確保了 LeakCanary 的所有程式碼和功能只存在於你的 Debug 版本中。當你建構 Release 版本給使用者時,它會被完全移除,不會對你正式版的應用程式造成任何性能影響或安全風險。
在你添加了依賴並運行 Debug 版本的應用程式後,LeakCanary 就開始了它不知疲倦的巡邏工作。它的工作流程大致如下:
監視:LeakCanary 會自動監視那些生命週期短、理應被系統快速回收的物件,最典型的就是 Activity
和 Fragment
。
等待:當一個 Activity
執行完 onDestroy()
後,LeakCanary 知道它應該「光榮退役」了。但它會給系統一點時間,通常是 5 秒。
檢查:5 秒後,LeakCanary 會強制觸發一次垃圾回收 (Garbage Collection),然後檢查那個 Activity
物件是否還存在於記憶體中。
分析:如果那個 Activity
物件「陰魂不散」,依然頑固地存在,LeakCanary 就判定發生了記憶體洩漏。此時,它會自動執行我們昨天手動做的所有事情:Dump 當前的記憶體快照,並在一個獨立的進程中開始分析。
報告:分析完成後,LeakCanary 會找到導致該 Activity 無法被回收的「引用鏈」,並將這份清晰的「戰情報告」以通知的形式呈現給你。
當 LeakCanary 偵測到洩漏時,你的手機會收到一條通知。點擊這條通知,你會進入一個專門的頁面,上面詳細列出了洩漏的路徑。
這份報告的核心是「洩漏追蹤 (Leak Trace)」,它看起來像這樣:
GC Root: System class
...
├─ com.example.MyApplication.leakedContext
│ Leaking: YES (Application is a singleton)
...
└─ com.example.MyLeakyActivity
Leaking: YES (Activity received onDestroy() but has not been garbage collected)
這份報告告訴我們:
垃圾回收的根節點 (GC Root) 是一個系統類別。
我們的 MyApplication
中有一個名為 leakedContext
的靜態變數。
這個靜態變數錯誤地持有了 MyLeakyActivity
的實例。
由於靜態變數的生命週期與應用程式一樣長,導致 MyLeakyActivity
在 onDestroy()
之後,依然被強引用,無法被回收,造成了洩漏。
LeakCanary 的報告會精準地指出引用鏈中的每一個環節,讓你能夠輕鬆地定位到問題的根源並修復它。
模擬一次洩漏,親眼見證哨兵的威力
為了讓你親眼看到 LeakCanary 的工作,我們可以故意製造一個洩漏。在你的 MainActivity 中加入以下程式碼:
// 為了測試,我們在 Application 類別中建立一個靜態變數
class MyApplication : Application() {
companion object {
var leakedActivity: Activity? = null
}
// ...
}
// 在 MainActivity 中
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 故意製造一個洩漏:讓一個靜態變數持有 Activity 的引用
MyApplication.leakedActivity = this
}
}
現在,運行你的 Debug App。然後,旋轉幾次螢幕(每次旋轉都會銷毀並重建 Activity)。等待一會兒,你就會看到 LeakCanary 發出的那條神聖的洩漏通知。
今天,我們為我們的軍隊配備了全自動化的記憶體洩漏哨兵。
我們學會了如何用一行 debugImplementation 程式碼來整合 LeakCanary。
我們理解了 LeakCanary 自動監視、分析並報告記憶體洩漏的工作原理。
我們知道了如何閱讀 LeakCanary 的洩漏追蹤報告,來快速定位問題。
從今天起,常見的記憶體洩漏將在開發階段就被我們的哨兵自動捕獲。我們從繁瑣的手動排查中解放出來,可以將精力集中在更重要的戰場上。
我們的記憶體防線已經穩固。明天,我們將把偵查的目光轉向另一個重要的資源通道:網路。我們將學習如何使用 Network Profiler 來全面監控應用程式的網路請求。
我們明天見!