Android之任务调度WorkManager和JobSchedule的使用

学更好的别人,

做更好的自己。

——《微卡智享》

5afafc6063b80ea67ece874506319eb8.png

本文长度为5347,预计阅读11分钟

前言

调度任务也是最近产品中需要用的,定时与后台进行数据同步,研究了几种方法后,觉得还是JobSchedule相对效果还好点,主要原因是WorkManager的定时任务最短也需要15分钟,虽然JobSchedule在Android7.0后也这样的,但是可以通过别的办法实现,所以两个都说一下,两个也都会用到。

f952fa306d4b66f28e04892df8d482c3.png

WorkManger

c93c069ba0bdcfcbfaae4c10d8cefc67.png

微卡智享

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
 * 邮箱:3657447@qq.com
 * 创建时间: 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 则更适合调度以一定间隔重复执行的工作。

3948cf8ef9cf74aafe562bcd08d23c68.png

以下代码会构建了一个工作请求,该工作请求仅在用户设备正在充电且连接到 Wi-Fi 网络时才会运行:

val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()


val myWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()


72de4c7b305f76225e0e80e6a285198d.png

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

我这边主要用的是重复的调度,也不需要别的参数设置,所以直接创建了PeriodicWorkRequest调用,在Activity中代码如下:

        //创建WorkManager任务
        val periodicwork =
            PeriodicWorkRequestBuilder<TestWorker>(5000, TimeUnit.MILLISECONDS).build()
        WorkManager.getInstance(this).enqueueUniquePeriodicWork(
            "test", ExistingPeriodicWorkPolicy.REPLACE,
            periodicwork
        )


代码中设置了重复间隔的时间为5秒钟,结果运行起来后,5秒是不起作用的,还是间隔的15分钟,效果如下图:

5544c8a75ff735382a946efa8ab2c004.png

总的来说其实WorkManager还是挺不错的,简单,方便,可以多任务,如果不是对间隔时间要求短,推荐使用WorkManager。

JobSchedule

39bca585c7c3068013558ae4fb336630.png

微卡智享

JobScheduler和JobService是安卓在api 21中增加的接口,用于在某些指定条件下执行后台任务。

JobScheduler

JobScheduler是用于计划基于应用进程的多种类型任务的api接口。

  • 对象获取方法:[Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)]

  • 使用JobInfo.Builder.JobInfo.Builder(int, android.content.ComponentName)构造JobInfo对象,并作为参数传给JobSechduler的schedule(JobInfo)方法。

  • 当JobInfo中声明的执行条件满足时,系统会在应用的JobService中启动执行这个任务。
    当任务执行时,系统会为你的应用持有WakeLock,所以应用不需要做多余的确保设备唤醒的工作。

JobService

JobService继承自Service,是用于处理JobScheduler中规划的异步请求的特殊Service

  • 使用JobService必须先在AndroidManifest.xml中声明service和权限
    <service android:name="MyJobService" android:permission="android.permission.BIND_JOB_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
 * 邮箱:3657447@qq.com
 * 创建时间: 11:31
 * 功能模块说明:
 */
class TestJobService : JobService() {


    private var TAG= "taskjob";


    suspend fun Count():Flow<Int>{
        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的最小执行间隔,从Android7.0后,这个设置最少也是15分钟了,就是你设置的再短也是按15分钟执行。

  • 在获取执行间隔时,会先比较最小间隔时间和设置的间隔时间,取其中大的那个。所以setPeriodic设置时间小于15分钟是不会生效的。

  • flexMillis参数是用来设置周期任务执行的活动时间的,这意味着JobScheduler规划的任务不是在精确的时间执行的。并且这个时间也是有最小值的,系统默认5分钟。

  • setMinimumLatency和setOverrideDeadline不能同setPeriodic一起使用,会引起报错。

上面的这段任务,调用后只会执行一次,因为把最小间隔去掉了,即使设置了也是15分钟的周期,无法实现我想要的效果,接下来就是本篇的重点了,利用JobScheduler自己写了个间隔时间的处理。

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
 * 邮箱:3657447@qq.com
 * 创建时间: 13:44
 * 功能模块说明:
 */
class PeriodicJobService: JobService() {


    private suspend fun Count(): Flow<Int> {
        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)

接下来看看实现的效果:

fd162c65add5a99735853ad79c878844.gif

上面设置了为3秒钟,可以看到,每隔三秒都会输出,说明当前的间隔任务已经实现。‍

需要提醒

  • JobScheduler和WorkManager都只能在APP存活的时候执行,但是定时器是一直工作的。

  • 关闭APP再启动,JobScheduler并不能够直接继续运行,但是WorkManager可以。

  • 如果重启APP的时候,WorkManager任务的计时器应该已经执行了一次或多次,则会立即开始执行。

  • 重启App之后WorkManager如果直接执行了一个任务,则从这个时候开始算新的周期,不会按旧有周期走。

91a206dd7e17388ca19d635077d6a2ac.png

扫描二维码

获取更多精彩

微卡智享

06b4551c6365072c923dba5a146e8c33.png

「 往期文章 」

Android使用LiveEventBus消息实现组件间通讯

Android制作AAR包并混淆后加载调用

Android使用Flow检测版本升级自动下载安装

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
可以使用SQL Server Agent来定时执行存储过程。以下是一个示例脚本: ```sql USE [msdb] GO DECLARE @jobId BINARY(16) EXEC msdb.dbo.sp_add_job @job_name=N'MyJobName', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'This job executes my stored procedure', @category_name=N'[Uncategorized (Local)]', @owner_login_name=N'sa', @job_id = @jobId OUTPUT EXEC msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Execute SP', @step_id=1, @cmdexec_success_code=0, @on_success_action=1, @on_fail_action=2, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'EXEC dbo.MyStoredProcedure', @database_name=N'MyDatabase', @output_file_name=N'\\MyServer\MyShare\MyJobOutput.txt', @flags=0 EXEC msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'MyJobSchedule', @enabled=1, @freq_type=4, @freq_interval=1, @freq_subday_type=1, @freq_subday_interval=0, @freq_relative_interval=0, @freq_recurrence_factor=0, @active_start_date=20220224, @active_end_date=99991231, @active_start_time=0, @active_end_time=235959 EXEC msdb.dbo.sp_add_jobserver @job_id=@jobId, @server_name=N'(local)' GO ``` 这个脚本创建了一个名为"MyJobName"的作业,该作业会执行名为"MyStoredProcedure"的存储过程,并且每天都会执行一次。你需要将"@command"参数中的存储过程名,"@database_name"参数中的数据库名以及"@freq_*"参数中的执行时间间隔修改为你的实际情况。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值