Android 延时性工作JobScheduler
JobScheduler介绍
JobScheduler
是一种API,能够将多样的工作在未来在应用程序进程中执行,JobScheduler
是执行延时性工作也可以吧多个工作整合到一起统一执行,使用JobScheduler
可以智能的安排工作并尽可能对工作做批处理和延迟。不制定工作的执行日期就可以按照JobScheduler
内部队列的当前状态,可以随时运行它。
JobScheduler
获取不能直接去new一个JobScheduler
对象而是通过Context来获取系统服务的形式来获取。
//获取jobScheduler 实例
jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo介绍与使用
JobScheduler
要执行工作还需要数据,数据的来源就是JobInfo
,JobInfo
作为传递给JobScheduler
的数据的容器,需要封装针对工作时需要的参数。JobInfo生
成是使用构建者模式在创建JobInfo
时至少要制定一种约束。比如(下面是距离下描述情况视情况而定添加约束):
JobInfo newJobInfo = new JobInfo.Builder(jobID,new ComponentName(appContext, MyJobService.class))
//在这里可以选择配置很多得属性
//设置不在低电量时工作
.setRequiresCharging(true)
//设置没有在无计量时
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
//设置的详细描述的那种网络你的工作需要 需要 (Build.VERSION_CODES.O =>26)
.setRequiredNetwork(new NetworkRequest.Builder().build())
//设置电量不可以太低 默认是false 如果是设置true 的时候工作会在电量不低的情况工作,
//一般会提示出低电量警告需要 (Build.VERSION_CODES.O =>26)
.setRequiresBatteryNotLow(false)
//设置工作是否被积极使用,默认是false 如果设置成true 及时在操作手机的同时也会工作
.setRequiresDeviceIdle()
//设置存储空间不能太低 需要 (Build.VERSION_CODES.O =>26)
.setRequiresStorageNotLow(true)
.build();
PersistableBundle
PersistableBundle使用XmlUtils操作xml文件.png官网解释是从字符串键到各种类型的值的映射。该类支持的类型集有目的地仅限于可以安全地保存到磁盘并从磁盘中还原的简单对象,
PersistableBundle
和Bundle
都是BaseBundle
的子类所以在用法上两者几乎是相同的。值得注意是PersistableBundle
相比Bundle
多实现XmlUtils.WriteMapCallback
来操作xml
文件写入保存和读取对象,如下图
JobService介绍
JobService.png
JobService
是JobScheduler
回调的入口点。这是官网给出的解释,简单来说就是当JobScheduler
开始工作就会回调到JobService
中进行相应的业务逻辑处理工作。JobService
主要实现两个方法onStartJob(JobParameters params)
和onStopJob(JobParameters params)
。
从上图可以看出JobService
就是Service
的子类,同时在JobService
中封装了几个需要实现的方法。
onStartJob方法
这是处理以前计划的异步请求的基类。负责重写
JobService#onStartJobs(JobParameters)
,这是实现作业逻辑的地方。
onStopJob方法
JobService
运行在主线程上所以处理业务逻辑需要在其他线程比如AsyncTask
,要不然会造成阻塞。当执行onStopJobs
就说明工作调度不在满足工作需求约束。
jobFinished方法
通知
JobScheduler
作业已经完成。当系统受到这个消息时会释放持有唤醒的工作。
使用JobService
时还需要注意在注册时需要加入权限(android.permission.BIND_JOB_SERVICE)
如下
<service android:name="MyJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
...
</service>
JobScheduler使用
首先获取到JobScheduler
对象,然后开始获取JobScheduler
对象的工作来合并工作以便做批处理(这里面以字符串为工作内容数据为例),首先拿到之前相同JobID
(JobID
为int
类型的自定义值随便多少都行,主要是为了根据JobID
找到工作并合并)的工作代码如下:
JobInfo jobInfo = null ;
//24之后可以直接寻找jobid合并工作 24之前需要遍历
if (Build.VERSION.SDK_INT>Build.VERSION_CODES.N) {
//Android N之后可以直接获取
jobInfo = jobScheduler.getPendingJob(jobID);
}else{
//Android N之前需要先获取全部工作在循环根据JobID获取
List<JobInfo> allPendingJobs = jobScheduler.getAllPendingJobs();
for (JobInfo info : allPendingJobs) {
if (info.getId()==jobID) {
jobInfo = info ;
break;
}
}
}
通过上面的代码就可以获取之前添加的工作,如果可以找到要在执行的相同JobID
工作就合并工作,如果没有找到工作就添加新的工作并给予JobID
。代码如下:
//判断jobInfo是否为空
if (jobInfo!=null) {
PersistableBundle extras = jobInfo.getExtras();
String my_location = extras.getString("location_data");
//这里使用字符串(也可以用别的类型的工作,具体的业务逻辑要交给JobService去实现)拼接的&可以随便写 要和后·· 台沟通达成自定义协议
//比如 传给后台的是“北京&上海&深圳&海口这”这一类的字符串后台自己解析 这里是统计数据后提交
location += "&"+location;
//关闭当前任务后面重新提交合并后的任务
jobScheduler.cancel(jobID);
}
//PersistableBundle和Bundle用法差不多都是BaseBundle的子类详情看代码这里用法就是和bundle差不多的
PersistableBundle bundle = new PersistableBundle();
//注意与上面取值时对应的key要相同
bundle.putString("location_data",location);
//这里设置了ComponentName中的MyJobService绑定了服务
JobInfo newJobInfo = new JobInfo.Builder(jobID,new ComponentName(appContext, MyJobService.class))
//在这里可以选择配置很多得属性
//设置不在低电量时工作
.setRequiresCharging(true)
//设置没有在无计量时
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
//设置的详细描述的那种网络你的工作需要 需要 (Build.VERSION_CODES.O =>26)
.setRequiredNetwork(new NetworkRequest.Builder().build())
//设置电量不可以太低 默认是false 如果是设置true 的时候工作会在电量不低的情况工作,一般会提示出低电量警告 需要 (Build.VERSION_CODES.O =>26)
.setRequiresBatteryNotLow(false)
//设置工作是否被积极使用,默认是false 如果设置成true 及时在操作手机的同时也会工作
.setRequiresDeviceIdle()
//设置存储空间不能太低 需要 (Build.VERSION_CODES.O =>26)
.setRequiresStorageNotLow(true)
//将放置在PersistableBundle中的数据设置到JobInfo中
.setExtras(bundle)
.build();
//提交任务
jobScheduler.schedule(newJobInfo);
下面看下MyJobService中的代码。
@Override
public boolean onStartJob(JobParameters params) {
//如果返回值是false,这个方法返回时任务已经执行完毕。
//如果返回值是true,那么这个任务正要被执行,就需要开始执行任务。
//当任务执行完毕时你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来通知系统
new UploadTask().execute();
return true;
}
//当系统接收到一个取消请求时
@Override
public boolean onStopJob(JobParameters params) {
//如果onStartJob返回false,那么onStopJob不会被调用
// 返回 true 则会重新计划这个job
return false;
}
class UploadTask extends AsyncTask<JobParameters,Void,Void> {
JobParameters jobParameters ;
@Override
protected Void doInBackground(JobParameters[] jobInfos) {
jobParameters = jobInfos[0];
String location = jobParameters.getExtras().getString("location_data");
OutputStream os = null ;
HttpURLConnection connection = null ;
try {
//这里随便写的网址无所谓的
connection = (HttpURLConnection) new URL("https://www.xxxxxx.com/").openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
os = connection.getOutputStream();
os.write(location.getBytes());
os.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (os!=null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(connection!=null){
connection.disconnect();
}
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
jobFinished(jobParameters,false);
}
}
代码很简单就是实现了JobService
做了一些逻辑处理操作并使用AsyncTask
去子线程完成任务。
那么为什么jobScheduler
可以绑定服务呢,一般的系统系统服务获取的要么反射要么AIDL
,所以看下jobScheduler
来一探究竟。首先jobScheduler
是通过Context.JOB_SCHEDULER_SERVICE
来获得到的,所以在源码中应该可以发现有JobSchedulerService
类。使用AS双击一般通过shift
键可以全局搜索文档,在弹出窗体内输入JobSchedulerService
,
随后就可以找到JobSchedulerService.java
,在JobSchedulerService
类的结构中可以找到JobSchedulerStub
所以看到这里应该可以猜到,JobScheduler
最后是通过AIDL
来通信最后执行JobSchedulerStub
中的代码来执行工作的,所以接下来重点查看JobSchedulerStub
中的代码。
JobScheduler
使用中通过JobScheduler.schedule(JobInfo)
来添加任务在JobSchedulerStub
中同样可以找到schedule
方法,所以在JobSchedulerStub
执行是最后也会执行schedule
方法,顺着这个思路继续看代码,可以在schedule
方法中找到最后时执行了scheduleAsPackage()
方法。
在scheduleAsPackage()
中结尾处可以看到如下图,在注释中可以获取到信息,大概意思是这是一个新工作,就可以立即把它放在等待名单并尝试运行它。说明maybeRunPendingJobsLocked()
方法就是执行任务时的关键代码。
maybeRunPendingJobsLocked()
方法中只执行了两个方法是assignJobsToContextsLocked()
和reportActiveLocked()
先看下reportActiveLocked()
方法,方法内容就是判断是否还有任务在排队等待或是正在执行,也就是会去校验当前的工作队列的状态来设置队列中工作的状态(active)
,如果有工作在工作或是工作队列中存在排队等待执行的任务,那么active
的状态就会被修改为true。所以reportActiveLocked()
方法是修改工作队列的状态的代码。
assignJobsToContextsLocked
方法中写了很多得循环和判断的代码,查阅代码时可以找到executeRunnableJob(JobStatus job)
方法,通过方法名称可以猜出executeRunnableJob(JobStatus job)
就是执行工作的代码。
executeRunnableJob(JobStatus job)
方法中可以看见注释意思是开始工作但是要确保context
上下文可以使用或者context
不可以使用异常。
executeRunnableJob(JobStatus job)
方法中找到:
final Intent intent = new Intent().setComponent(job.getServiceComponent());
boolean binding = mContext.bindServiceAsUser(intent, this,Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,new UserHandle(job.getUserId()));
代码中通过job.getServiceComponent()
获取在创建JobInfo
时传入的ComponentName
对象,而在传入的ComponentName
对象当中就包含了MyJobService.class
信息,在执行到这段代码时将JobInfo
中的ComponentName
取出同时带着MyJobService
放到了intent
中,然后绑定了服务。
所以JobScheduler
之所以能工回调到JobService
中是因为在初始化创建JobInfo
时传入的ComponentName
中包含着集成JobService
的类的信息,然后在执行工作时再通过JobInfo
来获取,最后通过public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user)
方法完成绑定Service
实现工作时回调到JobService
子类中进行工作。
那么JobScheduler分析就到这里了查看完整代码及更多知识点这里