一、JobScheduler简介
JobScheduler是api-21版本上新增加的一个api类,用于根据应用程序自身进程中安排的任务情况来安排各种类型的job。
使用JobScheduler,需要构建JobInfo,然后自定义一个JobService,在使用构造JobInfo时,可以标识实现作业逻辑的服务组件JobInfo.Builder(int, android.content.ComponetName).
这个作业调度的执行框架主要目的就是将执行的作业智能化,尽可能批量地处理这些定义好的job,如果没有定义截止日期,可以随时运行作业,具体取决于JobScheduler内部队列的当前状态。
当作业运行的时候,系统表示你的应用程序保留唤醒锁。因此你无需任何措施来保证设备在工作期间保持清醒状态,之前采用的广播什么的可以忽略了。
你不需要实例化这个JobScheduler,直接使用Context.getSystemService(Context.JOB_SCHEDULER_SERVICE),这是系统启动的时候加到Context中静态块中的系统服务,但是要记住,这个和注册到ServiceManager中的服务有本质的区别。
二、作业调度核心类与接口
2.1 作业调度Client端
frameworks/base/core/java/android/app/job/
IJobCallback.aidl
IJobScheduler.aidl
IJobService.aidl
JobInfo.java
JobInfo.aidl
JobParameters.java
JobParameters.aidl
JobScheduler.java
JobService.java
JobServiceEngine.java
JobWorkItem.java
JobWorkItem.aidl
我们关注的几个核心类是JobInfo.java、JobScheduler.java、JobService.java,它们之间简单的关系如下:
所谓client端,就是开发者在使用作业调度的时候,直接调用的就是clien端的api,所以这些api接口开发者会很熟悉,开发者基本的调度步骤是:
- 通过Context本地获取JOB_SCHEDULER_SERVICE代表的JobScheduler对象。
一般的调度步骤是:
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
具体的解释看下面。- 创建JobInfo对象,JobInfo对象内部是使用Builder设计模式来获取的,Builder内部类中有很多JobInfo相关的属性,这是控制Job属性的重要标识,后续的作业调度也是根据设置的job属性来决定的。下面透露一些简单的属性:
private final int mJobId;
private final ComponentName mJobService; //这个JobService就是下面要设置的JobService对象
private int mConstraintFlags; //这个限制条件里面的信息很多,例如是否正在充电、是否在wifi条件等等。- 自定义一个类,继承JobService,继承其中的函数,尤其是:
onStartJob(JobParameters);
onStopJob(JobParameters);- 调用第一步中获取的jobScheduler对象,执行schedule(JobInfo)函数,当前定义的Job已经在运行中,会根据设置的Job条件来执行。
这儿谈到的JobSchedulerImpl对象,是在系统启动的时候加入到SystemServiceRegister中的。
SystemServiceRegistry.java
registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class,
new StaticServiceFetcher() {
@Override
public JobScheduler createService() throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.JOB_SCHEDULER_SERVICE);
return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
}});
这儿可以看出来,全局注册的Service中使用的是JobSchedulerImpl对象,所以才不需要开发者手动new一个JobScheduler对象出来了。
2.2 作业调度server端
这儿直接截图吧,代码的位置在frameworks/base/services/core/java/com/android/server/job
这儿的代码是完全对开发者隐藏的,但是这儿才是实现作业调度的核心代码。注意到一个关键的类:JobSchedulerService.java,这是SystemService的子类。
2.2.1 JobSchedulerService启动
SystemServer.java
private void startOtherServices() {
//......
traceBeginAndSlog("StartJobScheduler");
mSystemServiceManager.startService(JobSchedulerService.class);
traceEnd();
//......
}
这儿执行了JobSchedulerService的构造函数,并且将获取的对象存放在SystemServiceManager.java中的链表中。
构造函数中做了什么?
JobSchedulerService.java
public JobSchedulerService(Context context) {
super(context);
mHandler = new JobHandler(context.getMainLooper());
mConstants = new Constants(mHandler);
mJobSchedulerStub = new JobSchedulerStub();
mJobs = JobStore.initAndGet(this);
// Create the controllers.
mControllers = new ArrayList();
mControllers.add(ConnectivityController.get(this));
mControllers.add(TimeController.get(this));
mControllers.add(IdleController.get(this));
mBatteryController = BatteryController.get(this);
mControllers.add(mBatteryController);
mStorageController = StorageController.get(this);
mControllers.add(mStorageController);
mControllers.add(AppIdleController.get(this));
mControllers.add(ContentObserverController.get(this));
mControllers.add(DeviceIdleJobsController.get(this));
// If the job store determined that it can't yet reschedule persisted jobs,
// we need to start watching the clock.
if (!mJobs.jobTimesInflatedValid()) {
Slog.w(TAG, "!!! RTC not yet good; tracking time updates for job scheduling");
context.registerReceiver(mTimeSetReceiver, new IntentFilter(Intent.ACTION_TIME_CHANGED));
}
}
2.2.1.1 创建一个JobHandler
JobHandler中使用的Looper是MainLooper,说明执行过程运行在主线程中。
2.2.1.2 创建JobSchedulerStub对象
这个对象是Binder类型,是JobScheduler的binder的server端,JobScheduler中执行的函数都在这里面接收。
final class JobSchedulerStub extends IJobScheduler.Stub {
}
2.2.1.3 获取JobStore对象
这个JobStore是维护作业调度程序正在跟踪的作业的主列表,除此之外,还处理持久作业的读写。
mJobs = JobStore.initAndGet(this);
JobStore.java
static JobStore initAndGet(JobSchedulerService jobManagerService) {
synchronized (sSingletonLock) {
if (sSingleton == null) {
sSingleton = new JobStore(jobManagerService.getContext(),
jobManagerService.getLock(), Environment.getDataDirectory());
}
return sSingleton;
}
}
private JobStore(Context context, Object lock, File dataDir) {
mLock = lock;
mContext = context;
mDirtyOperations = 0;
File systemDir = new File(dataDir, "system");
File jobDir = new File(systemDir, "job");
jobDir.mkdirs();
mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
mJobSet = new JobSet();
mXmlTimestamp = mJobsFile.getLastModifiedTime();
mRtcGood = (System.currentTimeMillis() > mXmlTimestamp);
readJobMapFromDisk(mJobSet, mRtcGood);
}
主要的工作是读取/data/system/job/jobs.xml中的内容,并将内容更新到本地来。这些Job相关的信息都会在后期管理job的时候用到。
下面是jobs.xml中的部分内容,这些和job相关的属性,会被更新到本地。
2.2.1.4 创建了一个StateController列表
放入了8个StateController对象。
StateController就是状态控制器,它是一个抽象类,链表中的8个对象都是继承它的。
StateController就是在作业管理的各种控制器之间包含共享控制器逻辑,它们全权负责跟踪作业列表,并在这些作业准备好时通知作业管理器运行,或者停止运行等等。
Controller名称 | 作用 |
---|---|
ConnectivityController | 网络状态变化控制器 |
TimeController | 为下一个即将到期的作业设置警报 |
IdleController | 注册屏幕亮与熄灭的监听,以及Dream模式开始与停止的监听,还有Debug模式的监听 |
BatteryController | 注册电池健康状态的监听和电池是否处于充电状态的监听 |
StorageController | 注册存储空间的监听 |
AppIdleController | 监听app是否空闲,尚未从前台应用程序主动启动或者访问的应用程序,在一段时间后被视为闲置,当应用程序进入闲置状态时,它可能被运行一些预定的作业 |
ContentObserverController | 用于通过ContentObserver监视对内容URI的更改的控制器 |
DeviceIdleJobsController | 当设备处于瞌睡的状态时,为除白名单之外的所有作业设置约束;当设备没有瞌睡时,将所有作业的约束设置为满足 |
public abstract class StateController {
protected static final boolean DEBUG = JobSchedulerService.DEBUG;
protected final Context mContext;
protected final Object mLock;
protected final StateChangedListener mStateChangedListener;
public StateController(StateChangedListener stateChangedListener, Context context,
Object lock) {
mStateChangedListener = stateChangedListener;
mContext = context;
mLock = lock;
}
public abstract void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob);
public void prepareForExecutionLocked(JobStatus jobStatus) {
}
public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate);
public void rescheduleForFailureLocked(JobStatus newJob, JobStatus failureToReschedule) {
}
public abstract void dumpControllerStateLocked(PrintWriter pw, int filterUid);
}
- maybeStartTrackingJobLocked:
这个函数的实现逻辑以确定是否应该由此控制器跟踪作业。在更新任务的时候也会调用,因此实现控制器必须注意预先存在的任务。- prepareForExecutionLocked:
job即将执行的时候执行的逻辑。- maybeStopTrackingJobLocked:
这儿实现的逻辑是当任务被取消的时候执行的。- rescheduleForFailureLocked:
当一个新的job被创建在一个老的失败的job上重新调度的时候实现的逻辑。
2.2.1.5 重新调度持久化job
如果上面的JobStore确定无法安排持久性的job,我们需要注册一个Time Clock的监听来保证持久性job完成。
if (!mJobs.jobTimesInflatedValid()) {
Slog.w(TAG, "!!! RTC not yet good; tracking time updates for job scheduling");
context.registerReceiver(mTimeSetReceiver, new IntentFilter(Intent.ACTION_TIME_CHANGED));
}
2.2.2 解析JobInfo
上面《2.2.1.3 获取JobStore对象》谈到了从本地的jobs.xml中解析Job信息,解析的具体过程是怎样的,下面讲解一下。
从本地读取Job信息是在放在一个实现Runable接口的类中去做的。在run()中执行的核心方法是:
List jobs;
FileInputStream fis = mJobsFile.openRead();
synchronized (mLock) {
jobs = readJobMapImpl(fis, rtcGood);
if (jobs != null) {
long now = SystemClock.elapsedRealtime();
IActivityManager am = ActivityManager.getService();
for (int i=0; i
其中核心函数readJobMapImpl(...)将本地文件读取起来转化为JobStatus,然后放在一个List中。
解析XML中的分支元素,并将其中节点信息放在JobInfo.Builder中,然后再利用JobInfo构建一个JobStatus对象。这就是执行的核心要点。
2.2.3 JobSchedulerService启动阶段
执行的核心是继承SystemService.java中的
SystemService.java
public void onBootPhase(int phase) {}
在函数执行内部,也细分为两个阶段:
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) {
mConstants.start(getContext().getContentResolver());
// Register br for package removals and user removals.
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
filter.addDataScheme("package");
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null);
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
try {
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
| ActivityManager.UID_OBSERVER_IDLE, ActivityManager.PROCESS_STATE_UNKNOWN,
null);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
// Remove any jobs that are not associated with any of the current users.
cancelJobsForNonExistentUsers();
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mLock) {
// Let's go!
mReadyToRock = true;
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
// Create the "runners".
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
mActiveServices.add(
new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
getContext().getMainLooper()));
}
// Attach jobs to their controllers.
mJobs.forEachJob(new JobStatusFunctor() {
@Override
public void process(JobStatus job) {
for (int controller = 0; controller < mControllers.size(); controller++) {
final StateController sc = mControllers.get(controller);
sc.maybeStartTrackingJobLocked(job, null);
}
}
});
// GO GO GO!
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
}
}
2.2.3.1 系统服务启动阶段
这儿执行的东西总结起来就是
- 注册启动ContentResolver的监听
- 注册多个Receiver,来监听Package事件——ACTION_PACKAGE_REMOVED、ACTION_PACKAGE_CHANGED、ACTION_PACKAGE_RESTARTED、ACTION_QUERY_PACKAGE_RESTART等等
2.2.3.2 三方应用启动阶段
创建了JobServiceContext对象,这个类是处理客户端绑定和作业的生命周期,作业在一个实例上一次执行一个。
public final class JobServiceContext implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
//......
}
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
closeAndCleanupJobLocked(true /* needsReschedule */, "unexpectedly disconnected");
}
}
}
这两个重要的方法分别标识当前JobServiceContext包含着两个重要的交互操作,执行一个job和取消一个job
三、作业操作
3.1 自定义JobService
JobService本质上是一个Service,但是又多了3个重要的方法,这个方法是关系到Job执行状态的。
public abstract class JobService extends Service {
private static final String TAG = "JobService";
public static final String PERMISSION_BIND =
"android.permission.BIND_JOB_SERVICE";
private JobServiceEngine mEngine;
public final IBinder onBind(Intent intent) {
if (mEngine == null) {
mEngine = new JobServiceEngine(this) {
@Override
public boolean onStartJob(JobParameters params) {
return JobService.this.onStartJob(params);
}
@Override
public boolean onStopJob(JobParameters params) {
return JobService.this.onStopJob(params);
}
};
}
return mEngine.getBinder();
}
public abstract boolean onStartJob(JobParameters params);
public abstract boolean onStopJob(JobParameters params);
public final void jobFinished(JobParameters params, boolean needsReschedule) {
mEngine.jobFinished(params, needsReschedule);
}
}
3.1.1 绑定service
既然是service,那么还是要和普通的service一样的,自定义的JobService也是需要绑定的,bindService会回调到onBind(...)
3.1.2 onStartJob
很显然这个方法是启动当前job执行的核心方法,值得注意的是:这个方法是执行在应用程序的主线程的,开发者写核心逻辑的时候,还是应该另起一个线程来执行主要的逻辑。
3.1.3 onStopJob
如果当前决定需要停止job的执行,调用此方法。
3.1.4 jobFinished
调用此方法通知JobManager已知完成执行,可以在任何线程中调用,但是最终会调度到程序的主线程中执行,当系统收到此消息时,它会释放当前的唤醒锁。
onStartJob与onStopJob都是需要开发者继承的方法。
3.2 作业调度
最终需要调度当前的job,执行JobScheduler中的schedule方法,从执行的路径来看:
JobSchedulerImpl.java
public int schedule(JobInfo job) {
try {
return mBinder.schedule(job);
} catch (RemoteException e) {
return JobScheduler.RESULT_FAILURE;
}
}
这时候调用到JobSchedulerService中的JobSchedulerStub内部类中:
public int schedule(JobInfo job) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "Scheduling job: " + job.toString());
}
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
enforceValidJobRequest(uid, job);
if (job.isPersisted()) {
if (!canPersistJobs(pid, uid)) {
throw new IllegalArgumentException("Error: requested job be persisted without"
+ " holding RECEIVE_BOOT_COMPLETED permission.");
}
}
if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
}
long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, -1, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
最终JobInfo会被转换成JobStatus,直接调用到:
private void startTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
if (!jobStatus.isPreparedLocked()) {
Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
}
jobStatus.enqueueTime = SystemClock.elapsedRealtime();
final boolean update = mJobs.add(jobStatus);
if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
StateController controller = mControllers.get(i);
if (update) {
controller.maybeStopTrackingJobLocked(jobStatus, null, true);
}
controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
}
}
}
这儿会将我们之前加入StateController遍历一遍,看看是否满足条件,如果满足条件,直接启动状态控制器追踪一下。
如果当前绑定成功的话,会进入到JobServiceContext中的onServiceConnected(...)方法。
JobServiceContext.java
public void onServiceConnected(ComponentName name, IBinder service) {
//.....
doServiceBoundLocked();
}
void doServiceBoundLocked() {
removeOpTimeOutLocked();
handleServiceBoundLocked();
}
private void handleServiceBoundLocked() {
//......
try {
service.startJob(mParams);
} catch (Exception e) {
}
}
这是最后一部,也是最关键的一部:
JobServiceEngine.java
static final class JobInterface extends IJobService.Stub {
final WeakReference mService;
JobInterface(JobServiceEngine service) {
mService = new WeakReference<>(service);
}
@Override
public void startJob(JobParameters jobParams) throws RemoteException {
JobServiceEngine service = mService.get();
if (service != null) {
Message m = Message.obtain(service.mHandler, MSG_EXECUTE_JOB, jobParams);
m.sendToTarget();
}
}
@Override
public void stopJob(JobParameters jobParams) throws RemoteException {
JobServiceEngine service = mService.get();
if (service != null) {
Message m = Message.obtain(service.mHandler, MSG_STOP_JOB, jobParams);
m.sendToTarget();
}
}
}
class JobHandler extends Handler {
JobHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
final JobParameters params = (JobParameters) msg.obj;
switch (msg.what) {
case MSG_EXECUTE_JOB:
try {
boolean workOngoing = JobServiceEngine.this.onStartJob(params);
ackStartMessage(params, workOngoing);
} catch (Exception e) {
Log.e(TAG, "Error while executing job: " + params.getJobId());
throw new RuntimeException(e);
}
break;
case MSG_STOP_JOB:
try {
boolean ret = JobServiceEngine.this.onStopJob(params);
ackStopMessage(params, ret);
} catch (Exception e) {
Log.e(TAG, "Application unable to handle onStopJob.", e);
throw new RuntimeException(e);
}
break;
case MSG_JOB_FINISHED:
final boolean needsReschedule = (msg.arg2 == 1);
IJobCallback callback = params.getCallback();
if (callback != null) {
try {
callback.jobFinished(params.getJobId(), needsReschedule);
} catch (RemoteException e) {
Log.e(TAG, "Error reporting job finish to system: binder has gone" +
"away.");
}
} else {
Log.e(TAG, "finishJob() called for a nonexistent job id.");
}
break;
default:
Log.e(TAG, "Unrecognised message received.");
break;
}
}
boolean workOngoing = JobServiceEngine.this.onStartJob(params);这儿才是重中之重,真正回调到我们自定义的JobService中了。
现在才真正执行到我们需要执行的onStartJob,这儿的逻辑有点混乱,为了便于理解,画一张时序图方便理解一下。
最终会调度到JobServiceEngine中的onStartJob(...)方法,这个方法是一个抽象方法:
JobServiceEngine.java
public abstract boolean onStartJob(JobParameters params);
public abstract boolean onStopJob(JobParameters params);
实现这些抽象方法的地方就在JobService中:
JobService.java
public final IBinder onBind(Intent intent) {
if (mEngine == null) {
mEngine = new JobServiceEngine(this) {
@Override
public boolean onStartJob(JobParameters params) {
return JobService.this.onStartJob(params);
}
@Override
public boolean onStopJob(JobParameters params) {
return JobService.this.onStopJob(params);
}
};
}
return mEngine.getBinder();
}
现在是不是一切都清楚了,Service绑定之后触发的回调就是onBind(...),然后我们在onBind(...)中实现JobServiceEngine抽象方法,这里直接调用到JobService中的抽象方法,而实现JobService抽象方法的地方又在我们自定义的JobService中,一切都连贯起来了。
3.3 作业取消
关于取消作业,有两种取消的函数,一个是根据jobId取消特定Job,还有是取消所有的Job。
JobScheduler.java
public abstract void cancel(int jobId);
public abstract void cancelAll();
下面描述一下cancel(int jobId)的执行流程:
其实这两个函数的区别从函数的名称上就可以看出来,cancel(int jobId)是取消特定的JobId的作业,cancelAll()是取消当前的所有的job的。
既然传入的JobId,那么肯定有根据JobId来查找对应的Job的流程。
其实两个函数后面执行的流程是一样的,但是cancelAll()在前面多了一个流程:
JobSchedulerService.java
public void cancelJobsForUid(int uid, String reason) {
if (uid == Process.SYSTEM_UID) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
return;
}
synchronized (mLock) {
final List jobsForUid = mJobs.getJobsByUid(uid);
for (int i=0; i
cancelAll()是将当前uid的所有jobStatus取出来,然后分别执行cancel(int jobId)同样的操作。
四、总结
JobScheduler是Google提供的一个可以设置一些前置条件,来规定在特定条件下处理的任务,这样的好处可以简化代码的逻辑,使代码的设计思路变得简单。同时保证了批量处理任务,可以在一定程度上省电,用过JobScheduler的都知道,用它来进程保活非常高效。