一、公寓楼的任务调度中心
想象安卓系统是一座名为 "安卓大厦" 的智能公寓,里面住着各种应用 "居民"。这些居民经常需要完成一些特殊任务,比如:
-
当 Wi-Fi 连接时同步文件
-
充电时清理缓存
-
空闲时备份数据
但这些任务不能随便执行,需要满足特定条件才行。这时,大厦里的 "任务调度局"——JobScheduler 就登场了,它就像专业的快递中心,确保任务在最合适的时间派送。
1.1 调度局的开张
每天大厦总管(SystemServer)醒来后,会专门叫醒任务调度局的管理员:
java
// 大厦总管启动任务调度局
private void startOtherServices() {
mSystemServiceManager.startService(JobSchedulerService.class);
}
调度局管理员上岗后,会立即组建一支 "条件检查队",包括:
-
网络检查员(ConnectivityController):负责检查网络是否可用
-
时间监督员(TimeController):监控任务时间是否到期
-
电量检测员(BatteryController):确认设备是否在充电
-
空闲侦察员(IdleController):判断设备是否空闲
-
应用闲置观察员(AppIdleController):观察应用是否长时间未使用
java
// 组建条件检查队
public JobSchedulerService(Context context) {
mControllers.add(ConnectivityController.get(this));
mControllers.add(TimeController.get(this));
mControllers.add(IdleController.get(this));
mControllers.add(BatteryController.get(this));
mControllers.add(AppIdleController.get(this));
}
1.2 任务调度局的 "任务账本"
管理员还会准备一本 "任务账本"(JobStore),记录所有待执行的任务。这本账本存在大厦的 "文件柜" 里(/data/system/job/jobs.xml),即使大厦停电(重启)也不会丢失记录。
java
// 创建任务账本
private JobStore(Context context, File dataDir) {
mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
mJobSet = new ArraySet<JobStatus>();
readJobMapFromDisk(mJobSet); // 从文件读取历史任务
}
二、居民的任务委托:从创建到提交
2.1 填写任务委托单
公寓居民(应用)想提交一个任务时,需要填写一份详细的 "任务委托单"(JobInfo)。比如,一个云同步应用想在 Wi-Fi 连接且充电时同步数据:
java
// 创建任务委托单
ComponentName jobService = new ComponentName(this, MyJobService.class);
JobInfo jobInfo = new JobInfo.Builder(123, jobService)
.setMinimumLatency(5000) // 至少延迟5秒
.setOverrideDeadline(60000) // 最多等待60秒
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // 需要Wi-Fi
.setRequiresCharging(true) // 需要充电
.setPersisted(true) // 设备重启后继续执行
.build();
这张委托单就像快递单,详细记录了任务 ID、执行组件、所需条件和时间要求。
2.2 提交委托单给调度局
居民把委托单交给大厦的 "前台接待员"(JobScheduler),接待员通过大厦内部对讲机(Binder 机制)传给任务调度局:
java
// 前台接待员提交任务
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
scheduler.schedule(jobInfo);
2.3 调度局的任务登记
管理员收到委托单后,先创建一个 "任务记录"(JobStatus),然后检查是否有相同任务:
java
// 管理员登记任务
public int schedule(JobInfo job, int uId) {
JobStatus jobStatus = new JobStatus(job, uId);
cancelJob(uId, job.getId()); // 取消旧任务(如果有)
startTrackingJob(jobStatus); // 开始追踪新任务
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); // 通知检查任务条件
return RESULT_SUCCESS;
}
三、条件检查:快递派送前的准备
3.1 各部门的条件检查
任务登记后,调度局的 "条件检查队" 开始工作,每个检查员都会检查自己负责的条件:
-
网络检查员:查看是否连接了 Wi-Fi(非流量计费网络)
-
电量检测员:确认设备是否在充电
-
时间监督员:计算任务是否到了该执行的时间
-
空闲侦察员:判断设备是否处于空闲状态(屏幕关闭、无操作)
java
// 条件检查示例(网络检查员)
public class ConnectivityController extends StateController {
private void onConnectivityChanged() {
for (JobStatus job : mTrackedJobs) {
if (job.hasConnectivityConstraint() && isNetworkReady()) {
notifyStateChanged(job); // 通知任务条件满足
}
}
}
}
3.2 任务就绪队列
当所有条件都满足时,任务会被放入 "就绪队列"(mPendingJobs),等待派送:
java
// 加入就绪队列
private void maybeQueueReadyJobsForExecutionLockedH() {
List<JobStatus> runnableJobs = new ArrayList<>();
for (JobStatus job : mJobs.getJobs()) {
if (isReadyToBeExecutedLocked(job)) {
runnableJobs.add(job); // 条件满足,加入就绪队列
}
}
mPendingJobs.addAll(runnableJobs);
}
四、任务执行:快递员的派送过程
4.1 派遣快递员(JobService)
调度局有一批 "快递员"(JobService),负责执行具体任务。当任务就绪后,管理员会派遣空闲的快递员:
java
// 派遣快递员
private void maybeRunPendingJobsH() {
for (JobStatus job : mPendingJobs) {
JobServiceContext availableContext = findAvailableContext();
if (availableContext != null) {
availableContext.executeRunnableJob(job); // 快递员执行任务
}
}
}
4.2 快递员上门服务
快递员接到任务后,会前往居民家中(应用进程)执行任务:
java
// 快递员上门(绑定服务)
boolean executeRunnableJob(JobStatus job) {
final Intent intent = new Intent().setComponent(job.getServiceComponent());
boolean binding = mContext.bindServiceAsUser(
intent, this, Context.BIND_AUTO_CREATE, new UserHandle(job.getUserId()));
if (binding) {
// 服务绑定成功,准备执行任务
}
}
4.3 执行任务回调
当快递员到达居民家后,会调用居民指定的 "任务处理方法"(onStartJob):
java
// 居民家中的任务处理
public class MyJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
// 在这里执行具体任务(如同步数据)
new Thread(() -> {
syncDataToCloud();
jobFinished(params, false); // 任务完成
}).start();
return true; // 任务正在执行
}
}
五、任务取消:快递的中途召回
5.1 居民取消任务
如果居民想取消任务,可以通知前台接待员:
java
// 取消任务
scheduler.cancel(123); // 取消ID为123的任务
scheduler.cancelAll(); // 取消当前应用的所有任务
5.2 调度局的召回流程
管理员收到取消请求后,会通知所有相关检查员停止追踪该任务:
java
// 管理员召回任务
public void cancelJob(int uid, int jobId) {
JobStatus toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
if (toCancel != null) {
cancelJobImpl(toCancel); // 执行取消流程
}
}
private void cancelJobImpl(JobStatus cancelled) {
stopTrackingJob(cancelled); // 停止条件检查
stopJobOnServiceContextLocked(cancelled); // 停止执行中的任务
}
5.3 快递员的任务终止
如果快递员正在派送该任务,会收到终止通知:
java
// 快递员收到终止通知
void cancelExecutingJob() {
mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget();
}
private void handleCancelH() {
if (mVerb == VERB_EXECUTING) {
sendStopMessageH(); // 发送停止任务消息
}
}
六、特殊场景:停电后的任务恢复
当大厦停电(设备重启)后,任务调度局会从 "任务账本" 中恢复所有未完成的任务:
java
// 重启后恢复任务
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
mReadyToRock = true;
ArraySet<JobStatus> jobs = mJobs.getJobs();
for (JobStatus job : jobs) {
for (StateController controller : mControllers) {
controller.maybeStartTrackingJob(job); // 重新开始追踪任务
}
}
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); // 检查任务条件
}
}
七、调度局的智慧:节能与效率优化
7.1 批量任务处理
调度局不会立即执行单个任务,而是会等待多个任务就绪后批量处理,就像快递员攒够一批快递再统一派送,减少频繁奔波:
java
// 批量处理条件
if (idleCount >= MIN_IDLE_COUNT ||
connectivityCount >= MIN_CONNECTIVITY_COUNT ||
runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
mPendingJobs.addAll(runnableJobs);
}
7.2 退避重试策略
如果任务执行失败,调度局会采用 "退避策略",就像快递员遇到收件人不在家时,会间隔越来越长的时间再尝试:
java
// 设置退避策略
.setBackoffCriteria(3000, JobInfo.BACKOFF_POLICY_LINEAR)
// 第一次失败后等3秒,第二次等6秒,依此类推
7.3 紧急任务优先
对于有截止时间的任务(deadline),调度局会优先处理,就像快递单上标注 "加急" 的包裹:
java
// 检查截止时间
private boolean isReadyToBeExecutedLocked(JobStatus job) {
if (job.hasDeadlineConstraint() &&
job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime()) {
return true; // 截止时间已到,优先执行
}
// 其他条件检查...
}
八、总结:JobScheduler 的工作流程图解
- 任务创建:应用创建 JobInfo 委托单,指定任务条件和执行组件
- 提交调度:通过 JobScheduler 提交任务,进入调度局登记
- 条件检查:各检查员(StateController)监控网络、电量等条件
- 任务就绪:条件满足后加入就绪队列,等待执行
- 任务执行:派遣 JobService 执行任务,回调 onStartJob 方法
- 任务取消:应用取消任务时,调度局终止执行并清理记录
- 重启恢复:设备重启后从文件恢复任务,继续追踪
九、实战小贴士:避免踩坑
-
cancelAll 的陷阱:
cancelAll()会取消同一 UID 下的所有任务- 如果多个应用共享 UID,一个应用调用 cancelAll 会取消其他应用的任务
-
耗时任务处理:
- onStartJob 运行在主线程,耗时操作必须放在子线程
- 使用 jobFinished () 通知调度局任务完成,避免资源浪费
-
条件组合使用:
-
合理组合网络、电量、时间条件,减少设备唤醒次数
-
例如:同时设置
setRequiresCharging(true)和setRequiredNetworkType(),确保只在充电且连 Wi-Fi 时执行
-
通过这个故事,我们了解到 JobScheduler 就像一个智能的快递调度中心,不仅能按条件精确派送任务,还能优化效率、节省资源,是安卓系统中处理后台任务的重要机制。