15. AlarmManagerService定时任务原理

15. AlarmManagerService定时任务原理

摘要

本文从Android Framework源码角度深度剖析AlarmManagerService(AMS)的定时任务机制。AlarmManagerService是Android系统定时调度的核心服务,负责管理应用的闹钟、周期性任务、精确定时等功能。文章将详细解析AlarmManagerService的启动流程、闹钟类型与调度策略、批量处理机制(Batching)、唤醒对齐优化(Wakeup Alignment)、Doze模式下的行为、以及与PowerManagerService的协作关系。通过源码级分析,帮助开发者深入理解Android定时任务的底层实现,掌握高效省电的定时策略。

关键词: AlarmManagerService、定时任务、批量调度、Doze适配、省电优化


1. AlarmManagerService架构概览

1.1 AMS在系统中的位置

应用层

内核层

SystemServer进程

Binder

JNI

WakeLock

Doze检查

触发

AlarmManagerService

AlarmThread

PowerManagerService

DeviceIdleController

dev/alarm驱动

RTC硬件

AlarmManager API

应用PendingIntent

1.2 闹钟类型

// frameworks/base/core/java/android/app/AlarmManager.java
public class AlarmManager {
    // 相对时间,休眠时不触发
    public static final int ELAPSED_REALTIME = 3;

    // 相对时间,休眠时唤醒
    public static final int ELAPSED_REALTIME_WAKEUP = 2;

    // 绝对时间(墙上时间),休眠时不触发
    public static final int RTC = 1;

    // 绝对时间,休眠时唤醒
    public static final int RTC_WAKEUP = 0;
}

2. AlarmManagerService启动流程

2.1 构造函数

// frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
public class AlarmManagerService extends SystemService {
    // 闹钟队列(按触发时间排序)
    final ArrayList<Alarm> mAlarmBatches = new ArrayList<>();

    // 挂起的闹钟(Doze模式)
    final ArrayList<Alarm> mPendingWhileIdleAlarms = new ArrayList<>();

    // 常量
    private static final long MIN_FUZZABLE_INTERVAL = 10000;  // 10秒
    private static final long MIN_INTERVAL = 60000;  // 1分钟

    public AlarmManagerService(Context context) {
        super(context);
        mConstants = new Constants(mHandler);

        // 创建闹钟线程
        mAlarmThread = new AlarmThread();
        mAlarmThread.start();

        // 初始化Native层
        mNativeData = init();

        // 初始化统计
        mNextWakeFromIdle = null;
        mNextAlarmClockForUser = new SparseArray<>();
    }

    private static native long init();
    private static native void close(long nativeData);
    private static native void set(long nativeData, int type, long seconds, long nanoseconds);
    private static native int waitForAlarm(long nativeData);
}

2.2 AlarmThread工作流程

// frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
private class AlarmThread extends Thread {
    public AlarmThread() {
        super("AlarmManager");
    }

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

        while (true) {
            // 1. 阻塞等待下一个闹钟到期
            int result = waitForAlarm(mNativeData);

            triggerList.clear();

            synchronized (mLock) {
                final long nowRTC = System.currentTimeMillis();
                final long nowELAPSED = SystemClock.elapsedRealtime();

                // 2. 收集所有到期的闹钟
                triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);

                // 3. 更新下一个闹钟时间
                rescheduleKernelAlarmsLocked();
                updateNextAlarmClockLocked();
            }

            // 4. 触发闹钟(在锁外执行,避免死锁)
            for (int i = 0; i < triggerList.size(); i++) {
                Alarm alarm = triggerList.get(i);
                try {
                    alarm.operation.send();
                } catch (PendingIntent.CanceledException e) {
                    // PendingIntent已取消
                }
            }
        }
    }
}

3. 闹钟设置与调度

3.1 设置闹钟流程

