(一)Alarm的设置
这里简单画了一下,从framework到native再到kernel的大概流程。
1. 应用层
我们先看一下应用层设置alarm的方法。
1. OnAlarmListener
第一个方法是实现OnAlarmListener接口,重写onAlarm方法。
public void setAlarm() {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, 100, "AlarmHelper", onAlarmListener, new Handler());
}
private AlarmManager.OnAlarmListener onAlarmListener = new AlarmManager.OnAlarmListener() {
@Override
public void onAlarm() {
//……
}
};
这是一个接口,只有一个回调方法比较简单。但是需要注意的是循环的alarm是不能使用这个方式的,也就是setRepeating接口不支持回调。
/**
* Direct-notification alarms: the requester must be running continuously from the
* time the alarm is set to the time it is delivered, or delivery will fail. Only
* one-shot alarms can be set using this mechanism, not repeating alarms.
*/
public interface OnAlarmListener {
/**
* Callback method that is invoked by the system when the alarm time is reached.
*/
public void onAlarm();
}
2. PendingIntent
第二个方法是通过PendingIntent。PendingIntent提供了很多静态方法,让我们可以通过PendingIntent来启动不同类型的组件。
比如下面的代码通过PendingIntent.getActivity方法,alarm触发时可以启动一个新的Activity页面。
public static void setAlarmActivity(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, FullscreenActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
alarmManager.setExact(AlarmManager.RTC_WAKEUP, 100, pendingIntent);
Log.d(TAG, "setAlarmActivity");
}
2. framework层
(1)客户端AlarmManager
我们看到,不管通过哪个API接口,最终实现都是在setImpl方法:
private void setImpl(@AlarmType int type, long triggerAtMillis, long windowMillis,
long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener,
String listenerTag, Handler targetHandler, WorkSource workSource,
AlarmClockInfo alarmClock) {
if (triggerAtMillis < 0) {
/* NOTYET
if (mAlwaysExact) {
// Fatal error for KLP+ apps to use negative trigger times
throw new IllegalArgumentException("Invalid alarm trigger time "
+ triggerAtMillis);
}
*/
triggerAtMillis = 0;
}
ListenerWrapper recipientWrapper = null;
if (listener != null) {//1
synchronized (AlarmManager.class) {
if (sWrappers == null) {
sWrappers = new ArrayMap<OnAlarmListener, ListenerWrapper>();//2
}
recipientWrapper = sWrappers.get(listener);
// no existing wrapper => build a new one
if (recipientWrapper == null) {//3
recipientWrapper = new ListenerWrapper(listener);
sWrappers.put(listener, recipientWrapper);
}
}
final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler;
recipientWrapper.setHandler(handler);//4
}
try {
mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
operation, recipientWrapper, listenerTag, workSource, alarmClock);//5
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
注释1 如果开发者定义了回调listener,则在AlarmManager中保存回调;
注释2 AlarmManager内部维护了一个ArrayMap结构,每个listener实例对应一个Runnable对象,这种方式有点类似AsyncTask,它的内部维护了一个ArrayDeque数据结构用来保存运行任务。这里对sWrappers 进行初始化;
注释3 如果该listener实例不存在,创建一个新ListenerWrapper对象并插入map;
注释4 指定处理任务的Handler,如果开发者未指定,则默认是主线程处理;
注释5 通过IAlarmManager的一个Binder调用AlarmManagerService中的方法,实际设置alarm是在服务端。
ListenerWrapper实际上是一个IBinder对象,实现了Runnable接口和AIDL接口IAlarmListener 。
IAlarmListener 声明了oneway interface,表示服务端请求时不需要等待应答,可以直接返回。实际上我们也可以理解,因为doAlarm本身不会返回数据,而且可能是一个耗时操作。
final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
//……
}
oneway interface IAlarmListener {
void doAlarm(in IAlarmCompleteListener callback);
}
(2)服务端AlarmManagerService
framework层有两个比较重要的代码逻辑,一个是修改alarm的Flag,一个是对相近的alarm进行对齐。
先看下flag的修改和限制:
1.不允许应用设置WAKE_FROM_IDLE和ALLOW_WHILE_IDLE_UNRESTRICTED标志;
2.只有系统应用能设置FLAG_IDLE_UNTIL标志;
3.如果是精准触发的alarm,不能被对齐,必须添加FLAG_STANDALONE标志;
4.如果这是一个alarmClock类型,也不会被对齐,会添加FLAG_STANDALONE和FLAG_WAKE_FROM_IDLE标志;
5.如果是系统应用,或者是添加了省电白名单的应用,会添加FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED和FLAG_ALLOW_WHILE_IDLE标志。
private final IBinder mService = new IAlarmManager.Stub() {
@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
| AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);//1
// 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) {
flags &= ~AlarmManager.FLAG_IDLE_UNTIL;//2
}
// 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;//3
}
// 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;//4
// 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
|| UserHandle.isSameApp(callingUid, mSystemUiUid)
|| ((mAppStateTracker != null)
&& mAppStateTracker.isUidPowerSaveUserWhitelisted(callingUid)))) {//5
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);
}
//……
}
然后看下alarm对齐的过程。
AlarmManagerService内部维护了一个ArrayList数组,用来存储所有的Batch。
final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
而每个Batch内部也各自维护了一个ArrayList数组,用来存储Alarm。
final class Batch {
long start; // These endpoints are always in ELAPSED
long end;
int flags; // Flags for alarms, such as FLAG_STANDALONE.
final ArrayList<Alarm> alarms = new ArrayList<Alarm>();
//……
}
开发者设置alarm之后,都会通过insertAndBatchAlarmLocked方法进行对齐。
注释1 如果flag标志了FLAG_STANDALONE,该alarm独自占有一个Batch,不会跟其他alarm对齐;
注释2 通过attemptCoalesceLocked方法寻找该alarm所属的Batch;
注释3 如果是新增了一个独立Batch,通过addBatchLocked方法将新的Batch插入ArrayList数组;
注释4 如果往旧Batch中插入alarm,则需要重新排序。
private void insertAndBatchAlarmLocked(Alarm alarm) {
final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1//1
: attemptCoalesceLocked(alarm.whenElapsed, alarm.maxWhenElapsed);//2
if (whichBatch < 0) {
addBatchLocked(mAlarmBatches, new Batch(alarm));//3
} else {
final Batch batch = mAlarmBatches.get(whichBatch);
if (batch.add(alarm)) {//4
// 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);
}
}
}
// Return the index of the matching batch, or -1 if none found.
int attemptCoalesceLocked(long whenElapsed, long maxWhen) {
final int N = mAlarmBatches.size();
for (int i = 0; i < N; i++) {
Batch b = mAlarmBatches.get(i);
if ((b.flags&AlarmManager.FLAG_STANDALONE) == 0 && b.canHold(whenElapsed, maxWhen)) {
return i;
}
}
return -1;
}
boolean canHold(long whenElapsed, long maxWhen) {
return (end >= whenElapsed) && (start <= maxWhen);
}
boolean add(Alarm alarm) {
boolean newStart = false;
// narrows the batch if necessary; presumes that canHold(alarm) is true
int index = Collections.binarySearch(alarms, alarm, sIncreasingTimeOrder);
if (index < 0) {
index = 0 - index - 1;
}
alarms.add(index, alarm);
if (alarm.listener == mTimeTickTrigger) {
mLastTickAdded = mInjector.getCurrentTimeMillis();
}
if (DEBUG_BATCH) {
Slog.v(TAG, "Adding " + alarm + " to " + this);
}
if (alarm.whenElapsed > start) {
start = alarm.whenElapsed;
newStart = true;
}
if (alarm.maxWhenElapsed < end) {
end = alarm.maxWhenElapsed;
}
flags |= alarm.flags;
if (DEBUG_BATCH) {
Slog.v(TAG, " => now " + this);
}
return newStart;
}
代码逻辑不是很复杂,这里画了一张图。
3. native层
接下来看看native方法:
static jint android_server_AlarmManagerService_set(JNIEnv*, jobject, jlong nativeData, jint type, jlong seconds, jlong nanoseconds)
{
AlarmImpl *impl = reinterpret_cast<AlarmImpl *>(nativeData);
struct timespec ts;
ts.tv_sec = seconds;
ts.tv_nsec = nanoseconds;
const int result = impl->set(type, &ts);//1
if (result < 0)
{
ALOGE("Unable to set alarm to %lld.%09lld: %s\n",
static_cast<long long>(seconds),
static_cast<long long>(nanoseconds), strerror(errno));
}
return result >= 0 ? 0 : errno;
}
注释1处调用了AlarmImpl的set方法。
int AlarmImpl::set(int type, struct timespec *ts)
{
if (static_cast<size_t>(type) > ANDROID_ALARM_TYPE_COUNT) {
errno = EINVAL;
return -1;
}
if (!ts->tv_nsec && !ts->tv_sec) {
ts->tv_nsec = 1;
}
/* timerfd interprets 0 = disarm, so replace with a practically
equivalent deadline of 1 ns */
struct itimerspec spec;
memset(&spec, 0, sizeof(spec));
memcpy(&spec.it_value, ts, sizeof(spec.it_value));//1
return timerfd_settime(fds[type], TFD_TIMER_ABSTIME, &spec, NULL);//2
}
注释1处将timespec时间赋值到临时变量spec;
注释2处通过timerfd_settime设置好定时器。
int timerfd_settime(int fd, int flags, const struct itimerspec *new, struct itimerspec *old)
{
return syscall(SYS_timerfd_settime, fd, flags, new, old);
}
这里的syscall是一个系统调用,内核源码不太了解,大概原理是,当计时器到时会产生一个硬件中断,通知上层触发alarm功能。
(二)Alarm的触发
AlarmManagerService维持了一个常驻线程,通过while(true)循环,不断取出需要触发的Alarm列表,进行alarm事件分发。这个地方有点像Handler的消息队列。
private class AlarmThread extends Thread
{
private int mFalseWakeups;
private int mWtfThreshold;
public AlarmThread()
{
super("AlarmManager");
mFalseWakeups = 0;
mWtfThreshold = 100;
}
public void run()
{
ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
while (true)
{
int result = mInjector.waitForAlarm();//1
final long nowRTC = mInjector.getCurrentTimeMillis();//2
final long nowELAPSED = mInjector.getElapsedRealtime();//3
synchronized (mLock) {
mLastWakeup = nowELAPSED;//4
}
if (result == 0) {
Slog.wtf(TAG, "waitForAlarm returned 0, nowRTC = " + nowRTC
+ ", nowElapsed = " + nowELAPSED);
}
triggerList.clear();
if ((result & TIME_CHANGED_MASK) != 0) {//5
// The kernel can give us spurious time change notifications due to
// small adjustments it makes internally; we want to filter those out.
final long lastTimeChangeClockTime;
final long expectedClockTime;
synchronized (mLock) {
lastTimeChangeClockTime = mLastTimeChangeClockTime;
expectedClockTime = lastTimeChangeClockTime
+ (nowELAPSED - mLastTimeChangeRealtime);
}
if (lastTimeChangeClockTime == 0 || nowRTC < (expectedClockTime-1000)
|| nowRTC > (expectedClockTime+1000)) {//6
// The change is by at least +/- 1000 ms (or this is the first change),
// let's do it!
if (DEBUG_BATCH) {
Slog.v(TAG, "Time changed notification from kernel; rebatching");
}
// StatsLog requires currentTimeMillis(), which == nowRTC to within usecs.
StatsLog.write(StatsLog.WALL_CLOCK_TIME_SHIFTED, nowRTC);
removeImpl(null, mTimeTickTrigger);
removeImpl(mDateChangeSender, null);
rebatchAllAlarms();
mClockReceiver.scheduleTimeTickEvent();
mClockReceiver.scheduleDateChangedEvent();
synchronized (mLock) {
mNumTimeChanged++;
mLastTimeChangeClockTime = nowRTC;
mLastTimeChangeRealtime = nowELAPSED;
}
Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
// The world has changed on us, so we need to re-evaluate alarms
// regardless of whether the kernel has told us one went off.
result |= IS_WAKEUP_MASK;
}
}
if (result != TIME_CHANGED_MASK) {//7
// If this was anything besides just a time change, then figure what if
// anything to do about alarms.
synchronized (mLock) {
if (localLOGV) Slog.v(
TAG, "Checking for alarms... rtc=" + nowRTC
+ ", elapsed=" + nowELAPSED);
if (WAKEUP_STATS) {
if ((result & IS_WAKEUP_MASK) != 0) {
long newEarliest = nowRTC - RECENT_WAKEUP_PERIOD;
int n = 0;
for (WakeupEvent event : mRecentWakeups) {
if (event.when > newEarliest) break;
n++; // number of now-stale entries at the list head
}
for (int i = 0; i < n; i++) {
mRecentWakeups.remove();
}
recordWakeupAlarms(mAlarmBatches, nowELAPSED, nowRTC);
}
}
mLastTrigger = nowELAPSED;
boolean hasWakeup = triggerAlarmsLocked(triggerList, nowELAPSED);//8
if (!hasWakeup && checkAllowNonWakeupDelayLocked(nowELAPSED)) {
// if there are no wakeup alarms and the screen is off, we can
// delay what we have so far until the future.
if (mPendingNonWakeupAlarms.size() == 0) {
mStartCurrentDelayTime = nowELAPSED;
mNextNonWakeupDeliveryTime = nowELAPSED
+ ((currentNonWakeupFuzzLocked(nowELAPSED)*3)/2);
}
mPendingNonWakeupAlarms.addAll(triggerList);
mNumDelayedAlarms += triggerList.size();
rescheduleKernelAlarmsLocked();//9
updateNextAlarmClockLocked();//10
} else {
// now deliver the alarm intents; if there are pending non-wakeup
// alarms, we need to merge them in to the list. note we don't
// just deliver them first because we generally want non-wakeup
// alarms delivered after wakeup alarms.
if (mPendingNonWakeupAlarms.size() > 0) {
calculateDeliveryPriorities(mPendingNonWakeupAlarms);
triggerList.addAll(mPendingNonWakeupAlarms);
Collections.sort(triggerList, mAlarmDispatchComparator);
final long thisDelayTime = nowELAPSED - mStartCurrentDelayTime;
mTotalDelayTime += thisDelayTime;
if (mMaxDelayTime < thisDelayTime) {
mMaxDelayTime = thisDelayTime;
}
mPendingNonWakeupAlarms.clear();
}
if (mLastTimeChangeRealtime != nowELAPSED && triggerList.isEmpty()) {
if (++mFalseWakeups >= mWtfThreshold) {
Slog.wtf(TAG, "Too many (" + mFalseWakeups
+ ") false wakeups, nowElapsed=" + nowELAPSED);
if (mWtfThreshold < 100_000) {
mWtfThreshold *= 10;
} else {
mFalseWakeups = 0;
}
}
}
final ArraySet<Pair<String, Integer>> triggerPackages =
new ArraySet<>();
for (int i = 0; i < triggerList.size(); i++) {
final Alarm a = triggerList.get(i);
if (!isExemptFromAppStandby(a)) {
triggerPackages.add(Pair.create(
a.sourcePackage, UserHandle.getUserId(a.creatorUid)));
}
}
deliverAlarmsLocked(triggerList, nowELAPSED);
reorderAlarmsBasedOnStandbyBuckets(triggerPackages);
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
}
}
} else {
// Just in case -- even though no wakeup flag was set, make sure
// we have updated the kernel to the next alarm time.
synchronized (mLock) {
rescheduleKernelAlarmsLocked();
}
}
}
}
}
注释1 这里调用了native方法,可能会阻塞等待;
注释2和3 获取了两个时间,RTC是手机系统时间,是用户可以修改的;ELAPSED是开机时间,用户不可修改;
//该时间从系统开机开始计算
long getElapsedRealtime() {
return SystemClock.elapsedRealtime();
}
//该时间从1970年1月1日开始计算
long getCurrentTimeMillis() {
return System.currentTimeMillis();
}
注释4 记录最后一次唤醒时间;
注释5 如果用户调节了RTC时间,驱动层返回的结果会带上TIME_CHAGNED_MASK;
注释6 如果调节的时间大于1s,或者是第一次调整时间,需要作出兼容操作;
注释7 如果除了时间调整,还有其他事件,比如唤醒类alarm,需要进一步处理;
注释8 通过triggerAlarmsLocked方法获取需要唤醒的alarm列表;
注释9 把新的alarm触发时间重新写入kernel
注释10更新下一个alarm状态
上图中两种颜色分别代表alarm的两种回调,一种是PendingIntent,一种是通过OnAlarmListener。具体可以看看deliverLocked方法:
public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
final long workSourceToken = ThreadLocalWorkSource.setUid(
getAlarmAttributionUid(alarm));
try {
if (alarm.operation != null) {
// PendingIntent alarm
mSendCount++;
try {
alarm.operation.send(getContext(), 0,
mBackgroundIntent.putExtra(
Intent.EXTRA_ALARM_COUNT, alarm.count),
mDeliveryTracker, mHandler, null,
allowWhileIdle ? mIdleOptions : null);
if (alarm.repeatInterval == 0) {
// Keep the listener for repeating alarms until they get cancelled
mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER,
alarm.operation).sendToTarget();
}
} catch (PendingIntent.CanceledException e) {
if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this
// is a repeating alarm, so toss it
removeImpl(alarm.operation, null);
}
// No actual delivery was possible, so the delivery tracker's
// 'finished' callback won't be invoked. We also don't need
// to do any wakelock or stats tracking, so we have nothing
// left to do here but go on to the next thing.
mSendFinishCount++;
return;
}
} else {
// Direct listener callback alarm
mListenerCount++;
if (RECORD_ALARMS_IN_HISTORY) {
if (alarm.listener == mTimeTickTrigger) {
mTickHistory[mNextTickHistory++] = nowELAPSED;
if (mNextTickHistory >= TICK_HISTORY_DEPTH) {
mNextTickHistory = 0;
}
}
}
try {
if (DEBUG_LISTENER_CALLBACK) {
Slog.v(TAG, "Alarm to uid=" + alarm.uid
+ " listener=" + alarm.listener.asBinder());
}
alarm.listener.doAlarm(this);
mHandler.sendMessageDelayed(
mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
alarm.listener.asBinder()),
mConstants.LISTENER_TIMEOUT);
} catch (Exception e) {
if (DEBUG_LISTENER_CALLBACK) {
Slog.i(TAG, "Alarm undeliverable to listener "
+ alarm.listener.asBinder(), e);
}
// As in the PendingIntent.CanceledException case, delivery of the
// alarm was not possible, so we have no wakelock or timeout or
// stats management to do. It threw before we posted the delayed
// timeout message, so we're done here.
mListenerFinishCount++;
return;
}
}
} finally {
ThreadLocalWorkSource.restore(workSourceToken);
}
// The alarm is now in flight; now arrange wakelock and stats tracking
if (DEBUG_WAKELOCK) {
Slog.d(TAG, "mBroadcastRefCount -> " + (mBroadcastRefCount + 1));
}
if (mBroadcastRefCount == 0) {
setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true);
mWakeLock.acquire();
mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget();
}
final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED);
mInFlight.add(inflight);
mBroadcastRefCount++;
if (inflight.isBroadcast()) {
notifyBroadcastAlarmPendingLocked(alarm.uid);
}
if (allowWhileIdle) {
// Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED);
if ((mAppStateTracker == null)
|| mAppStateTracker.isUidInForeground(alarm.creatorUid)) {
mUseAllowWhileIdleShortTime.put(alarm.creatorUid, true);
} else {
mUseAllowWhileIdleShortTime.put(alarm.creatorUid, false);
}
if (RECORD_DEVICE_IDLE_ALARMS) {
IdleDispatchEntry ent = new IdleDispatchEntry();
ent.uid = alarm.uid;
ent.pkg = alarm.packageName;
ent.tag = alarm.statsTag;
ent.op = "DELIVER";
ent.elapsedRealtime = nowELAPSED;
mAllowWhileIdleDispatches.add(ent);
}
}
if (!isExemptFromAppStandby(alarm)) {
final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage,
UserHandle.getUserId(alarm.creatorUid));
mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage,
UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
}
final BroadcastStats bs = inflight.mBroadcastStats;
bs.count++;
if (bs.nesting == 0) {
bs.nesting = 1;
bs.startTime = nowELAPSED;
} else {
bs.nesting++;
}
final FilterStats fs = inflight.mFilterStats;
fs.count++;
if (fs.nesting == 0) {
fs.nesting = 1;
fs.startTime = nowELAPSED;
} else {
fs.nesting++;
}
if (alarm.type == ELAPSED_REALTIME_WAKEUP
|| alarm.type == RTC_WAKEUP) {
bs.numWakeup++;
fs.numWakeup++;
ActivityManager.noteWakeupAlarm(
alarm.operation, alarm.workSource, alarm.uid, alarm.packageName,
alarm.statsTag);
}
}
}
Idle状态
我们应该注意到有两个比较特殊的alarm,是AlarmManagerService特地保留的。
mNextWakeFromIdle记录了下一次从doze中唤醒的时间;
mPendingIdleUntil记录了维持doze直到结束的时间。
这两个概念好像是一样的,有点难理解,实际上是这样的:
mPendingIdleUntil是Doze自己定义的唤醒时间,也就是系统自己退出的睡眠。
而mNextWakeFromIdle是由别人设置的唤醒时间,可以理解为提前退出睡眠。
Alarm mPendingIdleUntil = null;
Alarm mNextWakeFromIdle = null;
前面介绍过setImpl方法中会对alarm的Flag进行过滤校验,有两个flag比较特殊,FLAG_IDLE_UNTIL和FLAG_WAKE_FROM_IDLE,这里会对他们进行过滤。
// 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
| 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) {
flags &= ~AlarmManager.FLAG_IDLE_UNTIL;
}
虽然这里会进行过滤,但是有一个接口是setAlarmClock,我们通过这个方法就可以绕过doze。以为系统会给alarmClock类型的任务添加FLAG_WAKE_FROM_IDLE标志。
接着看看setImplLocked中是怎么处理的。
注释1 如果已经有人设置了提前退出doze的时间,那么mPendingIdleUntil不能比这个时间晚。
注释2 对触发时间做一个调整,每次比实际触发的时间要提前一个随机数。
注释3 如果当前存在一个mPendingIdleUntil,说明系统处于doze,alarm会被暂挂起来,存储到mPendingWhileIdleAlarms列表中。
注释4 更新mPendingIdleUntil,下面需要对Batch重新对齐。
private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
// 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;//1
}
// Add fuzz to make the alarm go off some time before the actual desired time.
final long nowElapsed = mInjector.getElapsedRealtime();
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;//2
a.when = a.maxWhenElapsed = a.whenElapsed;
}
}
else if (mPendingIdleUntil != null) {
// 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);//3
return;
}
}
//……
if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
//……
if ((mPendingIdleUntil != a) && (mPendingIdleUntil != null)) {
Slog.wtfStack(TAG, "setImplLocked: idle until changed from " + mPendingIdleUntil
+ " to " + a);
}
mPendingIdleUntil = a;//4
needRebatch = true;
}
//……
接下来看看如何对Batch进行重新调整。
注释1 doValidate这个参数在系统RTC时间调整或者是alarm取消的情况下为true,这里可以先不看。
注释2 遍历每一个Batch中的每一个alarm,重新对齐。
void rebatchAllAlarmsLocked(boolean doValidate) {//1
final long start = mStatLogger.getTime();
final int oldCount =
getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms);
final boolean oldHasTick = haveBatchesTimeTickAlarm(mAlarmBatches)
|| haveAlarmsTimeTickAlarm(mPendingWhileIdleAlarms);
ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
mAlarmBatches.clear();
Alarm oldPendingIdleUntil = mPendingIdleUntil;
final long nowElapsed = mInjector.getElapsedRealtime();
final int oldBatches = oldSet.size();
for (int batchNum = 0; batchNum < oldBatches; batchNum++) {
Batch batch = oldSet.get(batchNum);
final int N = batch.size();
for (int i = 0; i < N; i++) {
reAddAlarmLocked(batch.get(i), nowElapsed, doValidate);//2
}
}
if (oldPendingIdleUntil != null && oldPendingIdleUntil != mPendingIdleUntil) {
Slog.wtf(TAG, "Rebatching: idle until changed from " + oldPendingIdleUntil
+ " to " + mPendingIdleUntil);
if (mPendingIdleUntil == null) {
// Somehow we lost this... we need to restore all of the pending alarms.
restorePendingWhileIdleAlarmsLocked();
}
}
final int newCount =
getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms);
final boolean newHasTick = haveBatchesTimeTickAlarm(mAlarmBatches)
|| haveAlarmsTimeTickAlarm(mPendingWhileIdleAlarms);
if (oldCount != newCount) {
Slog.wtf(TAG, "Rebatching: total count changed from " + oldCount + " to " + newCount);
}
if (oldHasTick != newHasTick) {
Slog.wtf(TAG, "Rebatching: hasTick changed from " + oldHasTick + " to " + newHasTick);
}
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start);
}
最后看一下对每一个alarm重新对齐的过程,这里实际上的递归调用了setImplLocked方法。
void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) {
a.when = a.origWhen;
long whenElapsed = convertToElapsed(a.when, a.type);
final long maxElapsed;
if (a.windowLength == AlarmManager.WINDOW_EXACT) {
// Exact
maxElapsed = whenElapsed;
} else {
// Not exact. Preserve any explicit window, otherwise recalculate
// the window based on the alarm's new futurity. Note that this
// reflects a policy of preferring timely to deferred delivery.
maxElapsed = (a.windowLength > 0)
? clampPositive(whenElapsed + a.windowLength)
: maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
}
a.expectedWhenElapsed = a.whenElapsed = whenElapsed;
a.expectedMaxWhenElapsed = a.maxWhenElapsed = maxElapsed;
setImplLocked(a, true, doValidate);
}
参考:
Android RTC 自下往上浅析
Linux中常用的时间结构struct timespec 和struct timeval
利用文件描述符进行通知的定时器:timerfd
深入Android 'M' Doze