首页 > 系统 > Android > 正文

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

2019-11-09 15:19:55
字体:
来源:转载
供稿:网友

上篇博客分析了Doze模式下WakeLock,这篇我们分析Alarm。

白名单

首先我们从白名单开始,在DeviceIdleController中会设置alarm的白名单。

    public final class LocalService {        public void setDeviceIdleUserWhitelist(int[] appids) {            setDeviceIdleUserWhitelistImpl(appids);        }    }最终就是设置到了mDeviceUserWhiteList中。

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

然后我们再看AlarmMangerService设置Alarm的set函数中有如下代码,当uid不是应用的uid或者。在白名单中能找到该uid,会将flags或上FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED标志(这个标志就是应用白名单的象征)。

        public void set(String callingPackage,                int type, long triggerAtTime, long windowLength, long interval, int flags,                PendingIntent Operation, IAlarmListener directReceiver, String listenerTag,                WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) {            ......            } else if (workSource == null && (callingUid < PRocess.FIRST_application_UID                    || Arrays.binarySearch(mDeviceIdleUserWhitelist,                            UserHandle.getAppId(callingUid)) >= 0)) {                flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;                flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;            }            setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,                    listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);        }

setIdleUntil函数激活Alarm的Doze模式

在DeviceIdleController中是调用了AlarmManager的setIdleUntil函数激活了AlarmManagerService的Doze模式

    public void setIdleUntil(int type, long triggerAtMillis, String tag, OnAlarmListener listener,            Handler targetHandler) {        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, null,                listener, tag, targetHandler, null, null);    }

我们先再来看看set函数,这里面对Doze模式相关的flag做了很多处理

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

下面我们就看setImplLocked函数,看看和Doze模式相关的。如果是调用了setIdleUntil接口也就有FLAG_IDLE_UNTIL的flag,会调整期whenElapsed时间。然后设置mPendingIdleUntil变量代表已经进入Doze模式,当然进入Doze模式,肯定要rebatch所有的alarm。当有mPendingIdleUntil时(Doze模式),这个时候只有有FLAG_WAKE_FROM_IDLE、FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED、FLAG_ALLOW_WHILE_IDLE的flag的alarm才会被设置下去。其他的直接加入mPendingWhileIdleAlarms然后return了。自然设置的alarm就无效了。

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

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

退出Doze模式

退出Doze模式有两种:

1.就是设置的setIdleUntil的alarm到时间了。

2.第二种就是自己主动删除的这个alarm

我们先来看看setIdleUntil的alarm时间到了的情况,在triggerAlarmsLocked函数(到期的alarm发送)有如下代码,如果发送的alarm是mPendingIdleUntil(也就是setIdleUntil的alarm),就把mPendingIdleUntil清除,然后重新rebatch所有的alarm,然后就是调用restorePendingWhileIdleAlarmsLocked函数,把之前没有设置的alarm(放在mPendingWhileIdleAlarms),重新设置alarm,然后把mPendingWhileIdleAlarms清零。

                if (mPendingIdleUntil == alarm) {                    mPendingIdleUntil = null;                    rebatchAllAlarmsLocked(false);                    restorePendingWhileIdleAlarmsLocked();                }

我们来看下restorePendingWhileIdleAlarmsLocked函数

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

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

        @Override        public void remove(PendingIntent operation, IAlarmListener listener) {            if (operation == null && listener == null) {                Slog.w(TAG, "remove() with no intent or listener");                return;            }            synchronized (mLock) {                removeLocked(operation, listener);            }        }removeLocked函数,先从mAlarmBatchs中删除,再从mPendingWhileIdleAlarms删除,如果从mAlarmBatchs中删除了alarm还要看删除的是否是mPendingIdleUntil,如果是要重新rebatch所有alarm,以及调用restorePendingWhileIdleAlarmsLocked函数恢复mPendingWhileIdleAlarms中的alarm。
    private void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {        boolean didRemove = false;        for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {            Batch b = mAlarmBatches.get(i);            didRemove |= b.remove(operation, directReceiver);            if (b.size() == 0) {                mAlarmBatches.remove(i);            }        }        for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {            if (mPendingWhileIdleAlarms.get(i).matches(operation, directReceiver)) {                // Don't set didRemove, since this doesn't impact the scheduled alarms.                mPendingWhileIdleAlarms.remove(i);            }        }        if (didRemove) {            boolean restorePending = false;            if (mPendingIdleUntil != null && mPendingIdleUntil.matches(operation, directReceiver)) {                mPendingIdleUntil = null;                restorePending = true;            }            if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(operation, directReceiver)) {                mNextWakeFromIdle = null;            }            rebatchAllAlarmsLocked(true);            if (restorePending) {                restorePendingWhileIdleAlarmsLocked();            }            updateNextAlarmClockLocked();        }    }最后mPendingIdleUntil删除了,那么也就退出了Doze模式,所有的alarm设置下来都会正常了。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表