内核驱动Native层AlarmManagerServiceAlarmManager应用内核驱动Native层AlarmManagerServiceAlarmManager应用时间到达setExact(type, time, pi)set(type, time, pi, uid)setImpl()insertAndBatchAlarmLocked()计算批量窗口插入到mAlarmBatchesrescheduleKernelAlarmsLocked()set(type, triggerTime)ioctl(ANDROID_ALARM_SET)中断通知waitForAlarm返回triggerAlarmsLocked()PendingIntent.send()

3.2 setImpl核心实现

// frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
private 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) {

    // 1. 参数校验
    if (triggerAtTime < 0) {
        Slog.w(TAG, "Invalid alarm trigger time " + triggerAtTime);
        return;
    }

    // 2. 创建Alarm对象
    Alarm alarm = new Alarm(type, triggerAtTime, windowLength, interval, operation,
            directReceiver, listenerTag, workSource, flags, alarmClock, callingUid,
            callingPackage);

    synchronized (mLock) {
        // 3. 权限检查(精确闹钟需要特殊权限)
        if (isExactAlarmChangeEnabled()) {
            if (!hasPermission(callingUid, callingPackage)) {
                throw new SecurityException("Exact alarms require permission");
            }
        }

        // 4. 插入闹钟并批量处理
        setImplLocked(alarm, false, true);
    }
}

private void setImplLocked(Alarm alarm, boolean rebatching, boolean doValidate) {
    // 1. 移除旧闹钟(如果存在相同的PendingIntent)
    removeLocked(alarm.operation, null);

    // 2. 计算批量窗口
    long nowElapsed = SystemClock.elapsedRealtime();
    final int fuzz = fuzzForDuration(alarm.windowLength);

    // 3. 插入到批量队列
    insertAndBatchAlarmLocked(alarm);

    // 4. 更新下一个闹钟时间
    if (alarm.alarmClock != null) {
        mNextAlarmClockMayChange = true;
    }

    // 5. 调度内核闹钟
    boolean needRebatch = false;
    if (!rebatching) {
        rescheduleKernelAlarmsLocked();
        updateNextAlarmClockLocked();
    }
}

4. 批量处理机制(Batching)

4.1 批量处理原理

闹钟1
10:00

批量窗口
10:00-10:05

闹钟2
10:02

闹钟3
10:04

统一在10:05触发
减少唤醒次数

4.2 批量窗口计算

// frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
private void insertAndBatchAlarmLocked(Alarm alarm) {
    final long nowElapsed = SystemClock.elapsedRealtime();
    final long triggerElapsed = convertToElapsed(alarm.whenElapsed, alarm.type);

    // 计算最大延迟时间
    final long maxElapsed;
    if (alarm.windowLength == AlarmManager.WINDOW_EXACT) {
        // 精确闹钟,不批量
        maxElapsed = triggerElapsed;
    } else if (alarm.windowLength == AlarmManager.WINDOW_HEURISTIC) {
        // 启发式窗口,根据间隔计算
        maxElapsed = triggerElapsed + fuzzForDuration(alarm.repeatInterval);
    } else {
        // 指定窗口
        maxElapsed = triggerElapsed + alarm.windowLength;
    }

    // 查找或创建批量组
    Batch batch = null;
    for (Batch b : mAlarmBatches) {
        if (b.canHold(triggerElapsed, maxElapsed)) {
            batch = b;
            break;
        }
    }

    if (batch == null) {
        batch = new Batch(alarm);
        mAlarmBatches.add(batch);
        Collections.sort(mAlarmBatches);
    } else {
        batch.add(alarm);
    }
}

// 批量模糊因子计算
private int fuzzForDuration(long duration) {
    if (duration < 15 * 60 * 1000) {
        // 15分钟内:不模糊
        return 0;
    } else if (duration < 90 * 60 * 1000) {
        // 90分钟内:5%模糊
        return (int)(duration * 0.05);
    } else {
        // 90分钟以上:最多15分钟模糊
        return 15 * 60 * 1000;
    }
}

5. Doze模式适配

5.1 Doze下的闹钟行为

setExactAndAllowWhileIdle

setAndAllowWhileIdle

普通set/setExact

设置闹钟

