ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

第 30 章:后台任务调度

2026/8/21 9:24:51 拓冰建站 浏览量
第 30 章:后台任务调度 后台工作是移动操作系统中永恒的矛盾点。用户希望邮件同步、照片备份、信息流刷新、通知能够及时送达。但每一项后台操作都会消耗电池、占用网络带宽,还会与前台应用争抢 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)23Doze 模式、应用待机7.0 (Nougat)24移动 Doze、隐式广播受限8.0 (Oreo)26后台服务限制、广播限制9.0 (Pie)28应用待机分组10 (Q)29后台启动 Activity 限制11 (R)30强制前台服务类型12 (S)31精确闹钟限制、后台启动前台服务限制12L32进一步收紧前台服务限制13 (T)33按应用语言、细化运行时权限14 (U)34强制前台服务类型、SCHEDULE_EXACT_ALARM权限受限15 (V)35dataSync 前台服务 6 小时超时1636用户发起任务 (UIJ) 通知集中管控,UIJ 通知禁止随意关闭1737新增getPendingJobReasons*()诊断接口、废弃 Job 检测、按网络连接批量调度、Perfetto Job 追踪、start‑user‑before‑alarm30.2 JobSchedulerJobScheduler 是 Android 调度可延迟后台任务的核心机制,Android5.0(API21)引入。应用声明需要执行的工作与执行条件,由系统决定实际运行时机。系统可以批量合并任务、推迟到合适时机(充电、Wi‑Fi 环境)执行,同时强制执行待机分组配额。30.2.1 架构总览全部核心组件位于 JobScheduler APEX 模块。源码路径:frameworks/base/apex/jobscheduler/service/java/com/android/server/job/30.2.2 JobSchedulerServiceJobSchedulerService是总调度协调器,继承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 ListStateController 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_FAILURE30.2.4 约束类型约束系统是 JobScheduler 的核心,每种约束由独立StateController管理。约束控制器源码文件网络类型 / 网络连通性ConnectivityControllercontrollers/ConnectivityController.java时间(延迟、截止时间、周期)TimeControllercontrollers/TimeController.java设备空闲IdleControllercontrollers/IdleController.java充电 / 电量充足BatteryControllercontrollers/BatteryController.java存储空间充足StorageControllercontrollers/StorageController.javaContent URI 变更ContentObserverControllercontrollers/ContentObserverController.java配额管控QuotaControllercontrollers/QuotaController.java弹性约束降级FlexibilityControllercontrollers/FlexibilityController.java后台限制BackgroundJobsControllercontrollers/BackgroundJobsController.javaDoze 模式DeviceIdleJobsControllercontrollers/DeviceIdleJobsController.java应用组件启用状态ComponentControllercontrollers/ComponentController.java预取时机控制PrefetchControllercontrollers/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; //