Service - JobScheduler

一、JobScheduler的诞生

Android 5.0系统以前,在处理一些特定情况下的任务,或者是为了应用的保活,我们通常是使用了Service常驻后台来满足我们的需求。当达到某个条件时触发该Service来进行相应任务的处理。或者仅仅是为了我们自己的应用不被系统回收销毁。这样做在满足了自己应用的需求的同时也消耗了部分硬件性能。对用户的体验上,和Android系统环境上都有不利的影响。而且在其它地方,大多数开发者都认为在后台永驻进程,是获取用户的隐私,是不合法的。然而在我国也许是因为开发商的需求大,迫切想要达到自己的目标,使用永驻的进程可以完成用户行为分析和推送等其它后台的业务。因此在开发的模式上采取了极端的个体主义思想。

Android5.0系统以后,Google为了优化Android系统,提高使用流畅度以及延长电池续航,加入了在应用后台/锁屏时,系统会回收应用,同时自动销毁应用拉起的Service的机制。同时为了满足在特定条件下需要执行某些任务的需求,google在全新一代操作系统上,采取了Job(jobservice & JobInfo)的方式,即每个需要后台的业务处理为一个job,通过系统管理job,来提高资源的利用率,从而提高性能,节省电源。这样又能满足APP开发商的要求,又能满足系统性能的要求。Jobscheduler由此应运而生。

JobScheduler的出现主要是为了解决某些任务需要在满足一个或多个条件的情况下才触发的需求,这些条件比如网络状态、电池充电、数据变化、自己设定的条件等,在满足条件时会触发相应的 JobScheduler 完成相应的任务。这个过程只需我们对要执行的任务设定条件,其它都由系统控制完成的,无需我们去控制任务。

二、JobScheduler的特性

1、Job Scheduler 只有在 Api21或以上 的系统支持

2、Job Scheduler 可以将多个任务打包在一个场景下执行

3、Job Scheduler 支持在一个任务上组合多个条件,比如:设备待机、设备充电和连接网络

4、Job Scheduler 支持持续的job,这意味着设备重启后,之前被中断的job可以继续执行

5、Job Scheduler 支持设置job的最后执行期限

6、根据你的配置,可以设置job在后台运行还是在主线程中运行

7、如果在一定期限内还没有满足特定执行所需情况,Job Scheduler会将这些任务加入队列,并且随后会进行执行

三、JobScheduler的API及使用

API 讲解

在学习 JobScheduler 的用法之前,先来了解相关的 API,这里涉及到 JobScheduler、JobInfo、JobParameters、JobService 这四个类。

JobScheduler

从官方文档可以知道,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.Builder 创建 JobInfo 实例。当你正在创建 JobInfo 对象时,你必须至少指定一项约束条件。这样做的目标是为你想完成的任务提供优先级高调度。如果你没有指定任何一项约束时,你的 app 会抛出异常。

JobInfo 对一个即将被执行的任务的信息进行封装,然后供 JobScheduler 调度。由于 JobInfo 包含的信息比较多,所有采用建造者模式来构建其实例,即 JobInfo.Builder 来创建。

下面创建实例:

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):设置网络类型。如果任务需要通过网络访问服务器,但是没有调用该方法设置网络类型时,那么任务不会被执行。提供四个参数可以设置:

      - [1] NETWORK_TYPE_NONE:默认值,不连接网络。
    
      - [2] NETWORK_TYPE_ANY:连接任何网络。
    
      - [3] NETWORK_TYPE_NOT_ROAMING:连接非漫游网络。
    
      - [4] 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

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,此时系统会移除任务,导致所要调度的任务必须暂停。

  • jobFinished(JobParameters, boolean) : 除此之外,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 对任务的信息进行封装,当任务被调度时,系统就会创建该对象,包含任务的信息;自己是无法实例化该对象的。

列出两个常用方法:

  • getJobId ():获取每个任务独一无二的 id。

  • getExtras ():获取额外参数。


JobScheduler的使用

Jobscheduler使用流程分四部分:

1、定义类继承JobService类,定义需要执行的任务(UI线程)
2、获取JobScheduler实例
3、构建JobInfo实例,指定JobService任务实现类以及执行条件
4、通过Jobscheduler假如到任务队列

(1) 创建JobService子类

public class JobSchedulerService extends JobService {
 
    private JobParameters mJobParameters
 
    @Override
    public boolean onStartJob(JobParameters params) {
        // 返回true,表示该工作耗时,同时工作处理完成后需要调用jobFinished销毁
        mJobParameters = params;
        mTask.execute();
        return true;
    }
 
    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
 
    private AsyncTask<Void, Void, Void> mTask = new AsyncTask<Void, Void, Void>() {
 
        @Override
        protected Void doInBackground(Void... params) {
            // TODO 做一些耗时操作
            return null;
        }
 
        @Override
        protected void onPostExecute(Void result) {
            // TODO 
            Toast.makeText(wenfengService.this, "finish job", 1000).show();
            jobFinished(mJobParameters, true);
            super.onPostExecute(result);
        }
    } 
}

不要忘记注册服务

<service
    android:name=".service.JobSchedulerService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true"
    android:enabled="true"/>

(2)创建JobScheduler对象

JobScheduler jobscheDuler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);

(3)构建JobInfo实例,指定JobService任务实现类以及执行条件

val JOB_ID = 100;
var build:JobInfo.Builder  = JobInfo.Builder(JOB_ID, ComponentName(this,JobSchedulerService::class.java))
//通过build设置条件 (这个条目的意思 必须是充电状态 才会执行这个job)
//build.setRequiresCharging(true)
//必须连接网络 这个任务才会执行
build.setRequiredNetworkType(NETWORK_TYPE_ANY)

//根据约束条件,创建jobInfo对象
var jobInfo = build.build()

(4)开启一个JobScheduler任务:

构建一个JobInfo对象设置预置的条件,然后通过如下所示的代码将它发送到的JobScheduler中。

val result = jobscheDuler.schedule(jobInfo)

在schedule时,会返回一个int类型的值来标记这次任务是否执行成功,如果返回小于0的错误码,这表示该次任务执行失败,反之则成功(成功会返回该任务的id,这里可以使用这个id来判断哪些任务成功了)。所以在返回值小于0的时候就需要我们手动去处理一些事情了。

if(mJobScheduler.schedule(JobInfo job) < 0){
    // do something when schedule goes wrong
}

(5)最后

如果需要停止一个任务,就通过JobScheduler中,cancel(int jobId)来实现(所以之前在Builer中的指定id又有了重要作用);

如果想取消所有的任务,可以调用JobScheduler对象的cancelAll()来实现。


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

推荐阅读更多精彩内容