学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为5347字,预计阅读11分钟
前言
调度任务也是最近产品中需要用的,定时与后台进行数据同步,研究了几种方法后,觉得还是JobSchedule相对效果还好点,主要原因是WorkManager的定时任务最短也需要15分钟,虽然JobSchedule在Android7.0后也这样的,但是可以通过别的办法实现,所以两个都说一下,两个也都会用到。
WorkManger
微卡智享
WorkManager 是一个 Android Jetpack 扩展库,它可以让您轻松规划那些可延后、异步但又需要可靠运行的任务。对于绝大部分后台执行任务来说,使用 WorkManager 是目前 Android 平台上的最佳实践。
WorkManager使用起来也非常简单,因为我这边定时任务的频率在1分钟以内,如果不是因为最小间隔是15分钟的原因,就全部使用WorkManager了,直接代码开始。
01
添加依赖项
在新建的工程的build.gradle中加入依赖项
def work_version = "2.5.0"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
02
创建自己的Worker类
新建一个TestWorker继承自Worker,里面只有一个重写的方法dowork()
package dem.vaccae.task
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import android.util.Log
import androidx.work.Data
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.Worker
import androidx.work.WorkerParameters
import java.util.concurrent.TimeUnit
/**
* 作者:Vaccae
* 邮箱:[email protected]
* 创建时间: 10:43
* 功能模块说明:WorkManager测试类
*/
class TestWorker(context: Context, parameters: WorkerParameters) : Worker(context,parameters) {
private var TAG= "taskjob";
private var times =0;
override fun doWork(): Result {
times++;
var data=Data.Builder().putInt("times",times).build();
if(times<10){
Log.i(TAG, "我是Work的测试")
return Result.success()
}else{
Log.i(TAG, "重新测试")
return Result.failure()
}
}
}
从 doWork() 返回的 Result 会通知 WorkManager 服务工作是否成功,以及工作失败时是否应重试工作。
Result.success():工作成功完成。
Result.failure():工作失败。
Result.retry():工作失败,应根据其重试政策在其他时间尝试。
03
创建WorkRequest
可以自定义 WorkRequest 对象来处理常见用例,例如:
调度一次性工作和重复性工作
设置工作约束条件,例如要求连接到 Wi-Fi 网络或正在充电
确保至少延迟一定时间再执行工作
设置重试和退避策略
将输入数据传递给工作
使用标记将相关工作分组在一起
WorkRequest 对象包含 WorkManager 调度和运行工作所需的所有信息。其中包括运行工作必须满足的约束、调度信息(例如延迟或重复间隔)、重试配置,并且可能包含输入数据(如果工作需要)。
WorkRequest 本身是抽象基类。该类有两个派生实现,可用于创建 OneTimeWorkRequest 和 PeriodicWorkRequest 请求。顾名思义,OneTimeWorkRequest 适用于调度非重复性工作,而 PeriodicWorkRequest 则更适合调度以一定间隔重复执行的工作。
以下代码会构建了一个工作请求,该工作请求仅在用户设备正在充电且连接到 Wi-Fi 网络时才会运行:
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build()
val myWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder()
.setConstraints(constraints)
.build()
val myWorkRequest = OneTimeWorkRequestBuilder()
.setInitialDelay(10, TimeUnit.MINUTES)
.build()
我这边主要用的是重复的调度,也不需要别的参数设置,所以直接创建了PeriodicWorkRequest调用,在Activity中代码如下:
//创建WorkManager任务
val periodicwork =
PeriodicWorkRequestBuilder(5000, TimeUnit.MILLISECONDS).build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"test", ExistingPeriodicWorkPolicy.REPLACE,
periodicwork
)
代码中设置了重复间隔的时间为5秒钟,结果运行起来后,5秒是不起作用的,还是间隔的15分钟,效果如下图:
总的来说其实WorkManager还是挺不错的,简单,方便,可以多任务,如果不是对间隔时间要求短,推荐使用WorkManager。
JobSchedule
微卡智享
JobScheduler和JobService是安卓在api 21中增加的接口,用于在某些指定条件下执行后台任务。
JobScheduler是用于计划基于应用进程的多种类型任务的api接口。
对象获取方法:[Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)]
使用JobInfo.Builder.JobInfo.Builder(int, android.content.ComponentName)构造JobInfo对象,并作为参数传给JobSechduler的schedule(JobInfo)方法。
当JobInfo中声明的执行条件满足时,系统会在应用的JobService中启动执行这个任务。
当任务执行时,系统会为你的应用持有WakeLock,所以应用不需要做多余的确保设备唤醒的工作。
JobService继承自Service,是用于处理JobScheduler中规划的异步请求的特殊Service
使用JobService必须先在AndroidManifest.xml中声明service和权限
应用需要实现onStartJob(JobParameters)接口,在其中执行任务逻辑。
这个Service会在一个运行在主线程的Handler中执行规划的任务,所以应用需要在另外的thread/handler/AsyncTask中执行业务逻辑,如果不这么做的话可能会引起主线程的阻塞。
onStopJob(android.app.job.JobParameters)接口是当计划的执行条件“不再”满足时被执行的(例如网络中断)。
01
创建JobService
package dem.vaccae.task.jobschedule
import android.app.job.JobParameters
import android.app.job.JobService
import android.util.Log
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.lang.Exception
/**
* 作者:Vaccae
* 邮箱:[email protected]
* 创建时间: 11:31
* 功能模块说明:
*/
class TestJobService : JobService() {
private var TAG= "taskjob";
suspend fun Count():Flow{
return flow {
for(i in 1..10){
delay(100)
emit(i)
}
}
}
override fun onStartJob(p0: JobParameters?): Boolean {
// 返回true,表示该工作耗时,同时工作处理完成后需要调用onStopJob销毁(jobFinished)
// 返回false,任务运行不需要很长时间,到return时已完成任务处理
val resScope = CoroutineScope(Job())
resScope.launch {
try {
Count().flowOn(Dispatchers.IO).collect {
Log.i(TAG, "collect $it")
}
//任务完成
Log.i(TAG, "jobFinished")
jobFinished(p0,false)
}catch (e:Exception){
e.printStackTrace()
Log.e(TAG, e.message.toString())
}
}
return true
}
override fun onStopJob(p0: JobParameters?): Boolean {
// 有且仅有onStartJob返回值为true时,才会调用onStopJob来销毁job
// 返回false来销毁这个工作
Log.i(TAG, "jobTest is over")
return false
}
}
创建了一个TestJobService 继承自JobService,主要在onStartJob中写入执行的函数,这里用flow写了个循环的输出打印。
02
创建JobScheduler任务 //创建JobSchedule任务
val mJobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val jobid = 10;
var componentName = ComponentName(this, TestJobService::class.java)
val jobinfo = JobInfo.Builder(jobid, componentName)
//设置间隔时间,不断的触发任务的启动,android 7后最少时间是15分钟,所以不用了
// .setPeriodic(3000)
.setMinimumLatency(15000)// 设置任务运行最少延迟时间,与setPeriodic相似,只是间隔时间不确定,不能与setPeriodic一起使用,
.setOverrideDeadline(50000)// 设置deadline,若到期还没有达到规定的条件则会开始执行
.setPersisted(true)//设备重启之后你的任务是否还要继续执行
.build()
在获取执行间隔时,会先比较最小间隔时间和设置的间隔时间,取其中大的那个。所以setPeriodic设置时间小于15分钟是不会生效的。
flexMillis参数是用来设置周期任务执行的活动时间的,这意味着JobScheduler规划的任务不是在精确的时间执行的。并且这个时间也是有最小值的,系统默认5分钟。
setMinimumLatency和setOverrideDeadline不能同setPeriodic一起使用,会引起报错。
上面的这段任务,调用后只会执行一次,因为把最小间隔去掉了,即使设置了也是15分钟的周期,无法实现我想要的效果,接下来就是本篇的重点了,利用JobScheduler自己写了个间隔时间的处理。
package dem.vaccae.task.jobschedule
import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.Context
import android.util.Log
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import java.lang.Exception
import java.util.concurrent.TimeUnit
/**
* 作者:Vaccae
* 邮箱:[email protected]
* 创建时间: 13:44
* 功能模块说明:
*/
class PeriodicJobService: JobService() {
private suspend fun Count(): Flow {
return flow {
for(i in 200..210){
delay(100)
emit(i)
}
}
}
override fun onStartJob(p0: JobParameters?): Boolean {
val resScope = CoroutineScope(Job())
resScope.launch {
try {
Count().flowOn(Dispatchers.IO).collect {
Log.i(TAG, "collect $it")
}
Log.i(TAG, "jobFinished")
}catch (e: Exception){
e.printStackTrace()
Log.e(TAG, e.message.toString())
}
}
startScheduler(this)
return false
}
override fun onStopJob(p0: JobParameters?): Boolean = false
companion object {
var TAG: String = "taskjob"
var JOBID : Int = 100
var InterValTime :Long = 10000
private var jobScheduler: JobScheduler? = null
private var jobInfo: JobInfo? = null
fun startScheduler(context: Context) {
jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
cancelScheduler()
if (jobInfo == null) {
jobInfo = JobInfo.Builder(JOBID, ComponentName(context, PeriodicJobService::class.java))
.setMinimumLatency(InterValTime) // 最小为10秒
.build()
}
val result = jobScheduler?.schedule(jobInfo!!)
}
fun cancelScheduler() {
jobScheduler?.cancel(JOBID)
}
}
}
代码中主要是通过递归的方式,在onStartJob中,利用setMinimumLatency来设置时间间隔,执行完后再重新创建启用任务来实现。写为了静态方法,外部调用也方便。
外部调用直接一句
//启动周期性任务
PeriodicJobService.startScheduler(this)
接下来看看实现的效果:
上面设置了为3秒钟,可以看到,每隔三秒都会输出,说明当前的间隔任务已经实现。
JobScheduler和WorkManager都只能在APP存活的时候执行,但是定时器是一直工作的。
关闭APP再启动,JobScheduler并不能够直接继续运行,但是WorkManager可以。
如果重启APP的时候,WorkManager任务的计时器应该已经执行了一次或多次,则会立即开始执行。
重启App之后WorkManager如果直接执行了一个任务,则从这个时候开始算新的周期,不会按旧有周期走。
完
扫描二维码
获取更多精彩
微卡智享
「 往期文章 」
Android使用LiveEventBus消息实现组件间通讯
Android制作AAR包并混淆后加载调用
Android使用Flow检测版本升级自动下载安装