后台工作是移动操作系统中永恒的矛盾点。用户希望邮件同步、照片备份、信息流刷新、通知能够及时送达。但每一项后台操作都会消耗电池、占用网络带宽,还会与前台应用争抢 CPU 与内存资源。一台普通设备上安装有成百上千款应用,这就造成了公地悲剧:单个应用的后台工作看似合理,但叠加在一起会严重损耗电池续航。
十余年以来,Android 针对后台行为出台了越来越严格的限制机制。本章完整梳理后台执行整套基础设施:Android 8.0 引入的执行限制、JobScheduler(使用约束感知调度器替代零散后台任务)、负责定时唤醒的 AlarmManager、WorkManager 抽象层、前台服务及其迭代变更的要求,以及用于限制隐式唤醒的广播限制。
30.1 后台执行限制
Android 8.0(Oreo,API 26)引入了平台历史上力度最大的后台执行限制。在 Oreo 之前,任意应用都可以启动服务、注册广播接收器,几乎不受约束地执行后台任务。最终造成电池续航差、系统性能下降。
30.1.1 Oreo 之前存在的问题
Android 8.0 之前,后台滥用现象十分普遍:
- 应用启动长期运行的服务,无期限驻留后台
- 数十个应用注册同一类隐式广播(例如
CONNECTIVITY_CHANGED),引发广播风暴,唤醒每一个注册应用 - 后台服务无协调地消耗 CPU、内存、网络资源
- 用户无法直观看到哪些应用在后台消耗资源
30.1.2 后台服务限制
从 Android 8.0 开始,target API26 及以上的应用受如下约束:
后台服务限制:处于后台的应用不能随意调用startService()。如果应用不在前台(无可见 Activity、无正在运行的前台服务),调用startService()会抛出IllegalStateException。
允许的替代方案:
startForegroundService()— 启动服务,必须在 5 秒内发布一条通知JobScheduler.schedule()— 调度带约束的任务WorkManager.enqueue()— 调度可延迟任务(AndroidX)

30.1.3 前台状态定义
满足下面任意条件,系统判定应用处于前台:
| 条件 | 示例 |
|---|---|
| 拥有可见 Activity | 应用打开,显示在屏幕上 |
| 拥有前台服务 | 音乐播放器、导航、文件上传 |
| 被前台应用通过 ContentProvider 调用 | 前台应用正在使用该应用的 ContentProvider |
| 处于临时白名单 | 刚刚收到高优先级 FCM 消息 |
前台 / 后台的精确状态由ActivityManagerService中每个应用 UID 对应的UidRecord跟踪维护。
30.1.4 应用待机分组(App Standby Buckets)
Android 9.0(Pie,API28)引入应用待机分组,根据用户最近使用频率,对后台限制做进一步分级。
| 分组 | 判定条件 | Job 延迟时长 | Alarm 延迟时长 |
|---|---|---|---|
| Active | 正在使用或刚刚使用过 | 无限制 | 无限制 |
| Working Set | 经常使用,当前不在活跃状态 | 最多延迟 2 小时 | 最多延迟 6 分钟 |
| Frequent | 经常使用,但非每日使用 | 最多延迟 8 小时 | 最多延迟 30 分钟 |
| Rare | 很少使用 | 最多延迟 24 小时 | 最多延迟 2 小时 |
| Restricted | 极少交互,耗电高 | 最多 24 小时,每日最多 1 个 Job | 最多延迟 24 小时 |
分组由UsageStatsManagerInternal与AppStandbyInternal管理。
// JobSchedulerService中定义的分组索引
// frameworks/base/apex/jobscheduler/service/java/com/android/server/job/
// JobSchedulerService.java
public static final int ACTIVE_INDEX = 0;
public static final int WORKING_INDEX = 1;
public static final int FREQUENT_INDEX = 2;
public static final int RARE_INDEX = 3;
public static final int NEVER_INDEX = 4;
public static final int RESTRICTED_INDEX = 5;
public static final int EXEMPTED_INDEX = 6;
30.1.5 Doze 模式与应用待机
Android 6.0 引入 Doze 模式:当设备静置、未充电、屏幕长时间熄灭时,限制后台活动。Android7.0 新增轻量 “移动 Doze”,屏幕熄灭即可触发,即便设备在移动状态。

