JobService这个东西或许我们没有接触过,但是他的出现解决了特定条件下需执行后台任务的场景。由于受到系统的统一管理和调度JobService在一些优化中特别是电量优化中常被采用。
一、JobService出现的背景
假如有一个需求是这样的,TV上有个 AppA 需要定时去服务端拿数据,然后把拿到的数据推送到TV Home 页的频道上。
看到这个需求我们心里就有解决方案了:搞个Service,然后Service内写个定时器即可解决。
此时需求增加了,产品说我想只给电量大于20%的用户推送消息(硬编的故事,忍着看,毕竟TV不用担心电😁)
看到这我们心里又有解决方案了,这能搞,注册广播监听电量变化,把这块逻辑结合Service完事。
可是需求又加了 😭,推送消息时延迟10分钟再推。
没办法,再加个倒计时的逻辑呗,,,,,
哈哈哈,随着需求的增多简单的Service相关的业务就耦合成了一大坨。不仅有普通的业务,还要结合其他的组件如广播,这些都要我们自己创建管理甚是麻烦,JobService的出现就是解决上述场景的。
JobService用于执行一些需要满足特定条件但不紧急的后台任务,利用 JobScheduler 来执行这些特殊的后台任务来减少电量的消耗。开发者可以设定需要执行的任务JobService,以及任务执行的条件 JobInfo,JobScheduler 会将任务加入到队列。在特定的条件满足时 Android 系统会去批量的执行所有应用的这些任务,而非对每个应用的每个任务单独处理。这样可以减少设备被唤醒的次数。
二、栗子 🌰
1、简单使用
大致的使用流程很简单
1、自定义个Service继承JobService
2、清单文件注册Service并且给Service添加android:permission="android.permission.BIND_JOB_SERVICE"权限
3、通过JobSchedule开启服务
(1)首先定义个PushJobService
private const val TAG = "PushJobService"
class PushJobService : JobService() {
override fun onCreate() {
Log.d(TAG, "onCreate")
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand" + super.onStartCommand(intent, flags, startId))
return super.onStartCommand(intent, flags, startId)
}
override fun onStopJob(params: JobParameters?): Boolean {
Log.d(TAG, "onStopJob")
return false
}
override fun onStartJob(params: JobParameters?): Boolean {
Log.d(TAG, "onStartJob")
pushData()
return false
}
override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "onUnbind")
return super.onUnbind(intent)
}
override fun onDestroy() {
Log.d(TAG, "onDestroy")
super.onDestroy()
}
private fun pushData() {
Log.d(TAG, "服务器获取数据->")
Log.d(TAG, "服务器获取数据成功->")
Log.d(TAG, "解析数据成功->")
Log.d(TAG, "推送数据->")
Log.d(TAG, "推送数据完成->")
}
}
(2)清单文件注册Service并添加权限
<service
android:name=".PushJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
(3)开启job
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
start.setOnClickListener {
startASimpleJob()
}
}
/**
* 开启一个简单的Job
* 1、首先通过getSystemService来获取系统服务。获取JobScheduler对象。
* 2、构建个JobInfo对象
* 3、通过jobScheduler的schedule方法开启一个job任务。
* ps:
* (1)JobInfo的构建通过Builder模式创建,其中JobInfo还可以控制job触发的条件。
* (2)为啥注册Service是需要添加一个权限呢?因为JobService的绑定、解绑交给了系统控制。不用我们自己管理了。
* */
private fun startASimpleJob(){
val jobScheduler = getSystemService(JobScheduler::class.java)
val componentName = ComponentName(this, PushJobService::class.java)
val builder = JobInfo.Builder(0, componentName)
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//特定条件限制:任意网络
builder.setMinimumLatency((1000 * 5).toLong())//特定条件限制:延迟20秒后开启
jobScheduler.schedule(builder.build())
Log.d("MainActivity", "schedulePushWork...")
}
}
D/MainActivity: schedulePushWork...
D/PushJobService: onCreate
D/PushJobService: onStartJob
D/PushJobService: 服务器获取数据->
D/PushJobService: 服务器获取数据成功->
D/PushJobService: 解析数据成功->
D/PushJobService: 推送数据->
D/PushJobService: 推送数据完成->
D/PushJobService: onUnbind
D/PushJobService: onDestroy
可以看到JobService生命周期,任务开启到结束:onCreate -> onStartJob -> onUnbind -> onDestroy
你或许会疑问,为啥onStartCommand没有回调?Job结束了onStopJob 不回调?
-
为啥onStartCommand没有回调? 通过观看JobService源码你会发现其内部是通过bind方式开启的服务,所以不走onStartCommand,并且绑定服务由系统控制。
-
Job结束了onStopJob 没有回调? 因为这属于系统设计。这个方法是当service的条件不满足时系统回调,例如JobInfo设置需要网络此时用户为断网状态,这时就会回调onStopJob。
JobId的作用?
上面在创建一个Job时会传一个id值,其实这个值是用来标识一条job任务的。此时我们应该注意了当一个job正在被系统管理时我们无法再开启一条相同id的job。不妨看下如下栗子:
a、连续开启三次同一个id的job
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 按钮连续点击三次
start.setOnClickListener {
startASimpleJob()
}
}
private fun startASimpleJob(){
val jobScheduler = getSystemService(JobScheduler::class.java)
val componentName = ComponentName(this, PushJobService::class.java)
val builder = JobInfo.Builder(0, componentName)
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
builder.setMinimumLatency((1000 * 5).toLong())//5s后开启job
jobScheduler.schedule(builder.build())
Log.d("MainActivity", "schedulePushWork...")
}
}
D/MainActivity: schedulePushWork...
D/MainActivity: schedulePushWork...
D/MainActivity: schedulePushWork...
D/PushJobService: onCreate
D/PushJobService: onStartJob
D/PushJobService: 服务器获取数据->
D/PushJobService: 服务器获取数据成功->
D/PushJobService: 解析数据成功->
D/PushJobService: 推送数据->
D/PushJobService: 推送数据完成->
D/PushJobService: onUnbind
D/PushJobService: onDestroy
发现按钮触发了三次,Job只成功开启一次。
b、点击一次按钮静待观察结,然后重复这个步骤1次
发现使用过后的id还可以循环使用,猜测系统使用过这个id后就释放了,下次我们还可使用。
c、连续开启三次,设置三个不同的JobId
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var jobId = 0
start.setOnClickListener {
startASimpleJob(++jobId)
}
}
private fun startASimpleJob(jobId: Int) {
val jobScheduler = getSystemService(JobScheduler::class.java)
val componentName = ComponentName(this, PushJobService::class.java)
val builder = JobInfo.Builder(jobId, componentName)
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
builder.setMinimumLatency((1000 * 5).toLong())
jobScheduler.schedule(builder.build())
Log.d("MainActivity", "schedulePushWork...")
}
}
override fun onStartJob(params: JobParameters?): Boolean {
// 打印jobId
Log.d(TAG, "onStartJob:${params?.jobId}")
pushData()
return false
}
D/MainActivity: schedulePushWork...
D/MainActivity: schedulePushWork...
D/PushJobService: onCreate
D/PushJobService: onStartJob:1
D/PushJobService: 服务器获取数据->
D/PushJobService: 服务器获取数据成功->
D/PushJobService: 解析数据成功->
D/PushJobService: 推送数据->
D/PushJobService: 推送数据完成->
D/PushJobService: onUnbind
D/PushJobService: onDestroy
D/PushJobService: onCreate
D/PushJobService: onStartJob:2
D/PushJobService: 服务器获取数据->
D/PushJobService: 服务器获取数据成功->
D/PushJobService: 解析数据成功->
D/PushJobService: 推送数据->
D/PushJobService: 推送数据完成->
D/PushJobService: onUnbind
D/PushJobService: onDestroy
发现如预期执行了两次,且每次的jobId能够印证这是两个不同的job
2、常见的计时逻辑
有时候我们或许需要每隔xxx分钟去做一次任务,此时我们就能这样做:
(1)首先写个ScheduleHelper工具类
private const val TAG = "ScheduleHelper"
/**
* 开启job任务
* 1、jobId每次累加
* 2、延迟5s执行
* */
fun schedulePushWork(context: Context, currentId: Int) {
val jobScheduler = context.getSystemService(JobScheduler::class.java)
val componentName = ComponentName(context, PushJobService::class.java)
val builder = JobInfo.Builder(generateNextJobId(jobScheduler, currentId), componentName)
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
builder.setMinimumLatency((1000 * 5).toLong())
jobScheduler.schedule(builder.build())
Log.d(TAG, "schedulePushWork...")
}
/**
* 自动生成一个JobId.
* ps:JobScheduler#getPendingJob(jobId)可从当前系统管理的JobService中查询到指定的jobInfo相关信息。
* jobInfo为空则代表系统管理中无对应id的job
* */
private fun generateNextJobId(jobScheduler: JobScheduler, currentId: Int): Int {
// 若是当前的id是Int的最大值了我们给id归零,再次从零开始。否则newId=currentId+1
var tempId: Int = if (currentId == Int.MAX_VALUE) {
0
} else {
currentId + 1
}
//去系统查询下当前的id是否正在使用,若是正在使用那newId=currentId+1
while (jobScheduler.getPendingJob(currentId) != null) {
tempId++
if (tempId == Int.MAX_VALUE) {
tempId = 0
}
}
return tempId
}
(2) PushJobService
private const val TAG = "PushJobService"
class PushJobService : JobService() {
override fun onCreate() {
Log.d(TAG, "onCreate")
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand" + super.onStartCommand(intent, flags, startId))
return super.onStartCommand(intent, flags, startId)
}
override fun onStopJob(params: JobParameters?): Boolean {
Log.d(TAG, "onStopJob")
return false
}
override fun onStartJob(params: JobParameters?): Boolean {
Log.d(TAG, "onStartJob:${params?.jobId}")
pushData()
schedulePushWork(applicationContext, params?.jobId?:0)
return false
}
override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "onUnbind")
return super.onUnbind(intent)
}
override fun onDestroy() {
Log.d(TAG, "onDestroy")
super.onDestroy()
}
private fun pushData() {
Log.d(TAG, "服务器获取数据->")
Log.d(TAG, "服务器获取数据成功->")
Log.d(TAG, "解析数据成功->")
Log.d(TAG, "推送数据->")
Log.d(TAG, "推送数据完成->")
}
}
(3)开启job
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
start.setOnClickListener {
schedulePushWork(applicationContext,0)
}
}
}
三、重要Api及其方法
1、方法
/**
* JobParameters 用于获得一些Job的参数。例如jobId
*
* 返回值true表示你的job为活跃状态。表明你的工作需要继续执行。一般为false。
*
* 1、你可以主动调用jobFinished 去告诉系统job工作已经完成。结束当前job。
* 2、job需要的约束条件不在满足时也会结束当前job。例如用户使用JobInfo.Builder为job添加了
* setRequiresCharging(boolean)电量约束。当用户吧设备电源关闭时,系统会立刻停止改job,
* 该job的onStopJob方法会被回调。
* <p>
* 3、只要你的job正在执行时,系统就会持有你app的唤醒锁。(此方法调用之前,系统就会获得唤醒锁)
* 在您主动调用obFinished或者系统调用onStopJob后这把锁才被释放。
*
* 4、返回false代表您写在此方法体中的工作已经完成,系统将释放该job的锁。系统不会去调用onStopJob
*
* job执行时调用此方法。这个方法运行在app的主线程中。你要重写这个方法,做一些自己的逻辑工作。
*/
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.i(TAG, "onStartJob: ");
return false;
}
/**
* 1、当系统确定停止job时会调用此方法,如果不满足build设置的相关要求时会触发此方法。
*
* 例如:你设置了setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY),执行任务期间你切换了wifi。
*
* 2、这个方法回调时,系统将释放app的唤醒锁。
*
* 返回值true表明告诉JobManager你要基于创建工作时的重试条件重新schedule这个job。false表明彻底结束了
* 这个job。
*
* 3、无论返回值如何都表明当前的job执行完毕啦。
*/
@Override
public boolean onStopJob(JobParameters jobParameters) {
Log.i(TAG, "onStopJob: ");
return false;
}
2、JobInfo.Build 的重要API
API | 简介 |
---|---|
setRequiredNetworkType | 网络要求(例子JobInfo.NETWORK_TYPE_ANY表名需要有网) |
setRequiresBatteryNotLow(true) | 运行此job时,设备电量不能低 |
setRequiresCharging(true) | 默认值为false。true表示此job需要在“充电”状态下工作。这里的充电可以理解为充电玩手机时,电量是增的。(假如你使用usb插在电脑上边充电边看视屏,电量可能冲着还减着) |
setRequiresDeviceIdle(true) | 设置设备在空闲的时候执行job,此条件一般很难达到。 |
setRequiresBatteryNotLow(true) | 运行此job需要设备的可用存储空间不能太低。 |
builder.setPeriodic(5*1000) | 设置的时间段内执行,执行不会超过一次。你不能控制执行的时间,仅仅能确定的是在设定的期间内job会执行一次。(安卓7.0开始这个值最小要设置15min否则15min内不起作用) |
builder.setMinimumLatency(1000 * 5) | 设置延迟的时间,在延迟时间到达之前不会考虑执行job(与setPeriodic一起使用会报异常) |
builder.setOverrideDeadline(1000L*10) | 设置执行的最后期限,在最后期限到达之前会执行此job(与setPeriodic一起使用会报异常) |
3、JobSchedule常用API
API | 简介 |
---|---|
scheduler.schedule(jobinfo) | 开启jobservice |
cancel(jobid) | 结束指定id的service |
cancelAll() | 结束所有的service |
JobInfo jobinfo = scheduler.getPendingJob(jobid) | 根据 jobid 获取jobInfo对象 |
List《JobInfo》allPendingJobs = scheduler.getAllPendingJobs() | 获得所有的jobInfo |
4、JobService自启动方案
1、onStopJob返回值为true.(对于调用cancel的job无效)
2、JobService的生命周期方法中再次schedule下jobservice(推荐做法)
The end
参考
官方文档。