Android4.4之后休眠状态下Alarm不准时的问题

贵有恒,何必三更起、五更眠、最无益,只怕一日曝、十日寒。这篇文章主要讲述Android4.4之后休眠状态下Alarm不准时的问题相关的知识,希望能为你提供帮助。
android4.4及之后休眠状态下Alarm不准时的问题 为了减轻功耗, 延长电池使用时间, Android 4.4及之后的版本采用非精准闹钟机制, 以及休眠状态下的wakeup类型的alarm不会实时唤醒设备, 而会等到机器被物理唤醒时才触发alarmAndroid 6.0提供了新的api: setExactAndAllowWhileIdle()部分解决这个问题, 但依然不能在休眠状态下精准唤醒。
关于alarm api 的支持与使用请参考下图:

Android4.4之后休眠状态下Alarm不准时的问题

文章图片

(图片来源: https://plus.google.com/+ AndroidDevelopers/posts/GdNrQciPwqo)
此外, 应用层面不要使用不持有wakelockBroadcastReceiver, 而要使用WakefulBroadcastReceiver
为了修复这个问题, 以5.0.2版本为例, 需要修改内核下的alarm-dev.c以及framework下的AlarmManagerService。
  • 内核 alarm-dev.c: 其原因是使用普通的static struct wakeup_source alarm_wake_lock; , 而非带有WAKE_LOCK_SUSPEND类别信息的struct wake_lock, 并且需要使用带有android_前缀的wakeup lock相关函数。即:
    android_wake_lock_init android_wake_lock_destroy android_wake_lock android_wake_lock_timeout android_wake_unlock

    而非普通的wake lock操作函数:
    wake_lock_init wake_lock_destroy wake_lock wake_lock_timeout wake_unlock

  • framework AlarmManagerService.java: 需要将wakeup类型的alarm特殊处理: 即精准闹铃。在setImpl中添加如下代码:
    public boolean isWakeup(int type) { return (type & TYPE_NONWAKEUP_MASK) = = 0; }void setImpl(int type, long triggerAtTime, long windowLength, long interval, PendingIntent operation, boolean isStandalone, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) { if (operation = = null) { Slog.w(TAG, " set/setRepeating ignored because there is no intent" ); return; }if (isWakeup(type)) { windowLength = AlarmManager.WINDOW_EXACT; isStandalone = true; }

    并在alarm被触发时多取几个满足条件的batch做处理:
    boolean triggerAlarmsLocked(ArrayList< Alarm> triggerList, final long nowELAPSED, final long nowRTC) { boolean hasWakeup = false; // batches are temporally sorted, so we need only pull from the // start of the list until we either empty it or hit a batch // that is not yet deliverableArrayList< Alarm> repeatList = new ArrayList< Alarm> (); ListIterator< Batch> it = mAlarmBatches.listIterator(); while (it.hasNext()) { Batch batch = it.next(); if (batch.start > nowELAPSED) { // Everything else is scheduled for the future break; }// We will (re)schedule some alarms now; don' t let that interfere // with delivery of this current batch it.remove(); final int N = batch.size(); for (int i = 0; i < N; i+ + ) { Alarm alarm = batch.get(i); alarm.count = 1; triggerList.add(alarm); // Recurring alarms may have passed several alarm intervals while the // phone was asleep or off, so pass a trigger count when sending them. if (alarm.repeatInterval > 0) { // this adjustment will be zero if we' re late by // less than one full repeat interval alarm.count + = (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval; // Also schedule its next recurrence repeatList.add(alarm); }if (alarm.wakeup) { hasWakeup = true; mNextWakeup = 0; }// We removed an alarm clock. Let the caller recompute the next alarm clock. if (alarm.alarmClock != null) { mNextAlarmClockMayChange = true; } } }if (repeatList.size() > 0) { for (Alarm alarm : repeatList) { final long delta = alarm.count * alarm.repeatInterval; final long nextElapsed = alarm.whenElapsed + delta; setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength, maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval), alarm.repeatInterval, alarm.operation, alarm.windowLength = = AlarmManager.WINDOW_EXACT, true, alarm.workSource, alarm.alarmClock, alarm.userId); } }// This is a new alarm delivery set; bump the sequence number to indicate that // all apps' alarm delivery classes should be recalculated. mCurrentSeq+ + ; calculateDeliveryPriorities(triggerList); Collections.sort(triggerList, mAlarmDispatchComparator); return hasWakeup; }

  • 应用层面使用WakefulBroadcastReceiver:
    import android.support.v4.content.WakefulBroadcastReceiver; public class AutoUpdateAlarmReceiver extends WakefulBroadcastReceiver {@ Override public void onReceive(Context context, Intent intent) { // Start the service, keeping the device awake while the service is // launching. This is the Intent to deliver to the service. Intent service = new Intent(context, AutoUpdateIntentService.class); service.setAction(intent.getAction()); startWakefulService(context, service); } }

    【Android4.4之后休眠状态下Alarm不准时的问题】相应的IntentService如下所示:
    public class AutoUpdateIntentService extends IntentService { public AutoUpdateIntentService() { super(" AutoUpdateIntentService" ); }@ Override protected void onHandleIntent(Intent intent) { String action = intent.getAction(); // do your work here. // ...// Release the wake lock provided by the WakefulBroadcastReceiver. AutoUpdateAlarmReceiver.completeWakefulIntent(intent); } }


    推荐阅读