美文网首页
2020-03-27-Android电源管理-AlarmMana

2020-03-27-Android电源管理-AlarmMana

作者: 耿望 | 来源:发表于2020-04-01 11:37 被阅读0次

    (一)Alarm的设置

    这里简单画了一下,从framework到native再到kernel的大概流程。


    Alarm设置.jpg

    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;
            }
    
    Alarm Batch.jpg

    代码逻辑不是很复杂,这里画了一张图。

    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的消息队列。


    Alarm触发.jpg
        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

    相关文章

      网友评论

          本文标题:2020-03-27-Android电源管理-AlarmMana

          本文链接:https://www.haomeiwen.com/subject/nlvquhtx.html