部分唤醒锁定卡住

部分唤醒锁定是 PowerManager API 中的一种机制,可让开发者在设备的显示屏关闭后(无论是由于系统超时还是用户按下电源按钮)继续保持 CPU 运行。您的应用会通过调用带有 PARTIAL_WAKE_LOCK 标记的 acquire() 来获取部分唤醒锁定。当您的应用在后台运行时,如果部分唤醒锁定保持了较长时间,则会变为卡住状态(用户看不到应用的任何部分)。这种情况会耗尽设备的电量,因为它会阻止设备进入低功耗状态。部分唤醒锁定仅应在必要时使用,并且在不再需要时立即释放。

如果您的应用的部分唤醒锁定卡住,您可以使用本页中的指南来诊断和解决问题。

检测问题

您可能并不总是知道应用的部分唤醒锁定卡住了。如果您已发布了您的应用,Android Vitals 可以帮助您认识到这个问题。

Android Vitals

当您的应用出现部分唤醒锁定卡住时,Android Vitals 可通过 Play 管理中心提醒您,从而帮助您改进应用性能。Android Vitals 报告部分唤醒锁定卡住的条件是在以下任一时段内至少发生了一次时长达 1 小时的部分唤醒锁定:

  • 所有情况下至少 0.70% 的电池工作时段。
  • 仅在后台运行时至少 0.10% 的电池工作时段。

“电池工作时段”是指两次电池充满电的时间间隔。显示的电池工作时段是针对应用的所有测量用户的汇总值。要了解 Google Play 如何收集 Android Vitals 数据,请参阅 Play 管理中心文档。

当您发现应用存在过多的部分唤醒锁定卡住时,下一步就是解决问题。

修复问题

唤醒锁定是在 Android 平台的早期版本中引入的,但随着时间的推移,许多以前需要唤醒锁定的用例现在都改用像 WorkManager 这样的新功能且效果更好。

本部分包含有关解决唤醒锁定的提示,但从长远来看,请考虑迁移您的应用,以遵循最佳做法部分中的建议。

找出并修复代码中获取唤醒锁定的位置,例如 newWakeLock(int, String)WakefulBroadcastReceiver。请参考以下提示:

  • 我们建议您在唤醒锁定标签名称中包含您的软件包、类或方法名称,以便在源代码中轻松识别创建了唤醒锁定的位置。下面提供了更多相关提示:

    • 在名称中省去任何个人身份信息 (PII),例如电子邮件地址。否则,设备将记录 _UNKNOWN 而不是唤醒锁定名称。
    • 请勿以编程方式(例如通过调用 getName())获取类或方法名称,因为它可能会被 Proguard 混淆。取而代之,应使用硬编码字符串。
    • 请勿向唤醒锁定标签添加计数器或唯一标识符。系统将无法汇总通过同一方法创建的唤醒锁定,因为它们都具有唯一标识符。
  • 确保您的代码会释放其获取的所有唤醒锁定。这比确保对 acquire() 的每次调用都有对 release() 的对应调用要更复杂。以下是一个由于未捕获的异常而导致未释放唤醒锁定的示例:

    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();
                }
            }
  • 确保唤醒锁定在不再需要时立即释放。例如,如果您使用唤醒锁定来促使后台任务完成,请确保在任务完成后进行释放。如果唤醒锁定的保持时间超出预期而没有释放,则可能意味着后台任务花费的时间超出预期。

修复代码中的问题后,请使用以下 Android 工具验证您的应用是否正确释放唤醒锁定:

  • dumpsys - 该工具提供有关设备上系统服务状态的信息。要查看包含唤醒锁定列表的电源服务状态,请运行 adb shell dumpsys power

  • Battery Historian - 该工具用于将 Android 错误报告的输出解析为电源相关事件的可视化表示。

最佳做法

一般来说,您的应用应避免部分唤醒锁定,因为它太容易耗尽用户的电量。Android 为以前需要部分唤醒锁定的几乎所有用例提供备用 API。部分唤醒锁定的一个剩余用例是确保在屏幕关闭时继续播放音乐应用。如果您使用唤醒锁定来运行任务,请考虑后台处理指南中介绍的替代方法。

如果必须使用部分唤醒锁定,请遵循以下建议:

  • 确保应用的某个部分保留在前台。例如,如果您需要运行服务,则改为启动前台服务。这会直观地向用户表明您的应用仍在运行。
  • 确保获取和释放唤醒锁定的逻辑尽可能简单。当唤醒锁定逻辑与复杂的状态机、超时、执行器池和/或回调事件关联时,该逻辑中的任何细微错误都可能导致唤醒锁定的保持时间超出预期。这些错误很难诊断和调试。