Android 延时性工作JobScheduler

作者: Leavy_song | 来源:发表于2019-12-13 14:19 被阅读0次

    JobScheduler介绍

    JobScheduler是一种API,能够将多样的工作在未来在应用程序进程中执行,JobScheduler是执行延时性工作也可以吧多个工作整合到一起统一执行,使用JobScheduler可以智能的安排工作并尽可能对工作做批处理和延迟。不制定工作的执行日期就可以按照JobScheduler内部队列的当前状态,可以随时运行它。

    JobScheduler获取不能直接去new一个JobScheduler对象而是通过Context来获取系统服务的形式来获取。

        //获取jobScheduler 实例
        jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
    

    JobInfo介绍与使用

    JobScheduler要执行工作还需要数据,数据的来源就是JobInfoJobInfo作为传递给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

    官网解释是从字符串键到各种类型的值的映射。该类支持的类型集有目的地仅限于可以安全地保存到磁盘并从磁盘中还原的简单对象,PersistableBundleBundle都是BaseBundle的子类所以在用法上两者几乎是相同的。值得注意是PersistableBundle相比Bundle多实现XmlUtils.WriteMapCallback来操作xml文件写入保存和读取对象,如下图

    PersistableBundle使用XmlUtils操作xml文件.png

    JobService介绍

    JobServiceJobScheduler回调的入口点。这是官网给出的解释,简单来说就是当JobScheduler开始工作就会回调到JobService中进行相应的业务逻辑处理工作。JobService主要实现两个方法onStartJob(JobParameters params)onStopJob(JobParameters params)

    JobService.png

    从上图可以看出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对象的工作来合并工作以便做批处理(这里面以字符串为工作内容数据为例),首先拿到之前相同JobIDJobIDint类型的自定义值随便多少都行,主要是为了根据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中的代码。

    JobSchedulerStub.png

    JobScheduler使用中通过JobScheduler.schedule(JobInfo)来添加任务在JobSchedulerStub中同样可以找到schedule方法,所以在JobSchedulerStub执行是最后也会执行schedule方法,顺着这个思路继续看代码,可以在schedule方法中找到最后时执行了scheduleAsPackage()方法。

    schedule方法.png

    scheduleAsPackage()中结尾处可以看到如下图,在注释中可以获取到信息,大概意思是这是一个新工作,就可以立即把它放在等待名单并尝试运行它。说明maybeRunPendingJobsLocked()方法就是执行任务时的关键代码。

    scheduleAsPackage方法.png

    maybeRunPendingJobsLocked()方法中只执行了两个方法是assignJobsToContextsLocked()reportActiveLocked()先看下reportActiveLocked()方法,方法内容就是判断是否还有任务在排队等待或是正在执行,也就是会去校验当前的工作队列的状态来设置队列中工作的状态(active),如果有工作在工作或是工作队列中存在排队等待执行的任务,那么active的状态就会被修改为true。所以reportActiveLocked()方法是修改工作队列的状态的代码。

    reportActiveLocked方法.png

    assignJobsToContextsLocked方法中写了很多得循环和判断的代码,查阅代码时可以找到executeRunnableJob(JobStatus job)方法,通过方法名称可以猜出executeRunnableJob(JobStatus job)就是执行工作的代码。

    executeRunnableJob.png

    executeRunnableJob(JobStatus job)方法中可以看见注释意思是开始工作但是要确保context上下文可以使用或者context不可以使用异常。

    executeRunnableJob开头注释.png

    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中,然后绑定了服务。

    bindservice.png

    所以JobScheduler之所以能工回调到JobService中是因为在初始化创建JobInfo时传入的ComponentName中包含着集成JobService的类的信息,然后在执行工作时再通过JobInfo来获取,最后通过public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user)方法完成绑定Service实现工作时回调到JobService子类中进行工作。


    那么JobScheduler分析就到这里了查看完整代码及更多知识点这里

    相关文章

      网友评论

        本文标题:Android 延时性工作JobScheduler

        本文链接:https://www.haomeiwen.com/subject/pcvxnctx.html