Doze 模式下行为:
- 禁止网络访问
- JobScheduler 任务不执行
- 不同步数据
- 闹钟全部延迟
- 唤醒锁失效
Doze 期间系统行为:
- 延迟全部闹钟(
setAlarmClock()以及白名单精确闹钟除外) - 阻断网络访问
- 暂停 Job 与同步任务
- 忽略唤醒锁
- 周期性打开维护窗口,执行被延迟的任务
30.1.6 省电模式
省电模式(用户手动开启或者低电量自动触发)会叠加更多限制:
- 降低后台网络访问
- 延迟 Job 与闹钟
- 降低定位精度
- 限制后台 CPU 占用
- 限制视觉效果(动画、动态壁纸)
30.1.7 后台限制演进历史
| Android 版本 | API | 核心限制 |
|---|---|---|
| 6.0 (Marshmallow) | 23 | Doze 模式、应用待机 |
| 7.0 (Nougat) | 24 | 移动 Doze、隐式广播受限 |
| 8.0 (Oreo) | 26 | 后台服务限制、广播限制 |
| 9.0 (Pie) | 28 | 应用待机分组 |
| 10 (Q) | 29 | 后台启动 Activity 限制 |
| 11 (R) | 30 | 强制前台服务类型 |
| 12 (S) | 31 | 精确闹钟限制、后台启动前台服务限制 |
| 12L | 32 | 进一步收紧前台服务限制 |
| 13 (T) | 33 | 按应用语言、细化运行时权限 |
| 14 (U) | 34 | 强制前台服务类型、SCHEDULE_EXACT_ALARM权限受限 |
| 15 (V) | 35 | dataSync 前台服务 6 小时超时 |
| 16 | 36 | 用户发起任务 (UIJ) 通知集中管控,UIJ 通知禁止随意关闭 |
| 17 | 37 | 新增getPendingJobReasons*()诊断接口、废弃 Job 检测、按网络连接批量调度、Perfetto Job 追踪、start‑user‑before‑alarm |
30.2 JobScheduler
JobScheduler 是 Android 调度可延迟后台任务的核心机制,Android5.0(API21)引入。应用声明需要执行的工作与执行条件,由系统决定实际运行时机。系统可以批量合并任务、推迟到合适时机(充电、Wi‑Fi 环境)执行,同时强制执行待机分组配额。
30.2.1 架构总览

