Android Alarm 工作原理(java)

一、APP 使用

闹钟设置步骤:

  • 定义一个 PendingIntent,用户闹钟到期时触发相应动作(发送广播、启动服务等);
  • 调用 AlarmManager 的 set 函数设置闹钟;
  • 对于第一步使用广播方式,需要定义广播接收器,捕捉闹钟到期广播;

1、时钟类型

Android framework 中定义的时钟类型:

public static final int RTC_WAKEUP = 0;
public static final int RTC = 1;
public static final int ELAPSED_REALTIME_WAKEUP = 2;
public static final int ELAPSED_REALTIME = 3;

对应的 Native 中使用的时钟类型:

static const clockid_t android_alarm_to_clockid[N_ANDROID_TIMERFDS] = {
    CLOCK_REALTIME_ALARM,
    CLOCK_REALTIME,
    CLOCK_BOOTTIME_ALARM,
    CLOCK_BOOTTIME,
    CLOCK_MONOTONIC,
    CLOCK_REALTIME,
};

上层时钟类型作为索引,在 android_alarm_to_clockid 表中取到的时钟类型,即为底层使用的时钟。比如上层时钟类型为 ELAPSED_REALTIME_WAKEUP,那么底层时钟类型为 CLOCK_BOOTTIME_ALARM。实际使用过程中 RTC_WAKEUP、RTC 两种类型,会在 framework 层中转换层 ELAPSED_REALTIME_WAKEUP、ELAPSED_REALTIME 时钟。
RTC 表示 Unix 时间,ELAPSED_REALTIME 表示系统启动到现在经过的时间。两种时间都 WAKEUP 的版本,表示可以唤醒系统。

2、使用实例

PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1,
        new Intent(context, AlarmReceiver.class).setAction("testtime"), PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE);
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 15000 * 60 * 1L, pendingIntent);

二、原理分析

1、闹钟设置

APP 设置闹钟最终都会调用 AlarmManager 的 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)

参数说明:

变量名含义
type类型:RTC_WAKEUP、RTC、ELAPSED_REALTIME_WAKEUP、ELAPSED_REALTIME
triggerAtMillis闹钟触发时间(与 type 对应设置)
windowMillis窗口时间,精确定间设置 WINDOW_EXACT(0)
intervalMillis重复闹钟间隔时间
flags标记:FLAG_STANDALONE、FLAG_WAKE_FROM_IDLE、FLAG_ALLOW_WHILE_IDLE、FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED、FLAG_IDLE_UNTIL
operationPendingIntent
listener与 operation 必须二选其一;重复闹钟,只能设置 operation 参数。
listenerTag与 listener 配置使用
targetHandlerOnAlarmListener 的 onAlarm() 方法将通过 targetHandler 调用,如果 targetHandler 为 null,则 onAlarm 将在应用程序的 main looper 上调用。

setImpl 函数通过 Binder 会调用到 AlarmManagerService 的 set 函数:

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
            || UserHandle.isSameApp(callingUid, mSystemUiUid)
            || ((mAppStateTracker != null)
                && mAppStateTracker.isUidPowerSaveUserWhitelisted(callingUid)))) {
        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);
}

注意 FLAG_WAKE_FROM_IDLE、FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED 标记是不能通过输入参数直接设置的,在后续适当的时候将被自动赋值。set 会调用其实现版本 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) {
    ...
    final long nowElapsed = mInjector.getElapsedRealtime();
    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;
    ...
    synchronized (mLock) {
        ...
        setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
                interval, operation, directReceiver, listenerTag, flags, true, workSource,
                alarmClock, callingUid, callingPackage);
    }
}

setImpl 中将 RTC 使用换成 Elapsed 时钟,然后调用 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");
            mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER,
                    operation).sendToTarget();
            return;
        }
    } catch (RemoteException e) {
    }
    removeLocked(operation, directReceiver);
    incrementAlarmCount(a.uid);
    setImplLocked(a, false, doValidate);
}

将参数构成一个 Alarm 对象,然后调用 setImplLocked 的重载版本:

private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
    ...
    adjustDeliveryTimeBasedOnBucketLocked(a);
    insertAndBatchAlarmLocked(a);

    if (a.alarmClock != null) {
        mNextAlarmClockMayChange = true;
    }
    ...
    if (!rebatching) {
        ...
        if (needRebatch) {
            rebatchAllAlarmsLocked(false);
        }

        rescheduleKernelAlarmsLocked();
        updateNextAlarmClockLocked();
    }
}

adjustDeliveryTimeBasedOnBucketLocked 会根据 Standby 机制调整闹钟的到期时间,insertAndBatchAlarmLocked 将单独的 Alarm 添加到批处理,rescheduleKernelAlarmsLocked 函数会将最近一次闹钟设置到 kernel。

