iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
Mobile Development

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

# Day 26:【資源戰爭】嚴格模式 (StrictMode) 的應用

  • 分享至 

  • xImage
  •  

各位戰士,歡迎來到第二十六天的戰場。至今,我們已經學習了無數的軍規鐵律:不要阻塞主執行緒、使用協程處理 I/O、用 WorkManager 執行後台任務……。但理論和實踐之間,總有差距。在複雜的專案和緊迫的時程中,我們,甚至是經驗豐富的老兵,都可能偶然寫下違反紀律的程式碼。

我們需要一個自動化的「糾察隊」,一個在我們犯錯時會立刻大聲喝止的「魔鬼教官」。這個教官,就是 Android SDK 內建的開發者工具——StrictMode (嚴格模式)

StrictMode 是一個專為開發階段設計的監控工具。當你在應用程式中啟用它時,它會像一個哨兵一樣,監視你的程式碼行為。一旦發現你做了某些「壞事」(例如在主執行緒上讀寫檔案),它就會立刻以你指定的方式發出警告。

最高指令:StrictMode 絕對、絕對不能在 Release 發佈版本中啟用! 它僅用於在 Debug 版本中幫助我們發現潛在問題。


StrictMode 能監控什麼?

StrictMode 主要負責執行兩套「軍法」:

  1. 執行緒策略 (ThreadPolicy):這是我們最關心的。它專門監視主執行緒上的違規行為,是我們抓 Jank 的好幫手。主要監控的行為包括:

    • 磁碟讀取 (detectDiskReads())
    • 磁碟寫入 (detectDiskWrites())
    • 網路請求 (detectNetwork())
  2. 虛擬機策略 (VmPolicy):監視與記憶體和資源洩漏相關的違規行為。主要監控的行為包括:

    • 未關閉的 SQLite 物件洩漏 (detectLeakedSqlLiteObjects())
    • 未關閉的 Closeable 物件洩漏(如 Cursor, FileStream 等)
    • Activity 洩漏 (detectActivityLeaks())

如何部署你的「憲兵部隊」

啟用 StrictMode 的最佳時機,是在你自訂的 Application 類別的 onCreate() 方法中。這樣可以確保它在應用程式一啟動時就開始監視。

關鍵步驟:

import android.app.Application
import android.os.StrictMode
import com.your.package.name.BuildConfig // 確保 import 了你專案的 BuildConfig

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        // 核心原則:只在 Debug 版本中啟用 StrictMode
        if (BuildConfig.DEBUG) {
            setupStrictMode()
        }

        // ... 其他的應用程式初始化 ...
    }

    private fun setupStrictMode() {
        // 設定「執行緒策略」
        StrictMode.setThreadPolicy(
            StrictMode.ThreadPolicy.Builder()
                .detectDiskReads()      // 監控磁碟讀取
                .detectDiskWrites()     // 監控磁碟寫入
                .detectNetwork()        // 監控網路請求
                .penaltyLog()           // 偵測到違規時,在 Logcat 中印出詳細堆疊
                // .penaltyDeath()      // (可選) 偵測到違規時,直接讓 App 崩潰 (最嚴格)
                .build()
        )

        // 設定「虛擬機策略」
        StrictMode.setVmPolicy(
            StrictMode.VmPolicy.Builder()
                .detectLeakedSqlLiteObjects()   // 監控 SQLite 物件洩漏
                .detectLeakedClosableObjects()  // 監控 Closeable 物件洩漏
                .penaltyLog()                   // 同樣在 Logcat 中印出
                .build()
        )
    }
}

程式碼解析:

  • if (BuildConfig.DEBUG):這是啟用 StrictMode 的金標準,確保這段程式碼在 Release 版本中會被完全移除。

  • .detect...():指定要監控哪些違規行為。

  • .penalty...():指定偵測到違規行為後的「懲罰措施」。

  • penaltyLog():最常用也是最推薦的。它不會干擾你正常測試,只會在 Logcat 中用 StrictMode 這個 Tag 印出詳細的錯誤報告,包含完整的函式呼叫堆疊,讓你精準定位到出問題的那一行程式碼。

  • penaltyDeath():最嚴格的懲罰,一旦違規,App 立刻崩潰。適合在進行專項整治,決心要修復某個問題時短暫開啟。

如何判讀違規報告

當你啟用了 penaltyLog() 之後,一旦發生違規,你就會在 Logcat 中看到類似這樣的紅色日誌:

E/StrictMode: StrictMode policy violation; ~duration=23ms: android.os.strictmode.DiskReadViolation
    at android.os.StrictMode$AndroidBlockGuardPolicy.onDiskRead(StrictMode.java:1593)
    at java.io.FileInputStream.<init>(FileInputStream.java:151)
    at com.example.myapp.MyRepository.loadDataFromFile(MyRepository.java:42)
    at com.example.myapp.MyViewModel.loadData(MyViewModel.java:25)
    at com.example.myapp.MainActivity.onCreate(MainActivity.java:30)
    ...

這份報告提供了所有你需要的情報:

  • 違規類型:DiskReadViolation (磁碟讀取違規)。

  • 大概耗時:~duration=23ms (這次違規大概花了 23 毫秒,已經超過 16ms 了!)。

  • 違規路徑:完整的堆疊追蹤,告訴你問題發生在 MyRepository.java 的第 42 行,而它是在 MainActivityonCreate 中被呼叫的。

有了這份報告,你就等於拿到了一張直達問題根源的地圖。

今日總結

今天,我們為我們的開發流程建立了一套自動化的紀律監督系統。

  • 我們認識了 StrictMode 這個只應在開發階段使用的強大「哨兵」。

  • 我們學會了如何設定 ThreadPolicy 來監控主執行緒的 I/O 操作,以及如何設定 VmPolicy 來監控資源洩漏。

  • 我們知道了如何閱讀 StrictMode 的日誌,並根據堆疊追蹤快速定位問題程式碼。

StrictMode 像一位嚴厲但有益的教官,它能幫助我們在開發早期就根除壞習慣,建立起對性能問題的肌肉記憶。

我們已經學會了如何在開發時「嚴于律己」。但隨著專案演進,我們要如何確保今天優異的性能不會在未來被某次不經意的程式碼提交給破壞掉?明天,我們將學習建立一個自動化的「靶場」,使用 Jetpack Macrobenchmark 來為我們的應用性能建立一個可量化的基準,並持續追蹤。

我們明天見!


上一篇
# Day 25:【資源戰爭】後台任務的紀律:WorkManager
下一篇
# Day 27:【自動化戰爭】建立性能基準 (Benchmark)
系列文
Android 性能戰爭:從 Profiler 開始的 30 天優化實錄28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言