用一个“快递公司”的故事来揭开JobScheduler的神秘面纱

41 阅读4分钟

好的,咱们用一个“快递公司”的故事来揭开JobScheduler的神秘面纱。我是这家公司(Android系统)的首席调度师(JobSchedulerService),而你的App就是个想寄快递的客户。


📦 ​​第一章:客户下单(App提交任务)​

客户(你的App)想寄个快递(后台任务),但要求很特别:

“必须在手机充电时 + 连着WiFi + 半夜没人用手机时才能发货!”

于是客户写了个​​快递订单​​(JobInfo):

java
Copy
// 客户用Builder模式创建订单(JobInfo)
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, new ComponentName(this, MyJobService.class))
        .setRequiresCharging(true)          // 充电时才发
        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // 用WiFi
        .setRequiresDeviceIdle(true)        // 手机闲着才发
        .setPeriodic(24 * 60 * 60 * 1000)   // 每24小时发一次
        .build();

接着把订单交给前台柜台(JobScheduler):

java
Copy
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
scheduler.schedule(jobInfo); // 前台把订单转给后台调度中心

💡 此时柜台只是个“传话筒”,真正处理订单的是藏在后院的​​调度中心(JobSchedulerService)​​。


🏢 ​​第二章:调度中心干脏活(系统服务层)​

调度中心一收到订单,就干三件事:

  1. ​持久化订单​​:立刻存进仓库(JobStore),即使手机重启订单也不丢 。

  2. ​检查条件​​:派一群“侦察兵”(Controller)盯着手机状态:

    • BatteryController:盯着是否充电

    • ConnectivityController:盯着WiFi

    • IdleController:盯着手机是否在睡觉

  3. ​条件满足时喊快递员​​:所有侦察兵同时点头时,调度中心大吼:“条件满足!快递员开工!”

🔍 ​​源码亮点​​:

  • JobSchedulerService.schedule() 会创建 JobStatus 对象(订单的加强版,含状态、重试次数等)
  • startTrackingJobLocked() 把订单注册给所有侦察兵(Controller)
  • 侦察兵们通过 maybeStartTrackingJobLocked() 和 maybeStopTrackingJobLocked() 动态启停监听

🚚 ​​第三章:快递员送货(任务执行层)​

快递员(JobServiceContext)是真正的打工人,他接到指令后的动作:

  1. ​开车到客户家​​:绑定到客户的MyJobService(就像快递员找到客户的仓库地址):

    java
    Copy
    // 系统源码中的绑定调用
    mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE, user);
    
  2. ​敲门喊干活​​:调用客户服务的 onStartJob()

    java
    Copy
    // 客户在JobService中实现的逻辑
    public class MyJobService extends JobService {
        @Override
        public boolean onStartJob(JobParameters params) {
            // 客户在这里搬货(比如下载文件)
            new Thread(() -> {
                // 模拟搬货2秒
                jobFinished(params, false); // 喊一声:货搬完了!
            }).start();
            return true; // true表示“活还没干完,干完会喊你”
        }
    }
    
  3. ​超时或被中断​​:如果客户太久不开门(超时),或调度中心喊停(比如WiFi断了),快递员会触发 onStopJob(),客户需要清理现场

⚠️ ​​关键细节​​:

  • 快递员有 ​​超时机制​​(默认1分钟),防止客户磨蹭
  • 客户在onStartJob()里​​必须开线程干活​​,否则阻塞主线程会被系统“投诉”

  • 客户干完活得主动jobFinished(),否则快递员会一直等(耗电)


♻️ ​​第四章:异常处理与省电秘籍​

调度中心最擅长“见风使舵”:

  • ​条件突然不满足​​:比如正送货时WiFi断了,侦察兵立刻通知调度中心,调度中心喊停快递员(触发onStopJob()

  • ​失败重试策略​​:客户在onStopJob()中返回true,订单会被重新挂起等待条件满足 :

    java
    Copy
    @Override
    public boolean onStopJob(JobParameters params) {
        return true; // 快递员:“调度中心,这单回头再试!”
    }
    
  • ​省电的精髓​​:把100个客户的“充电+WiFi发货”需求​​合并到同一时段​​,让手机只唤醒一次,避免反复“起床干活”耗电


🌟 ​​总结:JobScheduler的智慧​

  1. ​解耦​​:客户(App)只提需求,调度中心(系统)负责时机。

  2. ​条件组合​​:多个侦察兵(Controller)协同决策。

  3. ​异步执行​​:快递员(JobServiceContext)和客户线程分离。

  4. ​持久化​​:仓库(JobStore)确保任务永不丢。

  5. ​省电​​:任务合并 + 条件触发 = 少醒少动

对比现实:就像你让美团跑腿买奶茶,​​不用自己盯着店铺是否开门、骑手是否在线​​,平台全自动调度——这就是JobScheduler的哲学。


​附:调度中心核心源码一览​​(给好奇的极客):

java
Copy
// 1. 订单持久化(JobSchedulerService.java)
void schedule(JobInfo job) {
    JobStatus jobStatus = JobStatus.createFromJobInfo(job);
    mJobStore.add(jobStatus); // 存进仓库
    startTrackingJob(jobStatus); // 交给侦察兵们
}

// 2. 侦察兵通知条件满足(JobSchedulerService.java)
public void onControllerStateChanged() {
    for (JobStatus job : mJobStore.getJobs()) {
        if (isReadyToBeExecuted(job)) { // 所有条件满足?
            queueJob(job); // 扔进执行队列
        }
    }
}

// 3. 快递员执行(JobServiceContext.java)
void executeRunnableJob(JobStatus job) {
    bindService(job.getService()); // 绑定客户服务
    onServiceConnected(..., service) {
        service.onStartJob(job.getParams()); // 调用客户干活
    }
}

💡 ​​小提醒​​:

  • 在Android 8.0+,谷歌推荐用WorkManager(底层可能用JobSchedulerAlarmManager),但​​核心思想不变​

  • 想深入源码?去frameworks/base/services/core/java/com/android/server/job/ 探险吧!