Definition 1.

The main purpose of Alarm is for the application to wake up the system at a certain point in the future and then do what it needs to do according to its own needs, including starting activities, broadcasting, Service, and so on. However, some applications use Alarm or live through Alarm, which directly leads to the increase of system power consumption, so it is necessary to make certain restrictions on the application of Alarm in Android. There are some restrictions on Alarm in Android system native, as follows:

(1) Android4.4 and later through the set, setRepeating set Alarm becomes not accurate transmission, the main strategy is to determine whether the WindowLength is equal to 0 to determine whether this Alarm needs to be added to the batch queue and other Alarm trigger time overlap. Because it is added to another Batch queue, the triggering time of this Alarm may be advanced or delayed. However, the setExact and setWindow methods can be used;

(2) in Android6.0 and later, due to the addition of Doze mode, setExact and setWindow will also become inaccurate transmission. The main strategy is that when the system enters Doze mode, it will set an Alarm through setIdleUtils method. When an Alarm is set, all the alarms in the list are rebatch. Those that do not meet the Doze criteria are added to the mPendingwhileIdleAlarm list, and those that do are added to the executable list. When the Alarm trigger time reaches, it rebatch all the alarms and set the earliest Alarm trigger time to the bottom layer.

(3) Android9.0 and later added application standby group (AppStandby), when Alarm Settings in the application will be based on the application standby group which level and the last time to send Alarm to delay the trigger time of Alarm.

2. The AlarmManager

2.1 define

All implementations of this class call AlarmManagerService, which is the intermediate class for external access to AlarmManagerService. This class stores the corresponding Service name and CachedServiceFetcher object (used to generate the corresponding Manager object) in a HashMap using static code blocks in SystemServiceRegistry. When the user calls getSystemService () in SystemServiceRegistry via getSystemService () in ContextImpl, In this function, the getService () function in CachedServiceFetcher object is called, and the corresponding Manager instance is generated through this function, as shown in the following sequence diagram:

2.2 flag on

(1) FLAG_STANDALONE = 1; It is used to indicate that the alarm will not be added to other alarm sets (in Android4.4 above is not accurate transfer, the alarm with similar time will be batch processing), and it is processed separately;

FLAG_WAKE_FROM_IDLE = 2; Indicates that the device is awakened to process the alarm even if it is in idle state.

FLAG_ALLOW_WHILE_IDLE = 4; Indicates that the device processes the alarm even if it is in idle state and does not exit idle state.

FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED = 8; Similar to 3, but without any constraints, it only applies to system alarm;

(5) FLAG_IDLE_UNTIL = 16; This parameter is applicable only to the system and is used to tell AlarmManager when to exit idle mode. That is, this parameter is used only on DeviceIdleController. When DeviceIdleController sets this flag, the system enters idle state.

(6) RTC_WAKEUP = 0; Time is set with System.currentTimemillis (), and the device will wake up when it enters idle state;

(7) RTC = 1; Time is set with System.currentTimemillis (), and the device is not executed when it enters idle state;

(8) ELAPSED_REALTIME_WAKEUP = 2; Through SystemClock. ElapsedRealtime () time Settings, and equipment will be executed when enters idle state;

(9) ELAPSED_REALTIME = 3; Through SystemClock. ElapsedRealtime () time Settings, and equipment when enters idle state will not be executed.

3. AlarmManagerService explanation

3.1 Starting Alarm by using AlarmManager First enters the set () function of IBinder class in AMS and performs the following judgment operations:

(1) The value of flag (used to identify whether the Alarm of the application belongs to the four types defined in AlarmManager to indicate whether the Alarm needs special processing) is initialized by identifying the application type by calling the application UID of Alarm;

(2) Judge the validity of the incoming data: If the windowLength (setWindow function starts Alarm, which is used to set the time that Alarm can be delayed) parameter is set to 1 hour if the value is greater than 12 hours. The maximum value of the cycle wake interval is 365 days and the minimum value is 1 minute. If the value exceeds the limit of the maximum value and minimum value, Is assigned a maximum or minimum value.