全部核心组件位于 JobScheduler APEX 模块。
源码路径:frameworks/base/apex/jobscheduler/service/java/com/android/server/job/
30.2.2 JobSchedulerService
JobSchedulerService是总调度协调器,继承SystemService,实现两个监听接口。
// frameworks/base/apex/jobscheduler/service/java/com/android/server/job/
// JobSchedulerService.java
public class JobSchedulerService extends com.android.server.SystemService
implements StateChangedListener, JobCompletedListener {
public static final String TAG = "JobScheduler";
/** 单个应用允许调度的默认最大任务数量 */
private static final int DEFAULT_MAX_JOBS_PER_APP = 150;
/** 任务主存储列表 */
final JobStore mJobs;
/** 控制器列表,通知服务任务状态变更 */
final List<StateController> mControllers;
/** 就绪待执行任务队列 */
private final PendingJobQueue mPendingJobQueue = new PendingJobQueue();
/** 管理任务并发执行槽位 */
final JobConcurrencyManager mConcurrencyManager;
// ...
}
关键常量与限制:
DEFAULT_MAX_JOBS_PER_APP = 150:单应用默认最多 150 个调度任务;该数值通过构造函数传入,可配置,不是硬编码常量。NUM_COMPLETED_JOB_HISTORY = 20:系统保存最近 20 个已完成任务用于调试。
构造函数会初始化JobPerfettoTracer(参见 §30.7),任务完整生命周期输出到 Perfetto 跟踪。
this(context, DEFAULT_MAX_JOBS_PER_APP, null, JobPerfettoTracer.create());
30.2.3 JobInfo:定义任务与约束
应用使用JobInfo.Builder描述任务。
JobInfo jobInfo = new JobInfo.Builder(JOB_ID,
new ComponentName(context, MyJobService.class))
// 约束条件
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.setRequiresCharging(true)
.setRequiresDeviceIdle(true)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
// 时间配置
.setMinimumLatency(15 * 60 * 1000) // 至少延迟15分钟后执行
.setOverrideDeadline(60 * 60 * 1000) // 1小时内必须执行
.setPeriodic(24 * 60 * 60 * 1000) // 每24小时重复执行
// 持久化,重启后保留
.setPersisted(true)
// Content URI内容触发器
.addTriggerContentUri(
new JobInfo.TriggerContentUri(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS))
// 重试退避策略
.setBackoffCriteria(30_000, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
// 加急任务(API31+)
.setExpedited(true)
// 预估网络流量
.setEstimatedNetworkBytes(
5 * 1024 * 1024, // 下载5MB
1024 * 1024) // 上传1MB
.build();
// 提交调度
JobScheduler scheduler = context.getSystemService(JobScheduler.class);
int result = scheduler.schedule(jobInfo);
// result == JobScheduler.RESULT_SUCCESS 或 RESULT_FAILURE
30.2.4 约束类型
约束系统是 JobScheduler 的核心,每种约束由独立StateController管理。
| 约束 | 控制器 | 源码文件 |
|---|---|---|
| 网络类型 / 网络连通性 | ConnectivityController | controllers/ConnectivityController.java |
| 时间(延迟、截止时间、周期) | TimeController | controllers/TimeController.java |
| 设备空闲 | IdleController | controllers/IdleController.java |
| 充电 / 电量充足 | BatteryController | controllers/BatteryController.java |
| 存储空间充足 | StorageController | controllers/StorageController.java |
| Content URI 变更 | ContentObserverController | controllers/ContentObserverController.java |
| 配额管控 | QuotaController | controllers/QuotaController.java |
| 弹性约束降级 | FlexibilityController | controllers/FlexibilityController.java |
| 后台限制 | BackgroundJobsController | controllers/BackgroundJobsController.java |
| Doze 模式 | DeviceIdleJobsController | controllers/DeviceIdleJobsController.java |
| 应用组件启用状态 | ComponentController | controllers/ComponentController.java |
| 预取时机控制 | PrefetchController | controllers/PrefetchController.java |
全部控制器源码路径: frameworks/base/apex/jobscheduler/service/java/com/android/server/job/controllers/
部分控制器(BatteryController、ConnectivityController、IdleController)继承RestrictingController而非直接继承StateController。该类增加两个钩子:startTrackingRestrictedJobLocked()、stopTrackingRestrictedJobLocked(),专门跟踪处于RESTRICTED待机分组的应用任务,该分组约束执行条件更加严格。
IdleController 的空闲检测逻辑在controllers/idle/子包下:包含DeviceIdlenessTracker、CarIdlenessTracker,以及IdlenessTracker/IdlenessListener接口,手机与车载设备可以使用不同的 “空闲” 判定逻辑。
30.2.5 StateController 架构
所有控制器继承抽象基类StateController。
// frameworks/base/apex/jobscheduler/service/java/com/android/server/job/
// controllers/StateController.java
public abstract class StateController {
protected final JobSchedulerService mService;
protected final StateChangedListener mStateChangedListener;
protected final Context mContext;
protected final Object mLock;
/**
* 实现该逻辑,判断控制器是否需要跟踪该任务
*/
public abstract void maybeStartTrackingJobLocked(
JobStatus jobStatus, JobStatus lastJob);
/**
* 将任务从控制器跟踪列表移除
*/
public abstract void maybeStopTrackingJobLocked(
JobStatus jobStatus, JobStatus lastJob);
/**
* 控制器状态变更时调用,重新评估就绪任务
*/
public void evaluateStateLocked(JobStatus jobStatus) {}
}
每个控制器维护一组被跟踪任务,在JobStatus对象上维护约束满足标记位。当控制器状态发生变化(例如设备连上 Wi‑Fi),通过StateChangedListener通知JobSchedulerService,重新评估待执行任务。
30.2.6 JobStatus:任务内部表示
JobStatus是调度器内部对任务的表示,融合原始JobInfo与控制器维护的运行时状态。
// frameworks/base/apex/jobscheduler/service/java/com/android/server/job/
// controllers/JobStatus.java
/**
* 任务内部唯一标识
* 任务被调度时,由公开API JobInfo生成
* 保存任务各项约束的当前状态,提供判断是否可运行的函数
*/
public final class JobStatus {
// 显式约束低位,与JobInfo.CONSTRAINT_FLAG_*共用
public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 << 0
public static final int CONSTRAINT_BATTERY_NOT_LOW =
JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
public static final int CONSTRAINT_STORAGE_NOT_LOW =
JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
// 其余显式约束占用高位比特
public static final int CONSTRAINT_TIMING_DELAY = 1 << 31;
public static final int CONSTRAINT_DEADLINE = 1 << 30;
public static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
public static final int CONSTRAINT_CONTENT_TRIGGER = 1 << 26;
// 系统额外叠加的隐式约束(应用未声明)
public static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // 隐式约束
public static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // 隐式约束
public static final int CONSTRAINT_PREFETCH = 1 << 23;
public static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // 隐式约束
public static final int CONSTRAINT_FLEXIBLE = 1 << 21; // 隐式约束
// 全部约束满足时任务才可执行
public boolean isReady() {
return isReady(mSatisfiedConstraintsOfInterest);
}
}
低 4 位(充电、电量充足、设备空闲、存储充足)与应用设置的JobInfo.CONSTRAINT_FLAG_*共用;高位保存时间、网络、内容触发器,同时叠加系统隐式约束(非 Doze 状态、配额可用、后台未被限制、弹性降级标记)。
这就是为什么仅仅设置网络约束的任务依然可能处于等待状态:Doze、配额、后台限制同样是约束条件,由对应控制器评估。
30.2.7 任务调度流程

30.2.8 JobStore:持久化存储
JobStore 将调度任务持久化为 XML 文件,设备重启后任务不丢失。
源码路径:frameworks/base/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
// frameworks/base/apex/jobscheduler/service/java/com/android/server/job/
// JobStore.java
package com.android.server.job;
// 任务存储路径 /data/system/job/jobs.xml
// XML格式,保存每个任务约束、时间、元数据
文件使用AtomicFile原子写入,防止异常关机文件损坏。开机时 JobStore 读取该文件,恢复所有通过setPersisted(true)设置持久化的任务。
30.2.9 ConnectivityController:网络约束
ConnectivityController跟踪网络状态,判断任务网络约束是否满足。
源码路径:frameworks/base/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
// frameworks/base/apex/jobscheduler/service/java/com/android/server/job/
// controllers/ConnectivityController.java
package com.android.server.job.controllers;
//


1731

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



