部分喚醒鎖定停滯

部分 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。如果 Wake Lock 並未釋放,且保留時間超出預期,可能表示背景工作花費的時間比預期長。

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

  • dumpsys - 這項工具可提供裝置系統服務的狀態資訊。如果想檢視電源服務狀態和其中包含的 Wake Lock 清單,請執行 adb shell dumpsys power

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

最佳做法

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

如果必須使用部分 Wake Lock,請依循以下建議事項:

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