部分 Wake Lock 停滯

部分 Wake Lock 是 PowerManager API 中的一項機制,可以讓開發人員在關閉裝置螢幕 (無論是因為系統逾時還是因為使用者按下電源鍵) 之後繼續運行 CPU。應用程式可以使用 PARTIAL_WAKE_LOCK 標記呼叫 acquire(),藉此獲取部分 Wake Lock。當應用程式在背景執行時 (使用者看不到應用程式的任何部分),如果部分 Wake Lock 保持了較長時間,就會進入「停滯」狀態。這個狀況會防止裝置進入低電源狀態,因此會消耗裝置電池。您應只在有必要時才使用部分 Wake Lock,當不再需要時就立即釋放。

如果應用程式發生部分 Wake Lock 停滯,您可以用本頁面的指南診斷並修復問題。

偵測問題

您可能永遠都不會知道應用程式的部分 Wake Lock 停滯了。如果您已經發布應用程式,Android Vitals 可以協助您注意這個問題。

Android Vitals

Android Vitals 會透過 Play 管理中心在應用程式發生部分 Wake Lock 停滯發出提醒,藉此改善應用程式的效能。如果在一個電池工作階段中,於背景發生至少一次部分 Wake Lock 並持續一小時,Android Vitals 就會報告部分 Wake Lock 停滯問題:

「電池工作階段」的定義按照各平台版本而有不同。

  • 在 Android 10 上,電池工作階段是指系統在指定的 24 小時內,根據收到的所有電池報告彙整而成的時間資料。「電池報告」指的是電池兩次充電 (從低於 20% 充至 80% 以上,或是從任意值充至 100%) 之間的間隔時間。
  • 在 Android 11 上,電池工作階段固定為 24 小時。

電池工作階段顯示的是所有應用程式測量使用者所彙整而成的數字。如果想瞭解 Google Play 如何收集 Android Vitals 資料,請參閱 Play 管理中心說明文件。

發現應用程式有過多部分 Wake Lock 停滯情形後,接下來就要解決問題。

修正問題

Wake Lock 是在早期的 Android 平台版本中推出的功能,不過隨著時間過去,許多之前必須仰賴 Wake Lock 才能運作的用例現在都已改用效果更好的新 API,例如 WorkManager

本章節會說明修正 Wake Lock 的訣竅,不過為了長期著想,還是建議您遷移應用程式,以便使用「最佳做法」章節所推薦的做法。

找出並修正程式碼中使用 Wake Lock 的部分,例如呼叫 newWakeLock(int, String)WakefulBroadcastReceiver 子類別。以下提供幾項訣竅:

  • 建議您在 Wake Lock 標籤名稱中加入封包、類別或方法名稱,這樣就能輕鬆在原始碼裡找到 Wake Lock 的建立位置。以下提供幾項額外的訣竅:
    • 請勿在名稱裡面填寫個人識別資訊 (PII),例如電子郵件地址等。否則裝置會記錄 _UNKNOWN 而不是 Wake Lock 名稱。
    • 請勿藉由程式輔助方式產生類別或方法名稱,例如藉由呼叫 getName() 產生名稱等,因為 Proguard 可能會進行模糊處理。請改用硬式編碼字串。
    • 請勿在 Wake Lock 標籤裡加上計數或專用 ID,因為用相同方法建立的 Wake Lock 都會有專用的 ID,導致系統無法彙整。
  • 確定程式碼可以釋放所有獲取的 Wake Lock。這比單純確定所有 acquire() 呼叫都有對應的 release() 呼叫還要複雜。以下示例顯示由於發生未偵測到的例外情形,導致無法釋放 Wake Lock:

    Kotlin

    @Throws(MyException::class)
    fun doSomethingAndRelease() {
        wakeLock.apply {
            acquire()
            doSomethingThatThrows()
            release()  // does not run if an exception is thrown
        }
    }

    Java

        void doSomethingAndRelease() throws MyException {
            wakeLock.acquire();
            doSomethingThatThrows();
            wakeLock.release();  // does not run if an exception is thrown
        }

    正確的程式碼版本如下:

    Kotlin

    @Throws(MyException::class)
    fun doSomethingAndRelease() {
        wakeLock.apply {
            try {
                acquire()
                doSomethingThatThrows()
            } finally {
                release()
            }
        }
    }

    Java

        void doSomethingAndRelease() throws MyException {
            try {
                wakeLock.acquire();
                doSomethingThatThrows();
            } finally {
                wakeLock.release();
            }
        }
  • 確定可在不再需要 Wake Lock 時立即釋放。舉例來說,如果您要使用 Wake Lock 以便完成背景工作,請確定在工作結束後系統會進行釋放。如果並未釋放而導致 Wake Lock 保留時間超出預期,可能表示背景工作花費的時間比預期要長。

修復程式碼裡的問題之後,請用以下 Android 工具檢查應用程式是否可以正確釋放 Wake Lock:

  • dumpsys - 此工具可以提供裝置內系統服務的狀態資訊。如果想檢視電源服務狀態 (包括 Wake Lock 清單),請執行 adb shell dumpsys power

  • Battery Historian - 此工具可以剖析 Android 錯誤報告的輸出結果,並用視覺化方式呈現電源相關的活動。

最佳做法

一般而言,應用程式應該要避免使用 Wake Lock,因為這樣做非常容易用光使用者的電池電力。Android 有為幾乎所有先前必須使用部分 Wake Lock 的用途提供替代用的 API。部分 Wake Lock 剩下的用途之一是確保音樂應用程式可以在螢幕關閉後繼續播放音樂。如果您要用 Wake Lock 執行工作,請考慮使用背景處理作業指南提供的替代方案。

如果一定要使用部分 Wake Lock,請遵守以下建議事項:

  • 確定應用程式有部分內容會保持在前景執行。舉例來說,如果您需要執行服務,請改為啟動前景服務。這樣做可以讓使用者看到應用程式仍然還在執行。
  • 確保獲取和釋放 Wake Lock 盡可能使用簡單的邏輯。如果 Wake Lock 邏輯和複雜的狀態機器、逾時、執行程式集區和/或回呼事件有關,那麼一旦這個邏輯裡有任何不明顯的錯誤,就可能會導致 Wake Lock 保留時間超出預期。這種錯誤非常難以診斷和偵錯。