【Android Service】JobService

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

参考

官方文档

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值