闹钟类型

Doze期间允许触发
15分钟限制一次

Doze期间允许触发
9分钟批量窗口

Doze期间延迟
退出Doze后触发

维护窗口触发

等待退出Doze

5.2 allowWhileIdle实现

// frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
private void setImplLocked(Alarm alarm, boolean rebatching, boolean doValidate) {
    // 检查Doze模式
    if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
        // 允许在Doze期间触发
        final long lastTime = mLastAllowWhileIdleDispatch.get(alarm.uid, 0);
        final long minTime = lastTime + mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;

        if (alarm.whenElapsed < minTime) {
            // 距离上次触发不足15分钟,延迟
            alarm.whenElapsed = minTime;
            alarm.maxWhenElapsed = minTime;
        }

        // 标记为allowWhileIdle闹钟
        alarm.type |= AlarmManager.RTC_WAKEUP;
    }

    // 在Doze模式下,非allowWhileIdle闹钟会被延迟
    if (mPendingIdleUntil != null && (alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) == 0) {
        mPendingWhileIdleAlarms.add(alarm);
        return;  // 不立即调度
    }

    // 正常插入队列
    insertAndBatchAlarmLocked(alarm);
}

// 退出Doze时恢复闹钟
void endIdleLocked(long nowElapsed) {
    mPendingIdleUntil = null;

    // 恢复挂起的闹钟
    if (mPendingWhileIdleAlarms.size() > 0) {
        ArrayList<Alarm> alarms = mPendingWhileIdleAlarms;
        mPendingWhileIdleAlarms = new ArrayList<>();

        for (int i = 0; i < alarms.size(); i++) {
            setImplLocked(alarms.get(i), false, false);
        }

        rescheduleKernelAlarmsLocked();
    }
}

6. 实战案例

6.1 案例1:设置精确闹钟

public class AlarmExample {
    private AlarmManager mAlarmManager;
    private PendingIntent mPendingIntent;

    public void setExactAlarm(Context context) {
        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        Intent intent = new Intent(context, AlarmReceiver.class);
        mPendingIntent = PendingIntent.getBroadcast(context, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

        long triggerTime = System.currentTimeMillis() + 60 * 1000;  // 1分钟后

        // Android 6.0+需要考虑Doze模式
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 允许在Doze期间触发(15分钟限制)
            mAlarmManager.setExactAndAllowWhileIdle(
                AlarmManager.RTC_WAKEUP,
                triggerTime,
                mPendingIntent
            );
        } else {
            mAlarmManager.setExact(
                AlarmManager.RTC_WAKEUP,
                triggerTime,
                mPendingIntent
            );
        }
    }

    public void cancelAlarm() {
        if (mAlarmManager != null && mPendingIntent != null) {
            mAlarmManager.cancel(mPendingIntent);
        }
    }
}

AndroidManifest.xml

<!-- 精确闹钟权限(Android 12+) -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>

<receiver android:name=".AlarmReceiver"
    android:exported="false"/>

6.2 案例2:重复闹钟

public class RepeatingAlarmExample {
    public void setRepeatingAlarm(Context context) {
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        Intent intent = new Intent(context, RepeatingReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
                PendingIntent.FLAG_IMMUTABLE);

        long triggerTime = System.currentTimeMillis() + 60 * 1000;
        long interval = 15 * 60 * 1000;  // 15分钟

        // 不精确重复(会被批量处理,省电)
        am.setInexactRepeating(
            AlarmManager.RTC_WAKEUP,
            triggerTime,
            interval,
            pi
        );

        // 精确重复(不推荐,耗电)
        // am.setRepeating(AlarmManager.RTC_WAKEUP, triggerTime, interval, pi);
    }
}

6.3 案例3:闹钟时钟(AlarmClock)

