背景
先来聊聊怎么会知道 JobSchduler 这神奇的东西。公司项目有这么一个需求:平板上实时记录小朋友的学习情况,然后生成学习报告上传到服务器,供手机端查看。原先的做法是开启服务,在服务里注册广播接收器,当广播接收器接收到 action,就会将数据上传,在网络正常的情况下,该做法是没有问题的;但是当网络很差的情况下,有可能造成上传失败,数据丢失。后来我采用一种方式:当上传失败时,开启定时器上传,直到成功为止。原本以为这样就解决问题,可是后来做后台的同事说由于设置时间短,访问量多,造成服务器流量过大,希望我能找另外一种解决方法。这时 Leader 跟我说采用 JobScheduler,它完全有系统控制,满足一定的条件时触发任务。于是我马上 Google,看看这玩意到底是啥?经过搜索一番,发现 JobScheduler 功能挺强大的。下面是自己学习 JobSchduler 小结。
JobScheduler 是在 Android 5.0 Google 推出的一个新组件,它的出现主要是为了解决某些任务需要在满足一个或多个条件的情况下才触发的需求,这些条件比如网络状态、电池充电、数据变化、自己设定的条件等,在满足条件时会触发相应的 JobScheduler 完成相应的任务。这个过程只需我们对要执行的任务设定条件,其它都由系统控制完成的,无需我们去控制任务。在学习 JobScheduler 的用法之前,先来了解相关的 API,这里涉及到 JobScheduler、JobInfo、JobParameters、JobService 这四个类。
API 讲解
JobScheduler
先来看下官方文档对 JobScheduler 的描述:
根据应用程序自己的进程中调度各种类型的任务。
关于可以运行的任务类型以及如何构建它们的更多描述,请参阅 JobInfo。你将构建这些 JobInfo 对象,并调用 JobScheduler 方法 schdule(JonInfo) 将这些 JobInfo 对象传给它。当设定的条件满足时,系统将会在你应用程序 JobService 上执行相应的任务。当你使用 JobInfo.Builder(int, android.content.ComponentName) 创建你的 JobInfo 时,意味着已经确定哪个 JobService 将执行你的任务逻辑。
框架对于你接收回调的时机很智能的,并且尝试尽可能地分批处理和延迟它们。通常来说,如果你没有为你的任务设置最后期限,那么就会根据 JobScheduler 内部队列当前的状态在任何时刻来执行它们;
可是只要到下一次设备连接电源,那么任务就有可能被延迟。你不能直接实例化 JobScheduler,而是需要通过 Context.getSystemService(Context.JOB_SCHEDULER_SERVICE))) 获取实例。
从官方文档可以知道,JobScheduler 的职责是调度任务、取消任务。JobScheduler 提供 2 个常量和 5 个方法,在了解它们之前,先来了解如何获取 JobScheduler 实例。正如官方文档所介绍的,通过获取系统服务来获取的,代码如下:
JobScheduler jobScheduler = (JobScheduler) Context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
接下来来了解两个常量的具体含义
RESULT_FAILURE:调度任务失败时返回值。
RESULT_SUCCESS:调度任务成功时返回值。
JobScheduler 提供 5 个方法供我们使用,让我们来了解下这 5 个方法的具体用法
cancel(int jobId):取消 JobScheduler 内部队列 id 为 jobId 待处理任务。
cancelAll():取消在这个应用程序上 JobScheduler 已经注册的所有任务。
getAllPendingJobs():检索 JobScheduler 待处理所有任务。
getPendingJob(int jobId):检索 JobScheduler 内部队列 id 为 jobId 待处理任务。
schedule(JobInfo job):调度任务。
JobInfo
JobInfo 对一个即将被执行的任务的信息进行封装,然后供 JobScheduler 调度。由于 JobInfo 包含的信息比较多,所有采用建造者模式来构建其实例,即 JobInfo.Builder 来创建。
来看下官方文档的描述:
将要调度的任务所需的参数(信息)封装为 JobInfo 对象传递给 JobScheduler。使用 JobInfo.Builder 创建 JobInfo 实例。当你正在创建 JobInfo 对象时,你必须至少指定一项约束条件。这样做的目标是为你想完成的任务提供优先级高调度。如果你没有指定任何一项约束时,你的 app 会抛出异常。
那么来看下如何创建 JobInfo 实例
JobInfo.Builder builder = new JobInfo.Builder(jobId, componentName);
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
JobInfo jobInfo = builder.build();
由于创建 JobInfo 对象时至少指定一项约束条件,所以以上只是指定请求网络类型,至于其它属性可以根据自己的需求指定。那么 JobInfo 到底有哪些属性呢?下面一一揭晓。
先来看下 JobInfo 提供常量:
BACKOFF_POLICY_EXPONENTIAL:退避策略,任务失败时等待间隔呈指数增长。
BACKOFF_POLICY_LINEAR:退避策略,任务失败时等待间隔呈线性增长。
DEFAULT_INITIAL_BACKOFF_MILLIS:默认情况下任务的 backoff,以毫秒为单位。
MAX_BACKOFF_DELAY_MILLIS:允许任务最大 backoff,以毫秒为单位。
NETWORK_TYPE_ANY:连接任何网络。
NETWORK_TYPE_NONE:默认值,没联网。
NETWORK_TYPE_NOT_ROAMING:连接非漫游网络。
NETWORK_TYPE_UNMETERED:连接非计量网络。
JobInfo 设置属性的方法由 JobInfo.Builder,那么来看下提供哪些方法设置属性
setRequiredNetworkType (int networkType):设置网络类型。如果任务需要通过网络访问服务器,但是没有调用该方法设置网络类型时,那么任务不会被执行。提供四个参数可以设置:
NETWORK_TYPE_NONE:默认值,不连接网络。
NETWORK_TYPE_ANY:连接任何网络。
NETWORK_TYPE_NOT_ROAMING:连接非漫游网络。
NETWORK_TYPE_UNMETERED:连接非计量网络。
setRequiresCharging (boolean requiresCharging):设置是否连接电源,默认值为 false。
setRequiresDeviceIdle (boolean requiresDeviceIdle):设置是否需要设备处于空闲模式,默认值为 false。空闲模式是系统提供的一种松散模式,意味着设备没有在使用或者已经有一段时间没有使用,这正是执行繁重任务的好时机。
addTriggerContentUri (JobInfo.TriggerContentUri uri):API 24 支持使用 content provider 变化作为触发任务执行的时机。需要指定触发 URL,并通过 ContentObserver 监听 content provider 变化,从而触发任务的执行。注意设置该属性后,不能设置 setPeriodic(long) 或者 setPersisted(boolean) 属性,也就是说不能与他们任何一个一起使用。因为他们之间是不兼容的,如果一起使用的话,当 build() 被调用时,会抛出 IllegalArgumentException 异常。为了持续监听 content 变化,需要在 JobService 完成最近变化执行的任务之前,调用新的 JobInfo 观察相同的 URL。
setTriggerContentMaxDelay (long durationMs):设置当第一次监听到 content 变化到任务执行时可以延迟的最大时间,以毫秒为单位。
setTriggerContentUpdateDelay (long durationMs):设置当监听到 content 变化时到任务执行时可以延迟的时间,如果在这期间监听到更多变化,那么延迟时间的计时将被重置到最近一次更改开始。
setBackoffCriteria (long initialBackoffMillis, int backoffPolicy):设置 back-off 或者 重试策略。注意尝试调用 setRequiresDeviceIdle(boolean) 为任务设置回退策略时,当 build() 被调用时会抛出异常。因为 back-off 对这些工作类型没意义。
第一个参数表示第一次失败时尝试的时间间隔,单位为毫秒,预设的参数有:
DEFAULT_INITIAL_BACKOFF_MILLIS:30000
MAX_BACKOFF_DELAY_MILLIS:18000000
第二个参数表示退避策略
BACKOFF_POLICY_EXPONENTIAL:任务失败时等待间隔呈指数增长。
BACKOFF_POLICY_LINEAR:任务失败时等待间隔呈线性增长。
setMinimumLatency (long minLatencyMillis):指定任务延迟执行时间。
setOverrideDeadline (long maxExecutionDelayMillis):设置任务执行最大的延迟时间。即使到了时间期限,条件还没满足,任务也会被执行。
setPeriodic (long intervalMillis):指定任务在一定的周期内执行,并且每一个任务在周期内只执行一次。调用该方法设置后,不能再调用 setMinimumLatency (long minLatencyMillis) 或者 setOverrideDeadline (long maxExecutionDelayMillis) 方法,否则会抛出异常。
setPersisted (boolean isPersisted):设置当设备重启,任务是否被重新调度。如果设置 true,必须申请权限 RECEIVE_BOOT_COMPLETED,否则运行时会报错。
setExtras (PersistableBundle extras):设置额外参数,值允许原始数据类型。
JobService
JobScheduler 所要调度的任务是在 JobService 定义的,而 JobService 是继承 Service;也就是说,JobService 也是服务,只是它与四大组件之一 Service 有所区别。JobService 有一大特点是无论你的 app 是否处于活跃状态,当你的任务满足特定的条件时,系统都会执行任务。我们可以编写多个 JobServices,而且每个 JobService 指定不同的任务,每个任务在某个时间点被执行。
来看下官方文档的描述
JobScheduler 回调的入口点。
JobService 是处理之前调度的异步请求的基类。你应该重写 onStartJob (JobParameters params) 方法,将在该方法实现你的任务逻辑。
此服务运行在应用程序主线程处理传入的任务。这意味着你必须将执行逻辑放到子线程、handler、AsyncTask。如果不这样做的话会阻塞 JobManager 的回调,特别是 onStopJob(android.app.job.JobParameters),这意味着将通知你不满足调度要求。
那么该如何实现 JobService 呢?必须创建一个新类,继承 JobService,并重写方法 onStartJob(JobParameters) 和 onStopJob(android.app.job.JobParameters)。下面给出一个模板:
public class JobSchedulerService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
从以上可知,两个方法都返回 boolean 值,那么什么时候返回 true,什么时候返回 false 呢,返回值对 JobScheduler 任务调度又有什么影响呢?下面一一来解析:
onStartJob(JobParameters):在此方法实现任务的逻辑。由于 JobService 是在主线程运行,因此对于逻辑简单的可以直接写在该方法里,但是对于比较复杂任务,例如网络请求,那么就要开启子线程来操作,以免造成阻塞。当任务完成的时候返回 false,作用是通知系统任务已经完成;当有任务要执行的话返回 true,作用是让系统知道有任务即将执行或正在执行,并对该任务持有锁。因为任务一旦完成并通知系统,系统就释放持有该任务锁。
onStopJob (JobParameters params):当任务未完成调用 jobFinished(JobParameters, boolean) 取消任务时,此方法就会被调用。发生这种现象的原因大部分是调度的任务不满足所指定的条件,导致系统无法执行任务。当任务停止时,如果还想系统重新调度任务的话,那么返回 true;反之返回 false,此时系统会移除任务,导致所要调度的任务必须暂停。
除此之外,JobService 还提供了 jobFinished(JobParameters, boolean) 这个方法,虽然不用重写该方法,但是该方法却有很大的作用。此回调方法用来通知 JobManager 任务已经完成。由于此方法最终在主线程调用,因此可以在任何线程调用该方法。当系统收到信息时,就会释放持有该任务锁。当 onStartJob(JobParameters) 返回 true,即表示任务正在执行或要被执行,在任务执行完成后需要调用 jobFinished(JobParameters, boolean) 方法来通知系统任务已经完成,此时系统才可以安全地释放持有该任务锁。如果忘记调用该方法的话,应用中其它任务就不会被执行。
jobFinished(JobParameters, boolean) 需要传入两个参数:第一个参数 JobParameters 表示当前任务的信息,以至于任务完成时系统知道释放哪个锁;第二个参数是 boolean 值,true 表示根据退避策略(back-off criteria)重新调度任务;false 则表示不调度任务。
跟四大组件之一 service 一样,都需要在 AndroidManifest.xml 声明,但是有一点不同的是需要添加权限 android:permission=”android.permission.BIND_JOB_SERVICE”
<service
android:permission="android.permission.BIND_JOB_SERVICE"
android:name=".service.JobSchedulerService" >
JobParameters
来看下官方文档描述
JobParameters 对任务的信息进行封装,当任务被调度时,系统就会创建该对象,包含任务的信息;自己是无法实例化该对象的。
PS:自己觉得是与 JobInfo 对应的,JobInfo 是设置属性,而 JobParameters 是获取相应属性。
那么来看下 JobParameters 提供的方法,只列出部分:
getJobId ():获取每个任务独一无二的 id。
getExtras ():获取额外参数。
了解 API 之后,接下来的任务是学习 JobScheduler 用法。
JobScheduler 用法
对于 JobScheduler 的用法,我打算用项目中使用到 JobScheduler 作为例子,前提是移除了业务逻辑,代码可能不太完整。换句话说吧,给个模板吧。
先给出例子,再来分步讲解吧。代码如下:
public class JobSchedulerService extends JobService {
private static final String TAG = JobSchedulerService.class.getCanonicalName();
public final static String TASK = "com.demo.panju.task";
private final static int JOB_ID = 1;
private static ComponentName mComponentName;
private static JobScheduler mJobScheduler;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case 1;
task();
break;
default:
break;
}
}
};
@Override
public void onCreate() {
super.onCreate();
mReceiver = new Receiver();
mFilter = new IntentFilter();
mComponentName = new ComponentName(getPackageName(), JobSchedulerService.class.getName());
mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
registerReceiver();
}
@Override
public boolean onStartJob(JobParameters params) {
sendMessage(params);
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
mHandler.removeMessages(1));
return false;
}
@Override
public void onLowMemory() {
super.onLowMemory();
stopSelf();
}
@Override
public void onDestroy() {
super.onDestroy();
if (null != mReceiver) {
unregisterReceiver(mReceiver);
mReceiver = null;
}
}
private static void scheduleJob(int jobId) {
JobInfo.Builder builder = new JobInfo.Builder(jobId, mComponentName);
builder.setPersisted(true);
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
builder.setOverrideDeadline(500);
mJobScheduler.schedule(builder.build());
}
private void sendMessage(JobParameters parameters) {
Message message = mHandler.obtainMessage();
message.what = 1;
message.obj = parameters;
mHandler.sendMessage(message);
}
private void registerReceiver() {
mFilter.addAction(TASK);
registerReceiver(mReceiver, mFilter);
}
private void task() {
mApi.task(new Callback() {}
@Override
public void onSuccess(Object result) {
jobFinished(mJobParameters, false);
}
@Override
public void onError(Object e, int errno) {
jobFinished(mJobParameters, true);
}
});
}
private class Receiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TASK.equals(action)) {
scheduleJob(JOB_ID);
}
}
}
}
以上例子应该不难理解,接下来分步讲解:
创建新类 JobSchedulerService 并继承 Service,重写方法 onStartJob(JobParameters params) 和 onStopJob(JobParameters params)。onStartJob(JobParameters params) 返回 true 表示任务将被执行;onStopJob(JobParameters params) 返回 false 表示当任务中途被取消而导致暂停任务,系统将会移除任务。
创建 JobScheduler 和 ComponentName 对象
mComponentName = new ComponentName(getPackageName(), JobSchedulerService.class.getName()); mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
对调度任务所需要的属性进行封装
private static void scheduleJob(int jobId) { JobInfo.Builder builder = new JobInfo.Builder(jobId, mComponentName); builder.setPersisted(true); builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); builder.setOverrideDeadline(500); mJobScheduler.schedule(builder.build()); }
编写广播接收器、注册接收器、重写 Handler 回调方法 handleMessage(Message msg) 并实现相应逻辑。
当注册的广播接收器收到相应 action 时,就会调用方法 scheduleJob(JOB_ID),即调度任务,那么方法 onStartJob(JobParameters params) 就会被调用,在该方法里通过 Handler 机制发送消息,Handler 的回调方法 handleMessage(Message msg) 就会被调用,实现的逻辑主要是发起网络请求,即 task() 方法。task() 方法逻辑中有两个回调方法:onSuccess(Object result) 和 onError(Object e, int errno)。发起网络请求成功的话就会调用方法 onSuccess(Object result),在该方法里又调用 jobFinished(mJobParameters, false),传入的 boolean 值是 false,意味着任务已经成功完成,无需重新调度任务;发起网络请求失败的话就会调用 onError(Object e, int errno),在该方法里又会调用 jobFinished(mJobParameters, true),传入的 boolean 值是 true,意味着任务失败,根据重试策略重新调度任务。
由于自己的水平有限,若有些地方描述的不对或者翻译的不恰当(参考官方文档和国外博客),欢迎指出 ! 大家一起学习,共同进步 !
参考链接
https://medium.com/google-developers/scheduling-jobs-like-a-pro-with-jobscheduler-286ef8510129
http://josiassena.com/the-jobscheduler-on-android/
http://blog.youkuaiyun.com/bboyfeiyu/article/details/44809395
http://zhanghuimin.com/2016/10/27/about-android-job-scheduler/