void rescheduleKernelAlarmsLocked() {
    // Schedule the next upcoming wakeup alarm.  If there is a deliverable batch
    // prior to that which contains no wakeups, we schedule that as well.
    final long nowElapsed = mInjector.getElapsedRealtime();
    long nextNonWakeup = 0;
    if (mAlarmBatches.size() > 0) {
        final Batch firstWakeup = findFirstWakeupBatchLocked();
        final Batch firstBatch = mAlarmBatches.get(0);
        if (firstWakeup != null) {
            mNextWakeup = firstWakeup.start;
            mNextWakeUpSetAt = nowElapsed;
            setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
        }
        if (firstBatch != firstWakeup) {
            nextNonWakeup = firstBatch.start;
        }
    }
    if (mPendingNonWakeupAlarms.size() > 0) {
        if (nextNonWakeup == 0 || mNextNonWakeupDeliveryTime < nextNonWakeup) {
            nextNonWakeup = mNextNonWakeupDeliveryTime;
        }
    }
    if (nextNonWakeup != 0) {
        mNextNonWakeup = nextNonWakeup;
        mNextNonWakeUpSetAt = nowElapsed;
        setLocked(ELAPSED_REALTIME, nextNonWakeup);
    }
}

其中 setLocked 函数实现如下:

private void setLocked(int type, long when) {
    if (mInjector.isAlarmDriverPresent()) {
        mInjector.setAlarm(type, when);
    } else {
        Message msg = Message.obtain();
        msg.what = AlarmHandler.ALARM_EVENT;

        mHandler.removeMessages(msg.what);
        mHandler.sendMessageAtTime(msg, when);
    }
}

setLocked 有两种实现方式,默认 isAlarmDriverPresent 的返回值为 true,则会调用 setAlarm,通过 native 实现定时操作。只有当 native alarm 初始化出错时,才会走 else 分支,通过 MessageQueue 方式实现定时。这里暂且不分析 native alarm 实现。闹钟设置至此已经完成。
set 的调用顺序图如下:
在这里插入图片描述

2、闹钟触发

那么何时处理闹钟到期事件呢?可以看到在服务的 onStart 函数函数中,启动了一个线程负责闹钟到期事件处理:

if (mInjector.isAlarmDriverPresent()) {
    AlarmThread waitThread = new AlarmThread();
    waitThread.start();
} else {
    Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
}

AlarmThread 线程处理函数如下:

public void run()
{
    ArrayList<Alarm> triggerList = new ArrayList<Alarm>();

    while (true)
    {
        int result = mInjector.waitForAlarm();
        final long nowRTC = mInjector.getCurrentTimeMillis();
        final long nowELAPSED = mInjector.getElapsedRealtime();
        synchronized (mLock) {
            mLastWakeup = nowELAPSED;
        }
        ...
        triggerList.clear();

        if ((result & TIME_CHANGED_MASK) != 0) {
            ...
        }

        if (result != TIME_CHANGED_MASK) {
            // If this was anything besides just a time change, then figure what if
            // anything to do about alarms.
            synchronized (mLock) {
                mLastTrigger = nowELAPSED;
                ...
                boolean hasWakeup = triggerAlarmsLocked(triggerList, nowELAPSED);
                if (!hasWakeup && checkAllowNonWakeupDelayLocked(nowELAPSED)) {
                    ...
                } else {
                    ...
                    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();
            }
        }
    }
}

waitForAlarm 函数会阻塞线程,直到有闹钟到期,或则修改了系统时间。通过返回值可以判断哪一路时钟唤醒了线程,然后调用 triggerAlarmsLocked 获取到期的 Alarm 保存到 triggerList。

boolean triggerAlarmsLocked(ArrayList<Alarm> triggerList, final long nowELAPSED) {
    boolean hasWakeup = false;
    while (mAlarmBatches.size() > 0) {
        Batch batch = mAlarmBatches.get(0);
        if (batch.start > nowELAPSED) {
            // Everything else is scheduled for the future
            break;
        }
        mAlarmBatches.remove(0);

        final int N = batch.size();
        for (int i = 0; i < N; i++) {
            Alarm alarm = batch.get(i);
            ...
            alarm.count = 1;
            triggerList.add(alarm);
            ...
            if (alarm.repeatInterval > 0) {
                alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
                // Also schedule its next recurrence
                final long delta = alarm.count * alarm.repeatInterval;
                final long nextElapsed = alarm.expectedWhenElapsed + delta;
                setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
                        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;
            }
            ...
        }
    }

    mCurrentSeq++;
    calculateDeliveryPriorities(triggerList);
    Collections.sort(triggerList, mAlarmDispatchComparator);

    return hasWakeup;
}

对于重复闹钟,计算下一次唤醒的时间,并调用 setImplLocked 设置新的闹钟。获取到期闹钟列表后,调用 deliverAlarmsLocked 函数处理闹钟:

void deliverAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED) {
    mLastAlarmDeliveryTime = nowELAPSED;
    for (int i=0; i<triggerList.size(); i++) {
        Alarm alarm = triggerList.get(i);
        ...
        mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
        ...
    }
}

deliverAlarmsLocked 函数会触发 PendingInent 预定义的动作,或则直接回调预定义监听函数。

public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
    final long workSourceToken = ThreadLocalWorkSource.setUid(
            getAlarmAttributionUid(alarm));
    try {
        if (alarm.operation != null) {
            ...
            alarm.operation.send(getContext(), 0,
                    mBackgroundIntent.putExtra(
                            Intent.EXTRA_ALARM_COUNT, alarm.count),
                    mDeliveryTracker, mHandler, null,
                    allowWhileIdle ? mIdleOptions : null);
        } else {
            ...
            alarm.listener.doAlarm(this);
            ...
        }
    } finally {
        ThreadLocalWorkSource.restore(workSourceToken);
    }
    ...
}

闹钟触发顺序图:
在这里插入图片描述

至此 java 部分的调用流程基本分析完成。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翻滚吧香香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值