Android 延时性工作JobScheduler

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分析就到这里了查看完整代码及更多知识点这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容