3.2 Convert absolute time to relative time for Alarm triggering and calculate the maximum delay Alarm triggering time. The code is as follows:

/ / to get from the start to the current time final long nowElapsed = SystemClock. ElapsedRealtime (); / / if the type is RTCxx type triggerAtTime - = System. CurrentTimeMillis () - SystemClock. ElapsedRealtime (); Final Long nominalTrigger = convertToElapsed(triggerAtTime, type); Final long minTrigger = Nowelsed + mConstants.MIN_FUTURITY; // Minimum trigger time = power-up time +5 SEC final long minTrigger = Nowelsed + mConstants. Final Long triggerElapsed = (nominalTrigger > minTrigger)? Final Long triggerElapsed = (nominalTrigger > minTrigger)? nominalTrigger : minTrigger; // Final Long maxElapsed; // Final Long maxElapsed; If (windowLength == alarmManager.window_exact) {// If (windowLength == alarmManager.window_exact) {// maxElapsed = triggerElapsed; } else if (windowLength < 0) {// Latest trigger time maxElapsed = maxTriggerTime(nowelPasssed, triggerElapsed, interval); windowLength = maxElapsed - triggerElapsed; } else {// If the elapsed time is greater than 0, set maxElapsed = triggerElapsed + windowLength; }Copy the code

3.3 Check whether the current alarm is FLAG_IDLE_UNTIL. The code is as follows:

FLAG_IDLE_UNTIL if ((a.lags&alarmManager.flag_IDLE_until)! If (mNextWakeFromIdle!) {if (mNextWakeFromIdle!) {if (mNextWakeFromIdle! = null && a.whenElapsed > mNextWakeFromIdle.whenElapsed) { a.when = a.whenElapsed = a.maxWhenElapsed = mNextWakeFromIdle.whenElapsed; } final long nowElapsed = SystemClock.elapsedRealtime(); // Fuzz has the following possible values based on the current alarm delay time: // (1) delay < 15min, return delay; // (2) delay < 90min, return 15min; // (3) else return 12min final int fuzz = fuzzForDuration(a.elapsed - nowelsed); // (4) else return 12min final int Fuzz = fuzzForDuration(a.elapsed - Nowelsed); If (fuzz > 0) {if (mRandom == null) {mRandom = new Random(); } final int delta = mRandom.nextInt(fuzz); a.whenElapsed -= delta; a.when = a.maxWhenElapsed = a.whenElapsed; } FLAG_IDLE_UNTIL alarm is set to FLAG_IDLE_UNTIL alarm, and alarm does not exist in idle state. Add to the mPendingWhileIdleAlarms delay start execution list else if (mpendingidleAlarms until! = null) { if ((a.flags&(AlarmManager.FLAG_ALLOW_WHILE_IDLE | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | AlarmManager.FLAG_WAKE_FROM_IDLE)) == 0) { mPendingWhileIdleAlarms.add(a); return; }}Copy the code

3.4 Set the alarm trigger time based on the standby group category of the application that invokes the alarm application. The key codes are as follows:

/ / to get final application standbyBucket category int standbyBucket = mUsageStatsManagerInternal. GetAppStandbyBucket (sourcePackage, sourceUserId, SystemClock.elapsedRealtime()); final Pair<String, Integer> packageUser = Pair.create(sourcePackage, sourceUserId); / / for the last time send alarm final long lastElapsed = mLastAlarmDeliveredForPackage. GetOrDefault (packageUser, 0 l); If (lastElapsed > 0) {/ / the next alarm triggering time minimum final long minElapsed = lastElapsed + getMinDelayForBucketLocked (standbyBucket);  if (alarm.expectedWhenElapsed < minElapsed) { alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed; } else { // app is now eligible to run alarms at the originally requested window. // Restore original requirements in case they were changed earlier. alarm.whenElapsed = alarm.expectedWhenElapsed; alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed; }}Copy the code

App Standby Groups: Android 9 introduces a new battery management feature called App Standby Groups. The application standby group helps the system schedule the priority of application request resources based on the recent application usage time and frequency. In this mode, each application is classified into one of five priorities. The system limits the device resources that each application can access based on the group to which the application belongs. There are five groups:

(1) Active: the user is using;

(2) Working set: The application is often running, but not in an active state;

(3) Common use: Apps are used regularly, but not every day.

(4) Rarely used: The app is not used often;

(5) Never used.

The application of alarm is delayed by 0 minutes, 6 minutes, 30 minutes, 2 hours, and 10 days respectively according to the standby bucket type.

3.5 Adding Alarm to Batch

Above Android4.4, Alarm works in non-precision mode by default, unless precision mode is used. However, in the imprecise mode, all alarms are processed in batches. Each Alarm is added to different batches according to the trigger time and maximum trigger time. Different alarms in the same batch occur simultaneously. The code is as follows:

// Return -1 if the current alarm is triggered at a time that is not otherwise affected, otherwise fetch int whichBatch = ((alarm.flags & alarmManager.flag_standalone)! = 0)? -1 : attemptCoalesceLocked(alarm.whenElapsed, alarm.maxWhenElapsed); If (whichBatch < 0) {// construct the alarm as a batch and find the subscript that needs to be added to by binary lookup, AddBatchLocked (mAlarmBatches, New Batch(Alarm)) addBatchLocked(mAlarmBatches, New Batch(Alarm)) } else { final Batch batch = mAlarmBatches.get(whichBatch); If (batch.add(alarm)) {// The newly added alarm fires at a different time, which needs to be removed to change the index mAlarmBatches in the list. Remove (whichBatch); addBatchLocked(mAlarmBatches, batch); }} // Batch in the list, AttemptCoalesceLocked (Long whenElapsed, long maxWhen) {final int N = mAlarmBatches. Size (); for (int i = 0; i < N; i++) { Batch b = mAlarmBatches.get(i); If ((b.lags&AlarmManager.flag_standalone) == 0 && if the time and maximum time of the alarm to be added overlap with the start and end time of the batch b.canHold(whenElapsed, maxWhen)) { return i; } } return -1; } Boolean canHold(long whenElapsed, long maxWhen) { return (end >= whenElapsed) && (start <= maxWhen); }Copy the code

3.6 All alarms are rebatch in the following two cases:

  • The current alarm flag is set to FLAG_IDLE_UNTIL, which indicates when the system exits idle.
  • If the current alarm is set to FLAG_WAKE_FROM_IDLE, the alarm can wake the system from idle. And mNextWakeFromIdle = = null | | mNextWakeFromIdle. WhenElapsed > aleem walji henElapsed, set the alarm to mNextWakeFromIdle, And mPendingIdleUntil! If = null, all alarms are rebatch processed.

3.7 rescheduleKernelAlarmsLocked, set up the first wake up alarm and not wake up alarm execution time to the kernel layer, the code is as follows:

long nextNonWakeup = 0; If (mAlarmBatches. Size () > 0) {// Batch final Batch firstWakeup = batch final Batch firstWakeup = findFirstWakeupBatchLocked(); // Batch final Batch firstBatch = mAlarmBatches. Get (0); if (firstWakeup ! = null) {mNextWakeup = firstwakeup.start; mLastWakeupSet = SystemClock.elapsedRealtime(); SetLocked (ELAPSED_REALTIME_WAKEUP, firstwakeup.start); } // if the alarm of the firstBatch batch is not an alarm, assign the start of the firstBatch object in the mAlarmBatches list to nextNonWakeup if (firstBatch! = firstWakeup) { nextNonWakeup = firstBatch.start; }} the if (mPendingNonWakeupAlarms. The size () > 0) {/ / if a non wake up the alarm class execution time is less than the current access to the time, The assignment again if (nextNonWakeup = = 0 | | mNextNonWakeupDeliveryTime < nextNonWakeup) {nextNonWakeup = mNextNonWakeupDeliveryTime; If (nextNonWakeup! = 0 && mNextNonWakeup ! = nextNonWakeup) { mNextNonWakeup = nextNonWakeup; setLocked(ELAPSED_REALTIME, nextNonWakeup); } // Get the first alarm that can wake the system class, where TYPE_NONWAKEUP_MASK = 1 and type = 0 or 3 cannot wake the system Boolean hasWakeups() {final int N = alarms. Size ();} // Get the first alarm that can wake the system class, where TYPE_NONWAKEUP_MASK = 1 and type = 0 or 3 cannot wake the system.  for (int i = 0; i < N; i++) { Alarm a = alarms.get(i); if ((a.type & TYPE_NONWAKEUP_MASK) == 0) { return true; } } return false; }Copy the code

3.8 updateNextAlarmClockLocked, calculate and set the alarmClock Alarm execution order, part of the code is as follows:

private void updateNextAlarmClockLocked() { if (! mNextAlarmClockMayChange) { return; }... final int N = mAlarmBatches.size(); for (int i = 0; i < N; i++) { ArrayList<Alarm> alarms = mAlarmBatches.get(i).alarms; final int M = alarms.size(); for (int j = 0; j < M; j++) { Alarm a = alarms.get(j); // Check whether alarmClock has been set in this alarm if (a.larmclock! Final int userId = userhandle.getUserId (a.id); / / in accordance with the uid from obtaining the corresponding mNextAlarmClockForUser alarmClock AlarmManager. AlarmClockInfo current = mNextAlarmClockForUser. Get (userId);  // Since alarm is stored in order of time and read in order, Nextforuser. get(userId) == null) {nextForuser. put(userId, a.larmclock); // If the alarm is in the nextForUser list and also in the mNextAlarmClockForUser list, } else if (a.larmcLocke.equals (current) &&current-gettriggerTime () <= nextForUser.get(userId).getTriggerTime()) { nextForUser.put(userId, current); }}}} //mNextAlarmClockForUser final int NN = nextForuser.size (); for (int i = 0; i < NN; i++) { AlarmManager.AlarmClockInfo newAlarm = nextForUser.valueAt(i); int userId = nextForUser.keyAt(i); AlarmManager.AlarmClockInfo currentAlarm = mNextAlarmClockForUser.get(userId); // If nextForUser and mNextAlarmClockForUser have different alarmcLocks for the same uid, //nextForUser AlarmClock to mNextAlarmClockForUser if (! newAlarm.equals(currentAlarm)) { updateNextAlarmInfoForUserLocked(userId, newAlarm); } } final int NNN = mNextAlarmClockForUser.size(); for (int i = NNN - 1; i >= 0; i--) { int userId = mNextAlarmClockForUser.keyAt(i); // If the uid in mNextAlarmClockForUser does not exist in nextForUser, If (nextForuser.get (userId) == null) {if (nextForUser.get(userId) == null) { updateNextAlarmInfoForUserLocked(userId, null); }}}Copy the code

By calling the updateNextAlarmInfoForUserLocked function of mNextAlarmClockForUser list data update, The last call to update in sendNextAlarmClockChanged function NEXT_ALARM_FORMATTED values in the system Settings, and send the NEXT_ALARM_CLOCK_CHANGED_INTENT broadcast notification.

Note: SystemClock) elapsedRealtime () from boot up to the time including sleep time, SystemClock. UptimeClock () from boot up to the time but does not include the time of sleep.

4.AlarmManagerService Startup process

4.1 AMS starts in SystemServer, and the sequence diagram is as follows:

@Override public void onStart() { mNativeData = init(); mNextWakeup = mNextRtcWakeup = mNextNonWakeup = 0; / / to the storage to shut off the alarm clock file into initial value 0 AlarmManager. WritePowerOffAlarmFile (AlarmManager POWER_OFF_ALARM_SET_FILE, AlarmManager.POWER_OFF_ALARM_NOT_SET); // When the Android system starts up for the first time or starts to restore its factory Settings, it performs a full encryption operation called FDE. During this process, Vold sets vold.decrypt to // trigger_restarT_min_framework and loads the TMPFS temporary partition. Services in the main group will be restarted, and the /data partition will be mounted after the encryption is successful. Vol // D then sets the vold.decrypt property to trigger_restart_framework. String cryptState = SystemProperties.get("vold.decrypt"); if (ENCRYPTING_STATE.equals(cryptState) || ENCRYPTED_STATE.equals(cryptState)) { mIsEncryptStatus = true; } // Set the current time zone information to the kernel layer. If the time zone changes send ACTION_TIMEZONE_CHANGED broadcast if (mIsEncryptStatus) {String tz = AlarmManager.readPowerOffAlarmFile(AlarmManager.POWER_OFF_ALARM_TIMEZONE_FILE); setTimeZoneImpl(tz); } else { setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY)); } // Set the system time if (mNativeData! = 0) { final long systemBuildTime = Environment.getRootDirectory().lastModified(); if (System.currentTimeMillis() < systemBuildTime) { Slog.i(TAG, "Current time only " + System.currentTimeMillis() + ", advancing to build time " + systemBuildTime); setKernelTime(mNativeData, systemBuildTime); SystemUI Final PackageManager packMan = getContext().getPackagemanager (); try { PermissionInfo sysUiPerm = packMan.getPermissionInfo(SYSTEM_UI_SELF_PERMISSION, 0); ApplicationInfo sysUi = packMan.getApplicationInfo(sysUiPerm.packageName, 0); if ((sysUi.privateFlags&ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) ! = 0) { mSystemUiUid = sysUi.uid; } else { Slog.e(TAG, "SysUI permission " + SYSTEM_UI_SELF_PERMISSION + " defined by non-privileged app " + sysUi.packageName + " - ignoring"); } } catch (NameNotFoundException e) { } if (mSystemUiUid <= 0) { Slog.wtf(TAG, "SysUI package not found!" ); } // Create a new wakelock PowerManager PM = (PowerManager) getContext().getSystemService(context.power_service); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*alarm*"); / / construction time PendingIntent mTimeTickSender = PendingIntent. GetBroadcastAsUser (getContext (), 0, new Intent(Intent.ACTION_TIME_TICK).addFlags( Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS), 0, UserHandle.ALL); Intent Intent = new Intent(intent.action_date_changed); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent, Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL); MClockReceiver = new ClockReceiver(); / / set the alarm for sending time broadcast mClockReceiver change after a minute scheduleTimeTickEvent (); / / set the alarm, for sending date one day after the broadcast mClockReceiver change scheduleDateChangedEvent (); / / initializes the kill bright screen broadcast receiver mInteractiveStateReceiver = new InteractiveStateReceiver (); MUninstallReceiver = new UninstallReceiver(); // Initialize AlarmThread and start an infinite loop in that thread to read alarm and execute if (mNativeData! = 0) { AlarmThread waitThread = new AlarmThread(); waitThread.start(); } else { Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler."); } / / used to register to monitor the uid status value change the try {ActivityManager. GetService () registerUidObserver (new UidObserver (), ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_ACTIVE, ActivityManager.PROCESS_STATE_UNKNOWN, null); } catch (RemoteException e) { // ignored; both services live in system_server } publishBinderService(Context.ALARM_SERVICE, mService); }Copy the code

4.3 AlarmManagerService AlarmThread starts a wireless loop and blocks the AlarmThread. When the alarm is triggered at a certain point in time, the AlarmThread continues to execute. The code is as follows:

private class AlarmThread extends Thread{ public AlarmThread(){ super("AlarmManager"); } public void run(){ ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); While (true){// block the method to continue executing int result = waitForAlarm(mNativeData) after the underlying alarm is raised; // Final Long nowRTC = system.currentTimemillis (); / / the current boot time, contain dormancy time final long nowELAPSED = SystemClock. ElapsedRealtime (); Synchronized (mLock) {// Synchronize mLastWakeup = nowelsed; synchronized (mLock) {// Record current system wake-up time mLastWakeup = nowelsed; } triggerList.clear(); if ((result & TIME_CHANGED_MASK) ! = 0) {final Long lastTimeChangeClockTime () {final Long lastTimeChangeClockTime (); final long expectedClockTime; Synchronized (mLock) {// The absolute time of the last alarm execution lastTimeChangeClockTime = mLastTimeChangeClockTime; // The absolute time when the last alarm is executed + the time when the current alarm is started - the time when the last alarm is started = // The absolute time when the current alarm is expectedClockTime = lastTimeChangeClockTime + (nowELAPSED - mLastTimeChangeRealtime); } / judgment for the first time or the absolute time of the alarm is supposed to be performed and the absolute time is greater than the difference + - 1 s the if (lastTimeChangeClockTime = = 0 | | nowRTC < (expectedClockTime - 1000) | | NowRTC > (expectedClockTime+1000)) {// Due to time change, re-batch all the alarm removeImpl(mtimekSender); removeImpl(mDateChangeSender); rebatchAllAlarms(); mClockReceiver.scheduleTimeTickEvent(); mClockReceiver.scheduleDateChangedEvent(); synchronized (mLock) { mNumTimeChanged++; MLastTimeChangeClockTime = nowRTC; mLastTimeChangeClockTime = nowRTC; mLastTimeChangeRealtime = nowELAPSED; Intent.action_time_changed = new 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) { synchronized (mLock) { mLastTrigger = nowELAPSED; Alarm Boolean hasWakeup = triggerAlarmsLocked(triggerList, NowelAlarmslocked, nowRTC); // Sed for some time. // Check whether the unawakened class alarm satisfies the delayed execution condition if (! HasWakeup && checkAllowNonWakeupDelayLocked (nowELAPSED)) {/ / record start delay time and calculating the next alarm sending time if (mPendingNonWakeupAlarms. The size ()  == 0) { mStartCurrentDelayTime = nowELAPSED; mNextNonWakeupDeliveryTime = nowELAPSED + ((currentNonWakeupFuzzLocked(nowELAPSED)*3)/2); } mPendingNonWakeupAlarms.addAll(triggerList); mNumDelayedAlarms += triggerList.size(); / / because the list data changes need to be updated kernel perform alarm in time and the next alarmClock rescheduleKernelAlarmsLocked (); updateNextAlarmClockLocked(); } else {the if (mPendingNonWakeupAlarms. The size () > 0) {/ / calculation not wake up alarm sent priority calculateDeliveryPriorities(mPendingNonWakeupAlarms); triggerList.addAll(mPendingNonWakeupAlarms); // Resort collections.sort (triggerList, mAlarmDispatchComparator); // Delay time for alarm Final Long thisDelayTime = Nowelsed - mStartCurrentDelayTime; mTotalDelayTime += thisDelayTime; if (mMaxDelayTime < thisDelayTime) { mMaxDelayTime = thisDelayTime; } mPendingNonWakeupAlarms.clear(); } final ArraySet<Pair<String, Integer>> triggerPackages = new ArraySet<>(); for (int i = 0; i < triggerList.size(); i++) { final Alarm a = triggerList.get(i); // if a.larmclock! = null | | is the core application | | flag & FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED / /! = 0 if (! isExemptFromAppStandby(a)) { triggerPackages.add(Pair.create( a.sourcePackage, UserHandle.getUserId(a.creatorUid))); }} // Send Alarm deliverAlarmsLocked(triggerList, Nowelsed); reorderAlarmsBasedOnStandbyBuckets(triggerPackages); / / update the kernel layer next execution time of alarm points as well as the next execution alarmClock 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(); }}}}} Boolean checkAllowNonWakeupDelayLocked (long nowELAPSED) {/ / whether bright screen if (mInteractive) {return false; If (mLastAlarmDeliveryTime <= 0) {return false; if (mLastAlarmDeliveryTime <= 0) {return false; } // If the current time of passing alarm is less than the current time, Then it returns false if (mPendingNonWakeupAlarms. The size () > 0 && mNextNonWakeupDeliveryTime < nowELAPSED) {/ / This is just a little  paranoia, if somehow we have pending non-wakeup alarms // and the next delivery time is in the past, then just deliver them all. This // avoids bugs where we get stuck in a loop trying to poll for alarms. return false; } // time last time alarm was delivered long timeSinceLast = nowelsed - mLastAlarmDeliveryTime; return timeSinceLast <= currentNonWakeupFuzzLocked(nowELAPSED); } long currentNonWakeupFuzzLocked (long nowELAPSED) {/ / coupon screen duration long timeSinceOn = nowELAPSED - mNonInteractiveStartTime; if (timeSinceOn < 5*60*1000) { return 2*60*1000; } else if (timeSinceOn < 30*60*1000) { return 15*60*1000; } else { return 60*60*1000; }}Copy the code

4.3.1 triggerAlarmsLocked: Checks whether there are alarms in the list that can wake the system from idle, and returns true if there are; And get the list of alarm triggers as follows:

boolean triggerAlarmsLocked(ArrayList<Alarm> triggerList, final long nowELAPSED,final long nowRTC) { boolean hasWakeup = false; While (mAlarmBatches. Size () > 0) {Batch Batch = mAlarmBatches. Get (0); // Withdraw from the current cycle if (Batch. start > nowelsed){// If the start time of the first alarm in the first Batch list is greater than the current time. } // Remove the currently being processed batch object from the list to prevent access to it elsewhere. final int N = batch.size(); for (int i = 0; i < N; i++) { Alarm alarm = batch.get(i); // If the current alarm can be executed when the system is idle, then the app needs to limit the frequency of calling these alarms. If ((alarm.flags&alarmManager.flag_allow_while_IDLE)! = 0) {/ / execution time for alarm last final long lastTime = mLastAllowWhileIdleDispatch. Get (alarm. CreatorUid - 1); / / get alarm as early as next execution time final long minTime = lastTime + getWhileIdleMinIntervalLocked (alarm. CreatorUid); // If (lastTime >= 0 && nowelsed < minTime) {// If the last execution time is greater than 0 and the current time is less than the minimum execution time since the last execution time // Re-set the execution time of the alarm. If (lastTime >= 0 && nowelsed < minTime) { alarm.expectedWhenElapsed = alarm.whenElapsed = minTime; if (alarm.maxWhenElapsed < minTime) { alarm.maxWhenElapsed = minTime; } alarm.expectedMaxWhenElapsed = alarm.maxWhenElapsed; . SetImplLocked (alarm, true, false); continue; }}... alarm.count = 1; // Store an alarm triggerList.add(alarm) that can be triggered; . If (mPendingIdleUntil == alarm) {// If the current alarm is mPendingIdleUntil, set mPendingIdleUntil to null. MPendingIdleUntil = null; rebatchAllAlarmsLocked(false); restorePendingWhileIdleAlarmsLocked(); } if (mNextWakeFromIdle == alarm) { mNextWakeFromIdle = null; rebatchAllAlarmsLocked(false); } // If (alarm.repeatinterval > 0) {// Calculate the number of times for alarm to the current time. Alarm. count += (nowelsed - alarm.expectedWhenElapsed) / alarm.repeatInterval; Final Long delta = alarm.count * alarm.repeatInterval; final long delta = alarm.count * alarm.repeatInterval; // Calculate the next elapsed time of alarm long value1 = alarm. WhenElapsed + delta; long value2 = nowELAPSED + alarm.repeatInterval; final long nextElapsed = value1 > value2 ? value1 : value2; SetImplLocked (alarm.type, alarm.when + delta, Nextelsed, alarm.windowlength, // Add the next execution of Alarm to the alarm list. maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval), alarm.repeatInterval, alarm.operation, null, null, alarm.flags, true, alarm.workSource, alarm.alarmClock, alarm.uid, alarm.packageName); } if (alarm.wakeup) {hasWakeup = true; } // Update the alarm if (alarm.alarmcLock! = null) { mNextAlarmClockMayChange = true; } } } // This is a new alarm delivery set; bump the sequence number to indicate that // all apps' alarm delivery classes should be recalculated. mCurrentSeq++; / / to recalculate the alarm relay calculateDeliveryPriorities priority (triggerList); Sort collections.sort (triggerList, mAlarmDispatchComparator); . return hasWakeup; }Copy the code

5. To summarize

Process for setting alarm to AlarmManagerService:

(1) The user uses AlarmManager to set the alarm to AlarmManagerService.

(2) AlarmManager is called into the setImpl () method;

(3) Call the set() method of IBinder class in AlamManagerService through setImpl();

(4) Initialize the flag value according to the application type, which is used to determine the processing mode of the alarm set by the application (whether the system can be awakened to execute alarm, whether the alarm can be executed in idle state, etc.);

(5) Call setImpl() method to legalize the parameters passed in by the user, and convert the absolute time into system startup time for processing;

(6) Create a new alarm. If this type of alarm exists in the list, remove it with the incoming PendingIntent and AlarmListener.

(7) If the current alarm is flag&FLAG_IDLE_UNTIL! = 0, then the current Alarm execution time is brought forward. else mPendingIdleUntil ! = null and flag & (FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_WAKE_FROM_IDLE) = = 0 it will add to the current alarm MPendingWhileIdleAlarms;

(8) Adjust the triggering time of the alarm according to the standby bucket of the application and the last sending time;

(9) Add FLAG_STANDALONE to the corresponding Batch based on whether Alarm is set, and add Batch to the list based on the sequence of start time in the Batch from small to large;

(10) Rebatch the list of alarms if mPendingIdleUntil and mNextWakeFromIdle change;

(11) Recalculate the start time of batch triggering awakening class and batch non-awakening class to the kernel layer;

(12) Update the next alarm Clock to the Settings and broadcast NEXT_ALARM_CLOCK_CHANGED_INTENT to all users;

Alarm trigger process description:

(1) AlarmThread detects the kernel layer and triggers the execution of alarm;

(2) If the return result contains the TIME_CHANGED_MASK tag, rebatch all the alarms and broadcast ACTION_TIME_CHANGED;

(3) Check whether there is a system type that can wake up in alarm. If not, add all alarms to the list of mPendingNonWakeupAlarms and update the data.

(4) If there is, send alarm according to the obtained alarm list, and finally update the time when the alarm is triggered next time and the alarm Clock to be executed next time.

The whole process can also be understood as follows: The user invokes the Alarm method to set AlarmManagerService, adds the earliest Alarm trigger time, latest Alarm trigger time or flag to the corresponding Batch object based on the Alarm trigger time set by the user, and updates the Alarm trigger time to the kernel layer. When AlarmThread listens to the return result of Alarm triggered by the kernel layer, it sends an Alarm based on the judged result, that is, the PendingIntent of Alarm.

Reference links:

Blog.csdn.net/singwhatiwa…

Blog.csdn.net/zhangyongfe…