15. AlarmManagerService定时任务原理
摘要
本文从Android Framework源码角度深度剖析AlarmManagerService(AMS)的定时任务机制。AlarmManagerService是Android系统定时调度的核心服务,负责管理应用的闹钟、周期性任务、精确定时等功能。文章将详细解析AlarmManagerService的启动流程、闹钟类型与调度策略、批量处理机制(Batching)、唤醒对齐优化(Wakeup Alignment)、Doze模式下的行为、以及与PowerManagerService的协作关系。通过源码级分析,帮助开发者深入理解Android定时任务的底层实现,掌握高效省电的定时策略。
关键词: AlarmManagerService、定时任务、批量调度、Doze适配、省电优化
1. AlarmManagerService架构概览
1.1 AMS在系统中的位置
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 设置闹钟流程
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 批量处理原理
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下的闹钟行为
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核心架构
8.2 关键要点
- 闹钟类型:RTC/ELAPSED_REALTIME,WAKEUP/非WAKEUP
- 批量处理:15分钟内不模糊,之后5%模糊,最多15分钟
- Doze适配:allowWhileIdle 15分钟限制,普通闹钟延迟
- 精确闹钟:Android 12+需要SCHEDULE_EXACT_ALARM权限
- 省电策略:优先使用非唤醒、不精确闹钟
8.3 开发建议
- 非关键任务使用setInexactRepeating:允许批量,省电
- 避免频繁唤醒:最小间隔1分钟
- 适配Doze模式:关键任务使用allowWhileIdle
- 及时cancel:避免内存泄漏
- 考虑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

1万+

被折叠的 条评论
为什么被折叠?



