首先第一时间想到了是否可能是进程被挂起或手机休眠导致倒计时不走了,但测试了这么多手机都没有问题,然后就google了一下发现 CPU 休眠确实会导致Timer、各种轮询方式失效。然后就想了一下如何使 CPU 休眠来复现这个问题,终于,在试了五六个手机之后发现在 oppo r9s 上开启省电模式会导致倒计时变慢,跟用户的问题是一样的。在 oppo 手机开启省电模式后,将app切到后台然后锁屏,过了30秒钟再打开手机将应用切到前台,发现倒计时只走了7秒,等于说少走了23秒,所以显而易见, oppo 的rom对手机电量优化后锁屏约几秒钟后就会使cpu休眠。问题找到了,那么handler内部延迟消息是如何发送的,为什么会因为CPU休眠而导致发送的晚了?下面我们看代码:
for (;;) {
Messagemsg= queue.next(); // might blockif (msg == null) {
// No message indicates that the message queue is quitting.return;
// do something复制代码
looper循环中取消息会调用MessageQueue的next方法,接下来看next方法:
final long now = SystemClock.uptimeMillis();
// do something
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;复制代码
* Returns milliseconds since boot, not counting time spent in deep sleep.
*
@return
milliseconds of non-sleep uptime since boot.
@CriticalNative
native
public
static
long
uptimeMillis
()
;
复制代码
uptimeMillis是个native方法,我们直接看方法的注释:Returns milliseconds since boot, not counting time spent in deep sleep,即这个函数获取的是自系统启动以来的时间,但不包括系统休眠的时间。比如我发送一个延迟300秒的消息,但cpu在第50秒的时候休眠了,又过了100秒我打开手机将app切换到前台,这个时候cpu被唤醒了,那么休眠的这100秒就不算在uptimeMillis()方法的时间内,等于说实际上隔了400 handler才会收到消息,所以这就是用户反映倒计时暂停的根本原因。
手机为什么会休眠呢?当然是为了省电,关于Android的休眠机制,推荐大家看这篇文章,在这里我就不详细介绍了!
Android休眠机制
那么针对手机休眠,我们的倒计时如何实现呢?方案有两种,WakeLock和AlarmManager。
Wake Lock是android电源管理中很重要的机制。它是一种锁的机制, 只要有任务拿着这个锁, 系统就无法进入休眠, 可以被用户态进程和内核线程获得。这个锁可以是有超时的或者是没有超时的, 超时的锁会在时间过去以后自动解锁。如果没有锁了或者超时了, 内核就会启动标准linux的那套休眠机制机制来进入休眠。
AlarmManager是系统时钟管理器,说到休眠,就必须讲到AlarmManager。系统在进入休眠后,进程suspend,CPU进入休眠状态。可能有些任务或事情在系统休眠后也要能执行或完成,如闹钟,wx要能在后台正常接收消息等,这就就要借助alarm机制。
系统在休眠后,只有系统时钟RTC(Real Time Clock)在进行工作,RTC是一个独立的硬件时钟,可以在CPU休眠时正常运行,在预设的时间到达时,通过中断唤醒CPU。AlarmManager是Android系统封装的用于管理RTC的模块。
针对我们的情况,我采用了WakeLock,给任务加锁,只要这个锁存在,就不允许系统进行休眠!
app获取wake lock的代码如下: