目录

Android秘技之JobService的使用详解

http://image.catbro.cn/c5f6c2951b8fd.png


  • JobService是Android L时候官方加入的组件。适用于需要特定条件下才执行后台任务的场景。 由系统统一管理和调度,在特定场景下使用JobService更加灵活和省心,相当于是Service的加强或者优化。

JobService 与Service的对比

对比角度 Service JobService 补充
实现原理 由APP侧发出请求,ActivityManagerService接收请求后进行调度,通知APP侧进行创建,开始(绑定),停止(解绑)和销毁Service。 由APP侧发出请求,JobSchedulerService接收请求后,通过ActivityManagerService去调度JobService的创建,绑定和解绑。并由JobSchedulerService自己进行JobService的开始,取消和停止等操作。 从原理上看,JobService的开始,取消和停止是由JobSchedulerService维护的,而不是由ActivityManagerService维护的。这是他们在实现原理上的明显区别。即JobService是由系统负责调用和维护
启动条件 Service的启动并没有什么特定的条件设置。如果说非要有什么具体的执行条件的话,就是APP侧自己根据业务逻辑在适当的时候调用startService()或者bindService()。 JobService的执行需要至少一个条件。没有条件的JobService是无法启动的,在创建JobInfo的时候会抛出异常。
运行时间 onStartCommand()的回调在UI线程,不可执行耗时逻辑,否则可能造成ANR。 onStartJob()的回调在UI线程,不可执行耗时逻辑,否则可能造成ANR或者Job被强制销毁(超过8s)。并且,JobService里即便新起了线程,处理的时间也不能超过10min,否则Job将被强制销毁。
启动角度 onStartCommand()里返回START_STICKY可以告诉AMS在被停止后自动启动。 onStopJob()里返回true,即可在被强制停止后再度启动起来。
扩展性 APP侧可以通过Binder创建远程Service进行IPC。 JobService的绑定实际上是由JobSchedulerService自己去做的。绑定后产生的Binder用于和JobSchedulerService进行IPC,APP侧无法通过JobService扩展去实现别的IPC功能。 Google本来的初衷也不是让JobService实现远程Service的功能。
实际应用上 适合需要常驻后台,立即执行,进行数据获取,功能维持的场景。比如 音乐播放,定位,邮件收发等。 适合不需要常驻后台,不需要立即执行,在某种条件下触发,执行简单任务的场景。比如 联系人信息变化后的快捷方式的更新,定期的更新电话程序的联系人信息,壁纸更改后去从壁纸提取颜色的后台任务。

简单来讲,Service适合一些优先级较高,执行任务复杂耗时的任务。JobService适合轻量级的灵活的任务。

JobService API

方法名 参数 描述 补充
onStartJob(JobParameters params) params:包含用于配置/识别作业的参数,系统传递给我们的 定义:Job开始的时候的回调,实现实际的工作逻辑。执行该方法时需要返回一个Boolean值,True表示需要执行,返回True时,作业将保持活动状态,直到系统调用jobFinished或者直到该作业所需的条件不再满了 注意:如果返回false的话,系统会自动结束本job;只要Job工作正在执行,系统就会代表应用程序保留一个唤醒锁。 在调用此方法之前获取此唤醒锁,并且直到您调用jobFinished(JobParameters,boolean)或系统调用onStopJob(JobParameters)以通知正在执行的作业它过早关闭之后才会释放。
jobFinished(JobParameters params, boolean wantsReschedule) wantsReschedule:若希望系统再次执行该Job,则设置为true后返回 调用此方法通知JobScheduler该作业已完成其工作。当系统收到此消息时,它会释放为该作业保留的唤醒锁。该操作在Job的任务执行完毕后,APP端自己的调用通知JobScheduler已经完成了任务。 注意:该方法执行完后不会回调onStopJob(),但是会回调onDestroy()
onStopJob(JobParameters params) 同上 定义:停止该Job。当JobScheduler发觉该Job条件不满足的时候,或者job被抢占被取消的时候的强制回调。即如果系统确定你必须在有机会调用jobFinished(JobParameters,boolean)之前必须停止执行作业,则调用此方法。 注意:如果想在这种意外的情况下让Job重新开始,返回值应该设置为true。
onCreate() 父类Service的基础方法,可以覆写来实现一些辅助作用。Service被初始化后的回调。 作用:可以在这里设置BroadcastReceiver或者ContentObserver
onDestroy() 定义:Service被销毁前的回调。 作用:可以在这里注销BroadcastReceiver或者ContentObserver

上面可以看出,JobService只是实际的执行和停止任务的回调入口。 那如何将这个入口告诉系统,就需要用到JobScheduler了。

JobScheduler API。

方法名 描述 补充
schedule() 定义:安排一个Job任务。
enqueue() 定义:安排一个Job任务,但是可以将一个任务排入队列。
cancel() 定义:取消一个执行ID的Job。
cancelAll() 定义:取消该app所有的注册到JobScheduler里的任务。
getAllPendingJobs() 定义:获取该app所有的注册到JobScheduler里未完成的任务列表。
getPendingJob() 定义:按照ID检索获得JobScheduler里未完成的该任务的JobInfo信息。

上面还提到需要创建JobInfo对象,实际要通过JobInfo.Builder类利用建造者模式创建出JobInfo对象。

JobInfo.Builder API

方法名 参数 描述
Builder(int jobId,
ComponentName jobService)
jobId:用于唯一标识该JobService,管理其的启动和停止;
ComponentName:封装要启动的JobService的信息
初始化一个新的构建器以在JobInfo中构造;
注意:参数之一ID必须是APP UID内唯一的,如果APP和别的APP共用了UID,那么要防止该ID和别的APP里有冲突。
addTriggerContentUri
(JobInfo.TriggerContentUri uri)
uri:指向监控的 ContentObserver的uri 添加需要被监控的ContentObserve,当内容发生变化将导致作业执行
setBackoffCriteria(
long initialBackoffMillis, int backoffPolicy)
initialBackoffMillis:作业失败时最初等待的毫秒时间间隔 ;
backoffPolicy:BACKOFF_POLICY_LINEAR or BACKOFF_POLICY_EXPONENTIAL.
设置后退/重试策略。
setClipData(ClipData clip, int grantFlags) ClipData:要设置的新的剪切板数据。可以为null以清除当前数据
grantFlags:权限flags,为任何URI授予的所需权限。这应该是Intent.FLAG_GRANT_READ_URI_PERMISSION,Intent.FLAG_GRANT_WRITE_URI_PERMISSION和Intent.FLAG_GRANT_PREFIX_URI_PERMISSION的组合。
设置与此作业关联的剪切板数据ClipData。
setEstimatedNetworkBytes(
long downloadBytes, long uploadBytes)
downloadBytes:此作业将下载的网络流量的估计大小(以字节为单位)。 值是非负数字节。
uploadBytes:此作业将上传的网络流量的估计大小(以字节为单位)。 值是非负数字节。
设置此作业将执行的网络流量的估计大小(以字节为单位)
setExtras(PersistableBundle extras) PersistableBundle:包含希望调度程序为保留的额外内容的Bundle。 该值绝不能为空。 设置可选的附加功能
setImportantWhileForeground(
boolean importantWhileForeground)
boolean:当应用程序在前台时,是否放松对此作业的打盹限制。默认为False。 将此设置为true表示此作业非常重要,而调度应用程序位于前台或用于后台限制临时白名单中;
这意味着在此期间系统将放松对此工作的打瞌睡限制。 应用程序应仅将此标志用于应用程序在前台正常运行所必需的短作业。 请注意,一旦调度应用程序不再从后台限制和后台列入白名单,或者由于约束不满意而导致作业失败,则应该预期此作业的行为与没有此标志的其他作业相同。
setMinimumLatency(long minLatencyMillis) long:不会考虑执行此作业的毫秒数。 指定此作业应延迟提供的时间量。因为在定期作业上设置此属性没有意义,所以这样做会在调用build()时抛出IllegalArgumentException。
setOverrideDeadline(long maxExecutionDelayMillis) 设置截止日期,即最大调度延迟。 即使没有满足其他要求,该工作也将在此截止日期前完成。 因为在定期作业上设置此属性没有意义,所以这样做会在调用build()时抛出IllegalArgumentException。
注意:即便其他条件没满足此期限到了也要立即执行
setPeriodic(long intervalMillis) long:此作业将重复的毫秒间隔。 指定此作业应以提供的间隔重复,每个周期不超过一次。 您无法控制何时在此时间间隔内执行此作业,只能保证在此时间间隔内最多执行一次。 使用setMinimumLatency(long)或setOverrideDeadline(long)在构建器上设置此函数将导致错误。
setPeriodic(long intervalMillis, long flexMillis) intervalMillis:此作业将重复的毫秒间隔。强制执行JobInfo.getMinPeriodMillis()的最小值。
flexMillis:这项工作的毫秒级。 Flex被限制为至少为JobInfo.getMinFlexMillis()或期间的5%,以较高者为准。
指定此作业应使用提供的间隔和弹性重复。作业可以在句点结束时的弹性长度窗口中随时执行
setPersisted(boolean isPersisted) 设置是否在设备重新启动后保留此作业。 需要RECEIVE_BOOT_COMPLETED权限。
setPrefetch(boolean prefetch) 将此设置为true表示此作业旨在预取内容,从而对此设备的特定用户的体验进行重大改进。例如,获取当前用户感兴趣的顶部标题
系统可以使用此信号来放宽您最初请求的网络约束,例如,当有可用的计量数据过剩时,允许JobInfo.NETWORK_TYPE_UNMETERED作业在计量网络上运行。 系统还可以将此信号与最终用户使用模式结合使用,以确保在用户启动应用程序之前预取数据。
setRequiredNetwork(NetworkRequest networkRequest) NetworkRequest:此作业所需网络类型的详细说明,如果不需要特定类型的网络,则为null。仅对未持久的作业支持定义NetworkSpecifier。 设置您的工作所需的网络类型的详细描述
如果您的作业不需要网络连接,则无需调用此方法,因为默认值为null。
调用此方法将网络定义为对您的工作的严格要求。 如果请求的网络不可用,您的工作将永远不会运行。 请参阅setOverrideDeadline(long)以更改此行为。 调用此方法将覆盖先前由setRequiredNetworkType(int)定义的任何要求; 您通常只想调用其中一种方法。
setRequiredNetworkType(int networkType) Value is NETWORK_TYPE_NONE, NETWORK_TYPE_ANY, NETWORK_TYPE_UNMETERED, NETWORK_TYPE_NOT_ROAMING or NETWORK_TYPE_METERED. 设置您的工作所需的网络类型的基本描述。 如果您需要更精确地控制网络功能,请参阅setRequiredNetwork(NetworkRequest)。 如果您的作业不需要网络连接,则无需调用此方法,因为默认值为JobInfo.NETWORK_TYPE_NONE。 调用此方法将网络定义为对您的工作的严格要求。 如果请求的网络不可用,您的工作将永远不会运行。 请参阅setOverrideDeadline(long)以更改此行为。 调用此方法将覆盖先前由setRequiredNetwork(NetworkRequest)定义的任何要求; 您通常只想调用其中一种方法。
setRequiresBatteryNotLow(boolean batteryNotLow) batteryNotLow 布尔值:设备的电池电量是否一定不能低 指定要运行此作业,设备的电池电量不能低。默认为false。如果为true,则作业将仅在电池电量不低时运行,这通常是用户被给予“低电量”警告的点。
setRequiresCharging(boolean requiresCharging) requiresCharging boolean:传递true以要求设备正在充电以运行作业。 指定要运行此作业,设备必须正在充电(或者是连接到永久电源的非电池供电设备,例如Android TV设备)。默认为false。
setRequiresDeviceIdle(boolean requiresDeviceIdle) boolean:传递true以防止在交互使用设备时运行作业。 设置为true时,请确保在设备处于活动状态时此作业不会运行。 默认状态为false:即,即使有人正在与设备交互,也可以使作业运行。 该状态是系统提供的松散定义。 通常,这意味着该设备当前未以交互方式使用,并且一段时间未使用。 因此,现在是执行资源繁重工作的好时机。 请记住,电池使用量仍将归功于你的应用程序,并在电池状态下显示给用户。
setRequiresStorageNotLow(boolean storageNotLow) boolean:设备的可用存储空间是否一定不能低。 指定要运行此作业,设备的可用存储空间不能低。 默认为false。 如果为true,则仅在设备未处于低存储状态时运行作业,这通常是用户被给予“低存储”警告的点。
setTransientExtras(Bundle extras) Bundle:捆绑包含希望调度程序保留的附加内容。 该值绝不能为空。 设置可选的瞬态附加功能,因为设置此属性与持久作业不兼容,所以这样做会在调用build()时抛出IllegalArgumentException。
setTriggerContentMaxDelay(long durationMs) long:初始内容更改后的延迟,以毫秒为单位。 设置从第一次检测到内容更改到安排作业时允许的最大总延迟(以毫秒为单位)
setTriggerContentUpdateDelay(long durationMs) long:最近一次内容更改后的延迟,以毫秒为单位。 设置从检测到内容更改到安排作业的延迟(以毫秒为单位)。如果在此期间有更多更改,则延迟将重置为在最近更改时开始

