Android7.0 Doze模式分析(三)alarm

上篇博客分析了Doze模式下WakeLock,这篇我们分析Alarm。
白名单 首先我们从白名单开始,在DeviceIdleController中会设置alarm的白名单。

public final class LocalService { public void setDeviceIdleUserWhitelist(int[] appids) { setDeviceIdleUserWhitelistImpl(appids); } }

最终就是设置到了mDeviceUserWhiteList中。

void setDeviceIdleUserWhitelistImpl(int[] appids) { synchronized (mLock) { mDeviceIdleUserWhitelist = appids; } }

然后我们再看AlarmMangerService设置Alarm的set函数中有如下代码,当uid不是应用的uid或者。在白名单中能找到该uid,会将flags或上FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED标志(这个标志就是应用白名单的象征)。
public void set(String callingPackage, int type, long triggerAtTime, long windowLength, long interval, int flags, PendingIntent operation, IAlarmListener directReceiver, String listenerTag, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) { ...... } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID || Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(callingUid)) >= 0)) { flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE; }setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver, listenerTag, flags, workSource, alarmClock, callingUid, callingPackage); }


setIdleUntil函数激活Alarm的Doze模式 在DeviceIdleController中是调用了AlarmManager的setIdleUntil函数激活了AlarmManagerService的Doze模式
public void setIdleUntil(int type, long triggerAtMillis, String tag, OnAlarmListener listener, Handler targetHandler) { setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, null, listener, tag, targetHandler, null, null); }

