上篇博客分析了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】??