public class AlarmClockExample {
    public void setAlarmClock(Context context) {
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        // 设置明天早上8点的闹钟
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_YEAR, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 8);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);

        Intent intent = new Intent(context, AlarmActivity.class);
        PendingIntent showIntent = PendingIntent.getActivity(context, 0, intent,
                PendingIntent.FLAG_IMMUTABLE);

        Intent alarmIntent = new Intent(context, AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, alarmIntent,
                PendingIntent.FLAG_IMMUTABLE);

        // AlarmClockInfo会在状态栏显示闹钟图标
        AlarmManager.AlarmClockInfo alarmClockInfo =
                new AlarmManager.AlarmClockInfo(calendar.getTimeInMillis(), showIntent);

        am.setAlarmClock(alarmClockInfo, pi);
    }
}

7. 性能优化与最佳实践

7.1 选择合适的闹钟类型

// ✅ 推荐:非唤醒闹钟(屏幕关闭时不触发,省电)
am.set(AlarmManager.ELAPSED_REALTIME, triggerTime, pi);

// ✅ 推荐:不精确闹钟(允许批量,省电)
am.setInexactRepeating(AlarmManager.RTC_WAKEUP, triggerTime, interval, pi);

// ⚠️ 谨慎:唤醒闹钟(会唤醒设备)
am.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pi);

// ❌ 避免:精确重复闹钟(频繁唤醒,耗电)
am.setRepeating(AlarmManager.RTC_WAKEUP, triggerTime, interval, pi);

7.2 Doze模式适配策略

// 1. 非关键任务:使用普通闹钟(Doze期间延迟)
am.set(AlarmManager.RTC_WAKEUP, triggerTime, pi);

// 2. 关键任务:使用allowWhileIdle(15分钟限制)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pi);
}

// 3. 非常关键:使用精确闹钟(需要权限)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pi);
}

// 4. 替代方案:使用WorkManager
OneTimeWorkRequest workRequest = new OneTimeWorkRequestBuilder<MyWorker>()
    .setInitialDelay(60, TimeUnit.SECONDS)
    .build();
WorkManager.getInstance(context).enqueue(workRequest);

7.3 避免常见陷阱

// ❌ 错误:没有cancel导致内存泄漏
am.setRepeating(..., pi);
// 忘记cancel

// ✅ 正确:及时cancel
am.cancel(pi);

// ❌ 错误:使用过短的间隔
am.setRepeating(..., 1000, pi);  // 1秒,太频繁

// ✅ 正确:最小间隔1分钟
am.setRepeating(..., 60 * 1000, pi);

// ❌ 错误:没有处理设备重启
// 重启后闹钟丢失

// ✅ 正确:监听BOOT_COMPLETED重新设置
<receiver android:name=".BootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>

8. 总结

8.1 AlarmManagerService核心架构

AlarmManagerService

AlarmThread
等待触发

批量队列
mAlarmBatches

挂起队列
mPendingWhileIdleAlarms

Native层
alarm驱动

批量窗口计算

Doze模式管理

内核RTC硬件

减少唤醒次数

省电优化

8.2 关键要点

  1. 闹钟类型:RTC/ELAPSED_REALTIME,WAKEUP/非WAKEUP
  2. 批量处理:15分钟内不模糊,之后5%模糊,最多15分钟
  3. Doze适配:allowWhileIdle 15分钟限制,普通闹钟延迟
  4. 精确闹钟:Android 12+需要SCHEDULE_EXACT_ALARM权限
  5. 省电策略:优先使用非唤醒、不精确闹钟

8.3 开发建议

  1. 非关键任务使用setInexactRepeating:允许批量,省电
  2. 避免频繁唤醒:最小间隔1分钟
  3. 适配Doze模式:关键任务使用allowWhileIdle
  4. 及时cancel:避免内存泄漏
  5. 考虑WorkManager替代:自动处理Doze和电池优化

AlarmManagerService是Android定时任务的核心,合理使用可以在保证功能的同时最大化省电。


参考资料

  • Android源码:frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
  • Android源码:frameworks/base/core/java/android/app/AlarmManager.java
  • Android官方文档:Schedule repeating alarms
  • Android官方文档:Optimize for Doze and App Standby
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

龚寿生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值