Android 8.1 Doze模式分析(五)Doze对Alarm的限制

在DeviceIdleController.java-->onBootPhase()会将白名单设置给AlarmManager

public void onBootPhase(int phase) {
    ......
    mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
    ......
}

AlarmManagerService.java  setDeviceIdleUserWhitelist设置alarm白名单

    public final class LocalService {
        public void setDeviceIdleUserWhitelist(int[] appids) {
            setDeviceIdleUserWhitelistImpl(appids);
        }
    }

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

激活Alarm的Doze模式

    void scheduleAlarmLocked(long delay, boolean idleUntil) {
        if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")");

        // NOTE: Bug #627645 low power Feature BEG-->
        boolean ignoreMotionSensor = mPowerControllerHelper.ignoreMontionSensor();
        //if (mMotionSensor == null) {
        if (mMotionSensor == null && !ignoreMotionSensor) {
        // <--NOTE: Bug #627645 low power Feature END

            // If there is no motion sensor on this device, then we won't schedule
            // alarms, because we can't determine if the device is not moving.  This effectively
            // turns off normal execution of device idling, although it is still possible to
            // manually poke it by pretending like the alarm is going off.
            return;
        }
        mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
        if (idleUntil) {
            mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
        } else {
            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
        }
    }

AlarmManager.java-->setIdleUntil()

    public void setIdleUntil(@AlarmType 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);
    }

AlarmManager.java-->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) {
            synchronized (AlarmManager.class) {
                if (sWrappers == null) {
                    sWrappers = new ArrayMap<OnAlarmListener, ListenerWrapper>();
                }

                recipientWrapper = sWrappers.get(listener);
                // no existing wrapper => build a new one
                if (recipientWrapper == null) {
                    recipientWrapper = new ListenerWrapper(listener);
                    sWrappers.put(listener, recipientWrapper);
                }
            }

            final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler;
            recipientWrapper.setHandler(handler);
        }

        try {
            mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
                    operation, recipientWrapper, listenerTag, workSource, alarmClock);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

AlarmManagerService.java-->set()

   @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) {
            final int callingUid = Binder.getCallingUid();

            // make sure the caller is not lying about which package should be blamed for
            // wakelock time spent in alarm delivery
            mAppOps.checkPackage(callingUid, callingPackage);

            // Repeating alarms must use PendingIntent, not direct listener
            if (interval != 0) {
                if (directReceiver != null) {
                    throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers");
                }
            }

            if (workSource != null) {
                getContext().enforcePermission(
                        android.Manifest.permission.UPDATE_DEVICE_STATS,
                        Binder.getCallingPid(), callingUid, "AlarmManager.set");
            }

            // 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;
            }

            // 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;

            // 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
                    || callingUid == mSystemUiUid
                    || Arrays.binarySearch(mDeviceIdleUserWhitelist,
                            UserHandle.getAppId(callingUid)) >= 0)) {
                flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
                flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
            }

             // SPRD: regular poweroff alarm. start>
             if(type == POWER_OFF_ALARM || type == POWER_OFF_WAKEUP){
                 flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
             }
             // <end
            setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
                    listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
        }

