从源码角度看JobScheduler

简介

做过 Android 应用开发的相信对 JobScheduler 都比较熟悉了,顾名思义,任务调度者的作用在于只需要获取到 JobScheduler 的服务、在 JobService 的子类下实现 onStartJob 与 onStopJob 方法,随后调用 JobScheduler.schedule 方法便可以将任务的调度任务交给系统。借助于此,开发者只需要关注任务在开发执行与结束执行时的业务逻辑,而不需要处理根据系统状态来调度任务启动停止时机的重担

Android O 之后,系统对 AMS 中的后台服务与隐性广播分别做出了严格的限制。如果一个进程正在处于后台的状态,那么他将不能通过调用 startService 来拉起一个后台的服务。如果一个应用发出了一个隐性的广播,那么使用静态 manifest 注册广播的接收者将不再能收到隐性 Intent 发来的通知。对于这两种限制,谷歌推荐的解决方案之一就是使用 JobScheduler,通过将业务逻辑从后台服务 onStartCommand 挪到 JobService的 onStartJob 方法可以越过系统对于后台服务的启动限制;通过设置 Job 的触发条件,比如说电池充满,网络已连接,可以实现等同于监听电池广播与 WIFI 广播变化的功能

JobScheudler 是如何能越过 AMS 限制的呢,这篇文章我将由客户端与服务端类图入手来讲解其中重要的类及其作用,通过这两张图大家可以对 JobScheduler 有个初步的认识。随后,我会结合着 JobScheduler.schedule 方法调用的时序图来分析 JobScheduler 从客户端到服务端的具体源码。文章最后,我会总结 JobScheduler 大体实现思路以及为什么谷歌推荐使用它来替代已经被限制的后台服务与隐性广播

本篇文章分析的源码是基于 Android 8.0的,但是JobScheduler 从5.0之后其实大致的流程都差不多,到了8.0也只是添加了一些细节

相关类图

客户端

查看大图

  • JobService: 使用 Android JobScheduler 必须在 xml 中要声明的一个 Service,它的启动需要使用到 android.permission.BIND_JOB_SERVICE 这个权限。JobService 的启动是由 JobSchedulerService 在 system_server 进程中以 bindService 的形式拉起来的。在 JobService 调用 onBind 之后会返回 IJobService.Stub 对象到 system_server,随后 JobSchedulerService 拿到 IJobService.Proxy 的对象后,随时可以调用 startJob 方法来触发客户端 Service 中的 onStartJob 这个方法
  • JobInterface: IJobService.Stub 的实现类,在这里是 system_server 的服务端,调用 startJob 之后会调用到这个类的方法,随后经过层层调用,最终调用了用户实现的 onStartJob 方法
  • JobServiceEngine: 顾名思义,这个类其实就相当于一个客户端的 JobService 的引擎,主要用于处理system_server服务端请求与客户端业务逻辑的调用
  • JobInfo: 一个 Job 信息的抽象,包含了任务实例中所有的信息,客户端常常需要对它先做好声明,随后才能发起派发请求到服务端
  • JobParameters: 一个 Job 参数的抽象,它的属性中包含了 IJobCallback.Proxy 对象,用于回复 system_server JobSchedulerService 执行 onStartJob 已经结束的信息
  • JobSchedulerImpl: JobSchedler 抽象类的真正实现者,其主要的操作是通过 binder call 向 system_server 发起请求

服务端

查看大图

  • JobSchedlerService: 用来在 system_server 进程中管理 JobScheduler 服务的主体,相当于一个大总管管理与协调各个重要的模块
  • JobSchedulerStub: JobScheduler 的服务端,用户处理客户端传来的 binder call 请求
  • JobStore: 服务端存储正在进行中的 Job 信息,提供了插入、删除、遍历的功能
  • JobSet: Job 的集合抽象,提供了 CRUD 的方法供 JobStore 进行使用
  • JobStatus: 一个 Job 在服务端运行状态的抽象,直接引用着从客户端传来的 JobInfo 对象
  • JobStatusFuntor: 用户负责处理任务的接口抽象
  • JobServiceContext: 真正会执行 bindService 操作的类,实现了 ServiceConnection 接口,从这个角度来看,system_server 相当于是一个客户端,而 APP 自己实现的 MyJobService onBind 返回的 JobInterface 对象才是一个服务端,当 JobServiceContext 的 onServiceConnected 方法触发后,只有调用 mService.startJob 后,APP 的 onStartJob 方法才会被触发
  • JobCallback: 用于实现 JobSchedulerService 与 App 双工通信的一个服务类,当 APP 执行完成 onStartJob 方法后,会调用 JobCallback 代理类的方法,通知 system_server APP 的操作已经执行完毕

调用 JobScheduler.schedule 后会发生什么

整体时序图

查看大图

APP->SystemServer

1
2
3
4
5
6
7
8
9
10
11
public void scheduleJob(View v) {
// 使用建造者模式来初始化一个 JobInfo 实例
JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);
...
// 得到 JobScheduler 服务
JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
// 调用 JobScheduler.schedule 来开始调度被初始化的任务
tm.schedule(builder.build());
}

JobInfo 是使用建造者模式进行初始化的,jobId 与需要被 JobScheduler 唤起的 Service 的 ComponentName 是必须填入的参数

http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/java/android/app/JobSchedulerImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
37 /* package */ JobSchedulerImpl(IJobScheduler binder) {
38 mBinder = binder;
39 }
...
41 @Override
42 public int schedule(JobInfo job) {
43 try {
44 return mBinder.schedule(job);
45 } catch (RemoteException e) {
46 return JobScheduler.RESULT_FAILURE;
47 }
48 }

getSystemService 得到的是在 system_server 进程启动时初始化的 JobSchedulerImpl 对象,它的 mBinder 成员对象是一个 IJobScheduler.Proxy 对象,客户端借助着它可以 binder call 到 JobSchedulerService 中,调用 schedule 方法

SystemServer 处理任务

1
2
3
4
5
6
7
8
9
10
11
12
13
1788 final class JobSchedulerStub extends IJobScheduler.Stub {
...
1843 @Override
1844 public int schedule(JobInfo job) throws RemoteException {
...
1865 try {
1866 return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, -1, null);
1867 } finally {
1868 Binder.restoreCallingIdentity(ident);
1869 }
1870 }
...
}

JobScheduler 的 enqueue, scheudle 和 scheduleAsPackage方法最终都会调用到 JobSchedulerService 的 scheduleAsPackage 方法

http://androidxref.com/8.0.0_r4/xref/frameworks/base/services/core/java/com/android/server/job/JobSchedulerService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
654 public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
655 int userId, String tag) {
...
709 if (isReadyToBeExecutedLocked(jobStatus)) {
...
712 mJobPackageTracker.notePending(jobStatus);
713 addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
714 maybeRunPendingJobsLocked();
715 }
716 }
717 return JobScheduler.RESULT_SUCCESS;
718 }
1474 private boolean isReadyToBeExecutedLocked(JobStatus job) {
1475 final boolean jobReady = job.isReady();
...
1516 if (jobPending || jobActive) {
1517 return false;
1518 }
1519
1520 final boolean componentPresent;
1521 try {
1522 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
1523 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
1524 userId) != null);
1525 } catch (RemoteException e) {
1526 throw e.rethrowAsRuntimeException();
1527 }
...
1535 return componentPresent;
1536 }

任务的状态与任务是否已经就绪的逻辑都封装在 JobInfo 中,通过它们可以判断出任务的就绪与有效状态。同时,查看客户端 JobService 的声明状态来确保 JobService 是可以被拉起的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1543 private void maybeRunPendingJobsLocked() {
1547 assignJobsToContextsLocked();
1548 reportActiveLocked();
1549 }
...
1581 private void assignJobsToContextsLocked() {
...
1686 mJobPackageTracker.noteConcurrency(numActive, numForeground);
1687 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
1688 boolean preservePreferredUid = false;
1689 if (act[i]) {
...
1698 } else {
...
1707 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
1708 Slog.d(TAG, "Error executing " + pendingJob);
1709 }
...
1714 }
...
1718 }
1719 }

调用 maybeRunPendingJobsLocked 之后会调用到 executeRunnableJob 方法,随后会触发拉起客户端 JobService 的操作

SystemServer->APP

http://androidxref.com/8.0.0_r4/xref/frameworks/base/services/core/java/com/android/server/job/JobServiceContext.java#192

1
2
3
4
5
6
7
8
9
10
11
12
13
14
192 boolean executeRunnableJob(JobStatus job) {
193 synchronized (mLock) {
...
217 mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
218 ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
219 isDeadlineExpired, triggeredUris, triggeredAuthorities);
...
224 final Intent intent = new Intent().setComponent(job.getServiceComponent());
225 boolean binding = mContext.bindServiceAsUser(intent, this,
226 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
227 new UserHandle(job.getUserId()));
...
250 }
251 }

通过调用 bindServiceAsUser 方法,system_server 拉起了一个非前台的服务来执行客户端的任务

值得注意的是,我之前的分析略去了这块逻辑的触发条件,实际上 JobScheduler 触发这个操作是需要严格的限制的,相当于系统管理了客户端消耗系统资源的时机,只有在用户电源充足、连接到 WIFI 之后等等这些条件满足后客户端才能开始使用服务去处理自己的业务

http://androidxref.com/8.0.0_r4/xref/frameworks/base/services/core/java/com/android/server/job/JobServiceContext.java#192

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
354 public void onServiceConnected(ComponentName name, IBinder service) {
355 JobStatus runningJob;
356 synchronized (mLock) {
...
367 this.service = IJobService.Stub.asInterface(service);
...
387 doServiceBoundLocked();
388 }
389 }
...
519 private void handleServiceBoundLocked() {
...
537 try {
540 service.startJob(mParams);
541 } catch (Exception e) {
...
547 }
548 }

bindService 之后的流程这块我就不细讲了,具体可以参考我之前写的《从源码角度看 Service》这篇文章

