iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0
Mobile Development

Android 性能戰爭:從 Profiler 開始的 30 天優化實錄系列 第 5

# Day 5:【Memory Profiler】LeakCanary:你的自動化洩漏哨兵

  • 分享至 

  • xImage
  •  

各位戰士,歡迎回到第五天的戰場。昨天,我們親身體驗了一次艱苦的偵查任務:我們深入龐大的記憶體快照 (Heap Dump) 中,依靠人工分析,才成功揪出一個由靜態引用導致的 Activity 洩漏。這個過程雖然強大,但無疑是耗時、費力且難以頻繁執行的。

如果每一次程式碼提交,我們都要手動去抓取並分析一次 Heap Dump,那將是一場災難。我們需要一個更聰明的解決方案。我們需要一個「自動化哨兵」,一個能不知疲倦地在前線巡邏,一旦發現記憶體洩漏的蹤跡,就立刻拉響警報的裝置。

這個哨兵,就是 Android 開發者社群中最負盛名、幾乎人手必備的神器——LeakCanary


部署你的哨兵:整合 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 就開始了它不知疲倦的巡邏工作。它的工作流程大致如下:

  1. 監視:LeakCanary 會自動監視那些生命週期短、理應被系統快速回收的物件,最典型的就是 ActivityFragment

  2. 等待:當一個 Activity 執行完 onDestroy() 後,LeakCanary 知道它應該「光榮退役」了。但它會給系統一點時間,通常是 5 秒。

  3. 檢查:5 秒後,LeakCanary 會強制觸發一次垃圾回收 (Garbage Collection),然後檢查那個 Activity 物件是否還存在於記憶體中。

  4. 分析:如果那個 Activity 物件「陰魂不散」,依然頑固地存在,LeakCanary 就判定發生了記憶體洩漏。此時,它會自動執行我們昨天手動做的所有事情:Dump 當前的記憶體快照,並在一個獨立的進程中開始分析。

  5. 報告:分析完成後,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 的實例。

  • 由於靜態變數的生命週期與應用程式一樣長,導致 MyLeakyActivityonDestroy() 之後,依然被強引用,無法被回收,造成了洩漏。

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 來全面監控應用程式的網路請求。

我們明天見!


上一篇
# Day 4:【Memory Profiler】記憶體快照:揪出不該存在的物件
下一篇
# Day 6:【Network Profiler】網路請求全監控
系列文
Android 性能戰爭:從 Profiler 開始的 30 天優化實錄8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
yulin494
iT邦新手 4 級 ‧ 2025-09-19 14:07:04

收穫滿滿 /images/emoticon/emoticon71.gif

我要留言

立即登入留言