AlarmManagerService.java-->setImpl()

 void setImpl(int type, long triggerAtTime, long windowLength, long interval,
            PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
            int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
            int callingUid, String callingPackage) {
        // must be *either* PendingIntent or AlarmReceiver, but not both
        if ((operation == null && directReceiver == null)
                || (operation != null && directReceiver != null)) {
            Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver");
            // NB: previous releases failed silently here, so we are continuing to do the same
            // rather than throw an IllegalArgumentException.
            return;
        }

        // Sanity check the window length.  This will catch people mistakenly
        // trying to pass an end-of-window timestamp rather than a duration.
        if (windowLength > AlarmManager.INTERVAL_HALF_DAY) {
            Slog.w(TAG, "Window length " + windowLength
                    + "ms suspiciously long; limiting to 1 hour");
            windowLength = AlarmManager.INTERVAL_HOUR;
        }

        // Sanity check the recurrence interval.  This will catch people who supply
        // seconds when the API expects milliseconds.
        final long minInterval = mConstants.MIN_INTERVAL;
        if (interval > 0 && interval < minInterval) {
            Slog.w(TAG, "Suspiciously short interval " + interval
                    + " millis; expanding to " + (minInterval/1000)
                    + " seconds");
            interval = minInterval;
        }

        if (type < RTC_WAKEUP || type > POWER_OFF_ALARM) {
            throw new IllegalArgumentException("Invalid alarm type " + type);
        }

        if (triggerAtTime < 0) {
            final long what = Binder.getCallingPid();
            Slog.w(TAG, "Invalid alarm trigger time! " + triggerAtTime + " from uid=" + callingUid
                    + " pid=" + what);
            triggerAtTime = 0;
        }

        // SPRD: Bug 431081. power-off alarm can't be set to past time. so check it here.
        if(type == POWER_OFF_ALARM && triggerAtTime < System.currentTimeMillis()){
            Slog.w(TAG, "Invalid alarm clock!! trigger time :"+triggerAtTime+ " is past-time. current time :"+System.currentTimeMillis());
            return;
        }
        // <end

        final long nowElapsed = SystemClock.elapsedRealtime();
        final long nominalTrigger = convertToElapsed(triggerAtTime, type);
        // Try to prevent spamming by making sure we aren't firing alarms in the immediate future
        final long minTrigger = nowElapsed + mConstants.MIN_FUTURITY;
        final long triggerElapsed = (nominalTrigger > minTrigger) ? nominalTrigger : minTrigger;

        final long maxElapsed;
        if (windowLength == AlarmManager.WINDOW_EXACT) {
            maxElapsed = triggerElapsed;
        } else if (windowLength < 0) {
            maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval);
            // Fix this window in place, so that as time approaches we don't collapse it.
            windowLength = maxElapsed - triggerElapsed;
        } else {
            maxElapsed = triggerElapsed + windowLength;
        }

        synchronized (mLock) {
            if (DEBUG_BATCH) {
                Slog.v(TAG, "set(" + operation + ") : type=" + type
                        + " triggerAtTime=" + triggerAtTime + " win=" + windowLength
                        + " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed
                        + " interval=" + interval + " flags=0x" + Integer.toHexString(flags));
            }

            mAlignHelper.notifyPowerGuru(type, triggerAtTime, triggerElapsed, windowLength,
                    maxElapsed, interval, operation, flags, workSource, alarmClock, callingUid, callingPackage);
            setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
                    interval, operation, directReceiver, listenerTag, flags, true, workSource,
                    alarmClock, callingUid, callingPackage);
        }
    }

AlarmManagerService.java-->setImplLocked()

    private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
            long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
            String listenerTag, int flags, boolean doValidate, WorkSource workSource,
            AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
        Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
                operation, directReceiver, listenerTag, workSource, flags, alarmClock,
                callingUid, callingPackage);
        try {
            if (ActivityManager.getService().isAppStartModeDisabled(callingUid, callingPackage)) {
                Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
                        + " -- package not allowed to start");
                return;
            }
        } catch (RemoteException e) {
        }
        removeLocked(operation, directReceiver);
        setImplLocked(a, false, doValidate);
    }
private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
        ......
        //有该变量代表之前调用过setIdleUntil接口了(进去Idle模式)
        } 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) {
                //没有这些flag的alarm,加入mPendingWhileIdleAlarms直接退出
                mPendingWhileIdleAlarms.add(a);
                return;
            }
        }
        ......
        // 判断alarm是否设置了FLAG_IDLE_UNTIL,说明进入了DONE模式
        if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {  
            mPendingIdleUntil = a;//设置mPendingIdleUntil代表进入Doze模式  
            mConstants.updateAllowWhileIdleMinTimeLocked();  
            needRebatch = true;//需要重新rebatch所有的alarm 
        }
        ......
    }

设置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设置下来都会正常了。

猜你喜欢

转载自blog.csdn.net/liu362732346/article/details/86596525