一. Background
首先这两个概念是从Android L加入的,最初的目的是为了省电!
从名字可以看出,是一个计划安排,但肯定不是为了保活就是了,我们可以通过官方一张图来看一下他是个什么原理。
对于后台网络加载,最初是一进行网络加载就唤醒设备,加载就唤醒设备,这个电量浪费可想而知,当电量低的时候,进行唤醒更不合适,因此在Android L后面加了此API,限制后台唤醒,系统会在合适的时候一起调度去运行,然后就形成了下面的图形。
这点应该跟MIUI的对齐唤醒类似。
所以根据上面所说,我们也可以提前知道后面的Api里面为什么会提供一些低电量停止任务,充电时执行任务的方法。
But!
看起来这么高大上的技术,国内的厂商却用他来做保活!!!
实际上,在Android5上,JobScheduler的运行效果相当显著,在某测试机上测试,基本上可以按照时间周期性调度。 (当然国内各大厂的系统还是太强了……)
但随着系统的迭代,在Android后面的版本上,系统底层所做的限制越来越多,包括周期执行时间不得低于15分钟,这就让IM类应用很难受了,还是要去另寻他法。
但无论如何,保活都是一个伪命题,就算是加入了系统白名单也不一定能存活或者重新唤醒,国内某鹅厂的Tim所采用的Linux底层唤醒,也免不了被一杀啥消息都收不到。
所以我们不谈论保活,而更多的是讨论JobScheduler和JobService用在执行后台简单任务上面。
二. Use
JobScheduler是需要协同JobService来一起使用的,在准备实现一个Schedule之前,我们要实现一个JobService。
1. 撸起袖子实现的 JobService
首先查看一下文档, JobService的继承关系如下。
JobService 是一个abstract class,继承自Service,那么他当然也会拥有一些Service拥有的特性(使用特点),比如说onCreate,onStartCommand,注册
对于其中的方法,主要是两个abstract的method,一个final的方法,而其中的abstract就是我们需要自行去实现的。
撸起袖子就是一顿继承。
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class DemoJobService extends JobService {
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d("tag","onStartJob");
return false;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
Log.d("tag","onStopJob");
return false;
}
}
阔以看到,我们已经实现了其中的方法,分别是对应job启动的onStartJob()和job结束的onStopJob()方法。
当然,不要忘记我们的API是在Android L里面加入的,所以加入@RequiresApi注解。
2. 记得注册了喵~
刚才说过玩service一定不要忘了注册,所以在各个地方,Google都提醒了你 别忘记注册!!!
Android Studio里也有,但是本喵不会截warning的图
这家伙与众不同的一点就是需要配置一个permission。
3. 关键人物 - Scheduler
对于JobScheduler,理论上是暴露给外人用的,比如我们在MainActivity中。
int jobId = 1;
JobScheduler jobScheduler = (JobScheduler)getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(getPackageName(),
DemoJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(jobId,jobService)
.setPeriodic(15 * 60 * 1000)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build();
if(jobScheduler != null){
jobScheduler.schedule(jobInfo);
}
但我们可爱的Java程序猿们总喜欢吧功能封装起来,给他扣一顶可爱的static帽子,然后公布于众。
于是修改DemoJobService代码,加一个静态的invoke()方法。
public static void invoke(){
int jobId = 1;
JobScheduler jobScheduler = (JobScheduler)
App.instance().getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(App.instance().getPackageName(),
DemoJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(jobId,jobService)
.setPeriodic(15 * 60 * 1000)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build();
if(jobScheduler != null){
jobScheduler.schedule(jobInfo);
}
}
核心是最后一句,用系统的JobScheduler,执行了一个自定义的JobInfo,仔细查看schedule()方法。
这家伙还有返回值,代表了schedule的结果。
安排?
ok, return RESULT_SUCCESS
这家伙甚至是一个abstract method,至于谁实现了他,我们先不用管。
这样调用变的简单起来。
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DemoJobService.invoke();
}
});
好了,你可以运行一下看看效果了,虽然你什么都不可能看到,偶尔有幸可以看到你的log,为什么这么说,我们后面为你揭晓。
三. Introduce
上面我们已经跑了一个只有一个 button的破界面了,可是更强大的机制在后台运行着呢。
1. 基础api
int jobId = 1;
JobScheduler jobScheduler = (JobScheduler)
App.instance().getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(App.instance().getPackageName(),
DemoJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(jobId,jobService)
.setPeriodic(15 * 60 * 1000)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.build();
if(jobScheduler != null){
jobScheduler.schedule(jobInfo);
}
还是不变的代码,变心了的文章。
你变了,变得开始讲api怎么使用了,你再也不是balabala给我说的天花乱坠的,我一句听不懂的,觉得你超帅的,讲系统源码了的那个大佬了。
嫌弃我?那我们简单了解即可。(自导自演好欢乐)
- jobId就是job的身份证,身份证号有啥特点就不用我说了吧。
- JobScheduler是从系统服务获取出来的用于执行job的大佬,至于App.instance()是我自己实现的获取context的方法,下面是代码,记得添加Application 的name属性(不会用自定义Application的请面Google思过)
public class App extends Application {
protected static App instance;
@Override
public void onCreate() {
super.onCreate();
instance = this;
}
public static Context instance() {
return instance.getApplicationContext();
}
}
JobInfo是基于构造者模式来实现的,其Builder默认需要接受jobId和JobService所包装的ComponentName,构造一个ComponentName对象需要两个参数,首先是包名,其次是要被安排上的JobService的名字。
setPeriodic(xxx毫秒),重复执行周期,设置了这个属性后,系统会每隔xxx毫秒来执行一次,勤勤恳恳,不会说累,结合 setPersisted(true) 使用,妈妈再也不用担心我关了机jobSevice就不重复执行了馁。
注1. Android L的时候,时间可以设的很小,比如说10s执行一次,系统也很认真的10s执行一次,但是随着国内各厂利用此Api进行保活手段越来越高明,和Google越来越机灵,在后来的Android版本上,这个时间被限制到15分钟以上了……尴尬。
注2. setPersisted(true),true为关机后再开机不影响任务,false为关机后就死翘翘了。
- setRequiredNetworkType() ,需要在什么网络下执行,这里我们选的是NETWORK_TYPE_ANY,代表任何网络均可。
2. 高级api
翻译文档走起!
谁会干那么无聊的事情呢,况且我English也不是very well啊,所以这里就捡几个重点的说一下吧,有些东西,可能你这辈子有永不到哦,(你非要用那你就用……)
setMinimumLatency (long minLatencyMillis)
设置job延迟一段时间后执行:谁还不是个宝宝,就不能等我打扮完再安排我?ps:打扮时间是
minLatencyMillis 毫秒
调用了此方法后又设置了setPeriodic周期将会 boom!
按照逻辑想想也不对是把,设置了周期又设置延迟,你让系统怎么执行,按照谁来走?左右为难啊。
setOverrideDeadline (long maxExecutionDelayMillis)
设置此Job的最大延迟调度,无论任何条件,即使有条件不满足,Job也将在该截止时间前运行,说白了就是一个系统执行此job的deadline。
同样,设置了周期时间会抛异常。
setRequiredNetworkType (int networkType)
在某种 网络类型下才可以执行此任务,比如说无网,任何网络,wifi,4g等等
setRequiresBatteryNotLow (boolean batteryNotLow)
设置是否低电量才执行这个任务
setRequiresCharging (boolean requiresCharging)
设置是否在充电时执行任务
setRequiresDeviceIdle (boolean requiresDeviceIdle)
设置是否在交互时执行任务
当用户玩手机的时候,就不执行,不玩了就执行。
setRequiresStorageNotLow (boolean storageNotLow)
指定此Job是否只在可用存储空间太低时运行,默认为false。
讲Api简直是世界上最无聊的事情了。我们来点刺激的。
3. 起始
又回到我们的JobService。
对于开始,这个话题,在这里就从onStartJob说起吧,设置了schedule之后,系统进入调度job状态,而这个调度跟手机电量,网络,balabala都有关,也有可能因为国产手机的一键清理全部死翘翘,但无论如何,我们都要说下去。
假设现在某job被调度了,进入了onStartJob()方法。
@Override
public boolean onStartJob(JobParameters jobParameters) {
//我是一个小任务
//我一会就执行完了
//到return时已完成任务
return false;
}
@Override
public boolean onStartJob(JobParameters jobParameters) {
//我是一个耗时任务
//半天才执行完
//到return时还没执行完,需要手动结束
return true;
}
首先onStartJob和onStopJob都是在主线程执行的,而onStartJob有这样两种情况,非耗时任务和耗时任务,对于这两种任务,系统要求我们返回false和true两种值。(对于自己的任务是否耗时开发者自己清楚,要是自欺欺人的话!哼哼!ANR来一发。)
首先我们要明白一点,这也是Google官方所做的限制,当我们的onStartJob返回值是false的时候,说明任务应执行完(在return之前),那就没必要再去执行onStopJob()方法, 因为我们完全可以在onStartJob最后面写执行完的逻辑,因此Google文档上有这么一段话。
这意味着,如果你的onStartJob返回false,你的onStopJob不会执行,千万不要在onStopJob里面做无用功了。
但是如果你要返回true的话,意味着你的任务是一个耗时操作。
按照Service是在主线程执行的规定,你需要在onStartJob里面写一个异步操作,这里用AsynTask和Handler都行。
在任务执行结束的时候需要手动调用相关方法来结束本次任务,否则将会造成阻塞,让系统误以为你一直在运行,还没结束,导致后面的任务没法执行。
joinFinished就是需要我们手动执行的方法,它有两个参数,第一个是JobParameters,第二个是wantsReschedule
首先第一个参数是从onStartJob方法传递的,这就要求我们要记录这个参数值,第二个参数是bool类型,表示是否想要被重新调度,填true的话就会被重新加入调度队列。
其实对于JobService需要知道的就是这三个比较重要的方法以及他们何时应该被主动调用和被动调用。
四. Summary
对于JobScheduler和JobService其实是作为一个后台任务来使用的,如果作为后台保活的招数来使用不仅达不到省电的目的还更耗电。
其次对于国内大厂定制的Rom,JobService有些时候也不是工作很正常,目前还没找到一个合适的工具来测试这个API,唯一的adb命令也没啥可参考的,(可能是我太弱了……),请有知道的大佬热心分享一下。