JobService 的使用

  • JobService类的继承关系。

      public abstract class JobService 
    
      extends Service
    
  • 可以看到JobService是继承自Service的抽象类。JobService的本质还是Service,只不过封装了些额外的方法和逻辑。

  • 首先我们来看下官方的对于JobService的解释。 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー Entry point for the callback from the JobScheduler.

      This is the base class that handles asynchronous requests that were previously scheduled.
      You are responsible for overriding onStartJob(JobParameters), which is where you will implement your job logic.
    
      This service executes each incoming job on a Handler running on your application's main thread.
      This means that you must offload your execution logic to another thread/handler/AsyncTask 
      of your choosing.
      ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
    

大概意思如下:

  • JobService的启动的入口点是由JobScheduler的回调进行控制
  • 这是处理先前调度的异步请求的基类。我们可以通过覆盖onStartJob(JobParameters)实现作业逻辑。
  • 该服务在我们的应用程序主线程上运行的Handler上执行每个传入作业,换句话也就是说JobService的执行实在APP的主线程里面响应的,所以我们需要提供额外的异步逻辑去执行任务。

启动流程

  • http://image.catbro.cn/0d0a837a6e574.png

使用案例

1、创建JobInfo

  • 我们需要在APP中创建一个JobInfo去描述我们需要启动一个具有什么特点的调度服务任务

2、创建JobService实现类及申请对应权限

  • 代码如下:

     class MyJobService: JobService() {
        companion object {
              val MYJOBSERVICE_JOB_ID = 0 //最为该jobservice的id标识
              val MYJOBSERVICE_JOB_OVERDIDE_DEADLINE = 1000 //延迟多少秒执行
              private val TAG = "MyJobService"
    
          }
    
          override fun onStopJob(params: JobParameters?): Boolean {
              Log.i(TAG, "onStopJob start")
              return true
          }
    
          override fun onStartJob(params: JobParameters?): Boolean {
              Log.i(TAG, "onStartJob start")
              return true
          }
      }
    
  • 在AndroidManifest.xml文件中申请JobService运行所需的android.permission.BIND_JOB_SERVICE权限,JobService的调度必须被权限所保护

     <service android:name="MyJobService"
            android:permission="android.permission.BIND_JOB_SERVICE" >
       ...
     </service>
    
  • 如果JobService未申请权限或者申请的权限未被授予,系统将忽略该jobService的调度请求

3、创建JobInfo

  • JobInfo为建造器模式,通过JobInfo.Builder进行创建

          val myJobServiceComponentName  = ComponentName(this, MyJobService::class.java)
          val jobBuilder = JobInfo.Builder(MyJobService.MYJOBSERVICE_JOB_ID, myJobServiceComponentName)
          jobBuilder.setPeriodic(5) //每隔5秒执行一次
          val myJob = jobBuilder.build()
    

4、创建JobScheduler

  • JobScheduler负责对JobService的调度

      val scheduler = this.getSystemService(JobScheduler::class.java)
      scheduler.schedule(myJob)
    
  • 完整代码如下:

  • MainActivity class MainActivity : AppCompatActivity() {

          @RequiresApi(Build.VERSION_CODES.M)
          @TargetApi(Build.VERSION_CODES.LOLLIPOP)
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_main)
              val myJobServiceComponentName  = ComponentName(this, MyJobService::class.java)
    
              val jobBuilder = JobInfo.Builder(MyJobService.MYJOBSERVICE_JOB_ID, myJobServiceComponentName)
              jobBuilder.setPeriodic(1000*60*15)
              val myJob = jobBuilder.build()
    
              val scheduler = this.getSystemService(JobScheduler::class.java)
    
              scheduler.schedule(myJob)
    
          }
      }
    
  • MyJobService @TargetApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP) class MyJobService: JobService() { companion object { val MYJOBSERVICE_JOB_ID = 0 //最为该jobservice的id标识 private val TAG = “MyJobService”

          }
    
          override fun onStopJob(params: JobParameters?): Boolean {
              Log.i(TAG, "onStopJob start")
              return true
          }
    
          override fun onStartJob(params: JobParameters?): Boolean {
              Log.i(TAG, "onStartJob start")
              this.jobFinished(params, true)
    
              return true
          }
      }