我们先再来看看set函数,这里面对Doze模式相关的flag做了很多处理
@Override public void set(String callingPackage, int type, long triggerAtTime, long windowLength, long interval, int flags, PendingIntent operation, IAlarmListener directReceiver, String listenerTag, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) { ......// No incoming callers can request either WAKE_FROM_IDLE or // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate. flags &= ~(AlarmManager.FLAG_WAKE_FROM_IDLE//先把这两个flag清了 | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED); // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm // manager when to come out of idle mode, which is only for DeviceIdleController. if (callingUid != Process.SYSTEM_UID) {//不是system不允许有FLAG_IDLE_UNTIL,也就是调用setIdleUntil也无效 flags &= ~AlarmManager.FLAG_IDLE_UNTIL; }// If this is an exact time alarm, then it can't be batched with other alarms. if (windowLength == AlarmManager.WINDOW_EXACT) { flags |= AlarmManager.FLAG_STANDALONE; }// If this alarm is for an alarm clock, then it must be standalone and we will // use it to wake early from idle if needed. if (alarmClock != null) { flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE; //clock自带FLAG_WAKE_FROM_IDLE // If the caller is a core system component or on the user's whitelist, and not calling // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED. // This means we will allow these alarms to go off as normal even while idle, with no // timing restrictions. } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID || Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(callingUid)) >= 0)) { flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; //白名单 flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE; }setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver, listenerTag, flags, workSource, alarmClock, callingUid, callingPackage); }

下面我们就看setImplLocked函数,看看和Doze模式相关的。如果是调用了setIdleUntil接口也就有FLAG_IDLE_UNTIL的flag,会调整期whenElapsed时间。然后设置mPendingIdleUntil变量代表已经进入Doze模式,当然进入Doze模式,肯定要rebatch所有的alarm。当有mPendingIdleUntil时(Doze模式),这个时候只有有FLAG_WAKE_FROM_IDLE、FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED、FLAG_ALLOW_WHILE_IDLE的flag的alarm才会被设置下去。其他的直接加入mPendingWhileIdleAlarms然后return了。自然设置的alarm就无效了。
private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) { if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {//如果是该flag,会设置下alarm的whenElapsed // This is a special alarm that will put the system into idle until it goes off. // The caller has given the time they want this to happen at, however we need // to pull that earlier if there are existing alarms that have requested to // bring us out of idle at an earlier time. if (mNextWakeFromIdle != null && a.whenElapsed > mNextWakeFromIdle.whenElapsed) { a.when = a.whenElapsed = a.maxWhenElapsed = mNextWakeFromIdle.whenElapsed; } // Add fuzz to make the alarm go off some time before the actual desired time. final long nowElapsed = SystemClock.elapsedRealtime(); final int fuzz = fuzzForDuration(a.whenElapsed-nowElapsed); if (fuzz > 0) { if (mRandom == null) { mRandom = new Random(); } final int delta = mRandom.nextInt(fuzz); a.whenElapsed -= delta; if (false) { Slog.d(TAG, "Alarm when: " + a.whenElapsed); Slog.d(TAG, "Delta until alarm: " + (a.whenElapsed-nowElapsed)); Slog.d(TAG, "Applied fuzz: " + fuzz); Slog.d(TAG, "Final delta: " + delta); Slog.d(TAG, "Final when: " + a.whenElapsed); } a.when = a.maxWhenElapsed = a.whenElapsed; }} else if (mPendingIdleUntil != null) {//有该变量代表之前调用过setIdleUntil接口了(进去Idle模式) // We currently have an idle until alarm scheduled; if the new alarm has // not explicitly stated it wants to run while idle, then put it on hold. if ((a.flags&(AlarmManager.FLAG_ALLOW_WHILE_IDLE | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | AlarmManager.FLAG_WAKE_FROM_IDLE)) == 0) { mPendingWhileIdleAlarms.add(a); //有这些flag的alarm,加入mPendingWhileIdleAlarms直接退出 return; } }int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0) ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed); if (whichBatch < 0) { Batch batch = new Batch(a); addBatchLocked(mAlarmBatches, batch); } else { Batch batch = mAlarmBatches.get(whichBatch); if (batch.add(a)) { // The start time of this batch advanced, so batch ordering may // have just been broken.Move it to where it now belongs. mAlarmBatches.remove(whichBatch); addBatchLocked(mAlarmBatches, batch); } }if (a.alarmClock != null) { mNextAlarmClockMayChange = true; }boolean needRebatch = false; if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) { mPendingIdleUntil = a; //设置mPendingIdleUntil代表进入Doze模式 mConstants.updateAllowWhileIdleMinTimeLocked(); needRebatch = true; //需要重新rebatch所有的alarm } else if ((a.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) { if (mNextWakeFromIdle == null || mNextWakeFromIdle.whenElapsed > a.whenElapsed) { mNextWakeFromIdle = a; // If this wake from idle is earlier than whatever was previously scheduled, // and we are currently idling, then we need to rebatch alarms in case the idle // until time needs to be updated. if (mPendingIdleUntil != null) { needRebatch = true; } } }if (!rebatching) {if (needRebatch) { rebatchAllAlarmsLocked(false); }rescheduleKernelAlarmsLocked(); updateNextAlarmClockLocked(); } }

前面我们也说到设置alarm的白名单有FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED的flag,这个flag可以在Doze模式下继续设置alarm。

退出Doze模式 退出Doze模式有两种:
1.就是设置的setIdleUntil的alarm到时间了。
2.第二种就是自己主动删除的这个alarm
我们先来看看setIdleUntil的alarm时间到了的情况,在triggerAlarmsLocked函数(到期的alarm发送)有如下代码,如果发送的alarm是mPendingIdleUntil(也就是setIdleUntil的alarm),就把mPendingIdleUntil清除,然后重新rebatch所有的alarm,然后就是调用restorePendingWhileIdleAlarmsLocked函数,把之前没有设置的alarm(放在mPendingWhileIdleAlarms),重新设置alarm,然后把mPendingWhileIdleAlarms清零。
if (mPendingIdleUntil == alarm) { mPendingIdleUntil = null; rebatchAllAlarmsLocked(false); restorePendingWhileIdleAlarmsLocked(); }

我们来看下restorePendingWhileIdleAlarmsLocked函数
void restorePendingWhileIdleAlarmsLocked() { // Bring pending alarms back into the main list. if (mPendingWhileIdleAlarms.size() > 0) { ArrayList alarms = mPendingWhileIdleAlarms; mPendingWhileIdleAlarms = new ArrayList<>(); final long nowElapsed = SystemClock.elapsedRealtime(); for (int i=alarms.size() - 1; i >= 0; i--) { Alarm a = alarms.get(i); reAddAlarmLocked(a, nowElapsed, false); //把mPendingWhileIdleAlarms重新设置 } }// Make sure we are using the correct ALLOW_WHILE_IDLE min time. mConstants.updateAllowWhileIdleMinTimeLocked(); // Reschedule everything. rescheduleKernelAlarmsLocked(); updateNextAlarmClockLocked(); // And send a TIME_TICK right now, since it is important to get the UI updated. try { mTimeTickSender.send(); } catch (PendingIntent.CanceledException e) { } }

还有一种情况是主动的删除了setIdleUntil设置的alarm,是调用了如下接口

@Override public void remove(PendingIntent operation, IAlarmListener listener) { if (operation == null && listener == null) { Slog.w(TAG, "remove() with no intent or listener"); return; }synchronized (mLock) { removeLocked(operation, listener); } }

removeLocked函数,先从mAlarmBatchs中删除,再从mPendingWhileIdleAlarms删除,如果从mAlarmBatchs中删除了alarm还要看删除的是否是mPendingIdleUntil,如果是要重新rebatch所有alarm,以及调用restorePendingWhileIdleAlarmsLocked函数恢复mPendingWhileIdleAlarms中的alarm。
private void removeLocked(PendingIntent operation, IAlarmListener directReceiver) { boolean didRemove = false; for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { Batch b = mAlarmBatches.get(i); didRemove |= b.remove(operation, directReceiver); if (b.size() == 0) { mAlarmBatches.remove(i); } } for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) { if (mPendingWhileIdleAlarms.get(i).matches(operation, directReceiver)) { // Don't set didRemove, since this doesn't impact the scheduled alarms. mPendingWhileIdleAlarms.remove(i); } }if (didRemove) { boolean restorePending = false; if (mPendingIdleUntil != null && mPendingIdleUntil.matches(operation, directReceiver)) { mPendingIdleUntil = null; restorePending = true; } if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(operation, directReceiver)) { mNextWakeFromIdle = null; } rebatchAllAlarmsLocked(true); if (restorePending) { restorePendingWhileIdleAlarmsLocked(); } updateNextAlarmClockLocked(); } }

最后mPendingIdleUntil删除了,那么也就退出了Doze模式,所有的alarm设置下来都会正常了。





【Android7.0 Doze模式分析(三)alarm】??

    推荐阅读