在服务被绑定成功之后,onServiceConencted 会被调用,随后 JobServiceContext 会拿掉 IJobSerivce.Proxy 对象,随后通过 service binder 句柄触发客户端的 startJob 方法

APP 处理任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
66 static final class JobInterface extends IJobService.Stub {
67 final WeakReference<JobServiceEngine> mService;
68
69 JobInterface(JobServiceEngine service) {
70 mService = new WeakReference<>(service);
71 }
72
73 @Override
74 public void startJob(JobParameters jobParams) throws RemoteException {
75 JobServiceEngine service = mService.get();
76 if (service != null) {
77 Message m = Message.obtain(service.mHandler, MSG_EXECUTE_JOB, jobParams);
78 m.sendToTarget();
79 }
80 }

IJobService.Stub 类是 JobInterface 实现的,system_server 的 binder call 调用最终会调用 JobInterface.startJob 方法。和 Android Framework 的大多组件类似,收到 binder call 调用之后,JobInterface 马上发出了一个 MSG_EXECUTE_JOB 的消息,异步调用处理 startJob 的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
97 class JobHandler extends Handler {
98 JobHandler(Looper looper) {
99 super(looper);
100 }
101
102 @Override
103 public void handleMessage(Message msg) {
104 final JobParameters params = (JobParameters) msg.obj;
105 switch (msg.what) {
106 case MSG_EXECUTE_JOB:
107 try {
108 boolean workOngoing = JobServiceEngine.this.onStartJob(params);
109 ackStartMessage(params, workOngoing);
110 } catch (Exception e) {
111 Log.e(TAG, "Error while executing job: " + params.getJobId());
112 throw new RuntimeException(e);
113 }
114 break;

对于 JobHandler 的 MSG_EXECUTE_JOB 的消息,首先会调用 JobServiceEngine.onStartJob 的方法,随后调用了 ackStartMessage 方法,告诉 system_server 客户端的操作已经完成

http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/java/android/app/job/JobService.java#mEngine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
66 public final IBinder onBind(Intent intent) {
67 if (mEngine == null) {
68 mEngine = new JobServiceEngine(this) {
69 @Override
70 public boolean onStartJob(JobParameters params) {
71 return JobService.this.onStartJob(params);
72 }
73
74 @Override
75 public boolean onStopJob(JobParameters params) {
76 return JobService.this.onStopJob(params);
77 }
78 };
79 }
80 return mEngine.getBinder();
81 }

可以看到,JobServiceEngine 的 onStartJob 方法的实现其实是直接调用了 JobService 的 onStartJob 方法

APP->SystemServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
144 private void ackStartMessage(JobParameters params, boolean workOngoing) {
145 final IJobCallback callback = params.getCallback();
146 final int jobId = params.getJobId();
147 if (callback != null) {
148 try {
149 callback.acknowledgeStartMessage(jobId, workOngoing);
150 } catch(RemoteException e) {
151 Log.e(TAG, "System unreachable for starting job.");
152 }
153 } else {
154 if (Log.isLoggable(TAG, Log.DEBUG)) {
155 Log.d(TAG, "Attempting to ack a job that has already been processed.");
156 }
157 }
158 }

ackStartMessage 的作用是在 JobService 执行完毕 onStartJob 操作之后告诉 system_server 该任务已经执行完毕。它的实现是通过 JobCallback 的服务来实现的。这种 binder 实现双工通信的手法其实很常见,在 JobScheduler 中,IJobCallback 的句柄是通过 JobParameters 传输过来的

http://androidxref.com/8.0.0_r4/xref/frameworks/base/services/core/java/com/android/server/job/JobServiceContext.java#137

1
2
3
4
5
6
7
8
9
10
11
12
137 final class JobCallback extends IJobCallback.Stub {
138 public String mStoppedReason;
139 public long mStoppedTime;
140
141 @Override
142 public void acknowledgeStartMessage(int jobId, boolean ongoing) {
143 doAcknowledgeStartMessage(this, jobId, ongoing);
144 }
...
307 void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) {
308 doCallback(cb, ongoing, "finished start");
309 }

最后通过调用 doCallback 来处理服务端的任务逻辑

总结

JobScheduler 是一个任务调度框架,谷歌在 Android 8.0 上已经开始推荐开发者使用它来代替后台 Service 与一些静态注册的广播。

谷歌之所以要限制住开发者调用后台服务的时机,是想保护住恶意开发者为了保护自己的应用而不惜消耗用户手机资源的行为。通过使用 JobScheduler,Android Framework 可以变被动与主动,限制住第三方 APP 对于系统资源的滥用,只在合适的时机调用客户端的服务,从而达到替换后台服务又保护的系统资源的目的

值得一提的是,谷歌在 Android 8.0 虽然限制住了 startService,但是却没有限制 bindService 接口,JobSchedulerService 与客户端 JobService 通信的根本原理就是使用了 bindService 接口

扫码支持0.99元,您的支持将鼓励我继续创作!