JobScheduler系列1-基本用法和注意事项

JobScheduler推出的背景

在android开发中,我们为了节约电池电量,经常要在稍后的某个时间点或者满足某个特定的条件时去执行某个任务,例如网络状态连接到wifi状态时执行某些网络请求,或者每隔24小时,进行一项特定的操作。

针对每隔一段时间执行特定操作的需求,我们可以使用AlarmManager来完成,示例代码如下:

public void alarmTest(Context c){
	//创建一个PendingIntent,用于定时来临时执行任务
	Intent intent = new Intent(c, WorkerService.class);
	PendingIntent sender = PendingIntent.getService(c,
			1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
	//获取AlarmManager对象
	AlarmManager alarm = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
	//执行定时任务
	alarm.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis()
			,1000*60*60,sender);
}

针对网络连接到wifi,设备充电中执行某些操作的需求,我们可以采用监听系统广播,在收到对应信息后启动Service来完成任务。

这么看来,要完成这些需求好像也不难,但是我们仔细想想,如果我们的条件是有相互关系的。例如在网络环境为wifi,且手机正在充电时,我们再执行某个任务,这样我们需要监听2个系统广播,并且得判断他们同时满足的关系,是不是就复杂了不少?而且任务的执行时机由开发者自己管理,即没有必要,繁琐,也让系统没办法对其执行时机进行优化。

针对以上的问题,android5.0之后,google推出了JobScheduler服务,旨在为我们解决上述问题。

简介

Jobscheduler的android在5.0上针对于降低功耗而提出来的一种策略方案,自 Android 5.0 发布以来,JobScheduler 已成为执行后台工作的首选方式,其工作方式有利于用户。应用可以在安排作业的同时允许系统基于设备状态、电源和连接情况等具体条件进行优化。JobScheduler 可实现控制和简洁性,谷歌推出该机制是想要所有应用在执行后台任务时使用它制是想要所有应用在执行后台任务时使用它。

简介听起来很牛逼的样子,其实就是说我们之前想要的功能,现在不需要大家自己动手写啦,只需要使用google的JobScheduler,配合上简单的几行代码,就可以完成原来需要多个组件配合进行的工作,使代码变的更加优雅。

适用范围

需要在Android设备满足某种场合才需要去执行处理数据:
1、应用具有可以推迟的非面向用户的工作(定期数据库数据更新)
2、应用具有当插入设备时希望优先执行的工作(充电时才希望执行的工作备份数据)
3、需要访问网络或 Wi-Fi 连接时需要进行的任务(如向服务器拉取内置数据)
4、希望作为一个批次定期运行的许多任务(s)

使用Job Scheduler,应用需要做的事情就是判断哪些任务是不紧急的,可以交给Job Scheduler来处理,Job Scheduler集中处理收到的任务,选择合适的时间,合适的网络,再一起进行执行。把时效性不强的工作丢给它做。

使用示例

适用JobScheduler大致分为4个步骤,我们下面分别进行介绍。

创建执行任务的JobService

JobService类是系统为我们提供的执行任务的Service,我们只需要继承它,在它的onStartJob方法中开始执行我们的业务逻辑,onStopJob方法中取消正在执行的业务逻辑即可。注意onStartJob,onStopJob方法执行在我们的主线程中,如果需要执行耗时操作,需要自行开启线程,这里我们仅仅打印了log。

public class JobTestService extends JobService {
    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        int jobId = jobParameters.getJobId();
        Log.d("hyc"," start job "+jobId);
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        int jobId = jobParameters.getJobId();
        Log.d("hyc"," stop job "+jobId);
        return false;
    }
}

创建完了JobService类之后,我们还需要把它在AndroidManifest文件中注册,因为JobService也是一个服务,必须注册之后系统才能识别。

<service android:name="com.hyc.jobtest.JobTestService"
   android:permission="android.permission.BIND_JOB_SERVICE"
   />

另外注意该服务必须申明BIND_JOB_SERVICE权限,否则系统会报错,为何会这样呢?我们之后分析。

获取JobScheduler对象

可以看到,该步骤非常简单,直接利用Context类的getSystemService方法就可以直接获取了。

private JobScheduler getJobService(Context c)	{        
	JobScheduler  mScheduler = (JobScheduler) c.getSystemService(Context.JOB_SCHEDULER_SERVICE);
	return mScheduler;
}

根据约束条件,创建JobInfo对象

1,首我们创建Builder构建器对象,在其构造方法中,我们需要指定一个应用唯一的整数JobId,来代表我们的任务,如果有相同的jobId,则系统会将它们当成相同的任务并覆盖。同时,我们也需要在CompentName参数中指定我们要执行任务的自定义JobService。
2,我们需要为该任务的执行指定约束条件。这里我们指定了充电中和连网的限制条件。
3,我们直接调用build方法就将该构建器转换成了JobInfo对象

private JobInfo createJobInfo(Context c){
	//创建一个Builder对象,注意其ComponentName参数,我们需要传入
	//自定义的JobService的class对象
	JobInfo.Builder builder = new JobInfo.Builder(
			JOB_ID,new ComponentName(c,JobTestService.class));
	//设置需要充电中的限制条件
	builder.setRequiresCharging(true)
	//设置需要连网的限制条件
	.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
	JobInfo info = builder.build();
	return info;
}

开启JobScheduler任务

这一步非常简单,使用我们第二部获取的JobScheduler和第三步产生的JobInfo,即可开启任务了。

private void runJob(JobScheduler scheduler,JobInfo info){
	scheduler.schedule(info);
}

JobInfo可选约束条件

上面我们介绍完了JobScheduler的基本使用方法和步骤,那么之后我们想要了解点什么呢,还记得我们使用JobScheduler的初衷么?我们使用它是为了在某些特定约束条件下,由系统为我们调度任务,而不是我们自己去检查这些约束条件是否满足。

这么说来,了解系统为我们提供了哪些调度约束条件就显得至关重要了,只有知道了系统提供的所有条件,我们才知道是否可以使用JobScheduler来实现我们的需求。

至于如何知道可选的约束条件呢?也很简单,直接看一看JobInfo.Builder类的源码就知道了,我们以android8.1的源码来说明约束条件。

设备状态约束

此类约束条件主要和设备的当前状态有关,例如是否正在充电,是否处于空闲,网络是否连接等,具体见下面总结。

//是否需要处于充电状态
setRequiresCharging(boolean requiresCharging)
//是否需要处于非低电量状态
setRequiresBatteryNotLow(boolean batteryNotLow)
//是否需要设备处于空闲状态
setRequiresDeviceIdle(boolean requiresDeviceIdle)
//是否需要设备剩余空间不为low
setRequiresStorageNotLow(boolean storageNotLow)

//可选参数
//NETWORK_TYPE_ANY :需要网络连接
//NETWORK_TYPE_UNMETERED : 需要不计费的网络连接
//NETWORK_TYPE_NOT_ROAMING : 需要不漫游的网络连接
//NETWORK_TYPE_METERED : 需要计费的网络连接,例如蜂窝数据网络
setRequiredNetworkType(@NetworkType int networkType)

时间约束条件

此类约束条件与任务的执行时间有关,例如设置周期执行的任务,设置任务延时执行等。我们在使用的时候需要注意如下2点。
1,周期任务setPeriodic和setMinimumLatency设置任务延时,setOverrideDeadline设置任务最大执行时间冲突,我们不应该一起设置它们,否则会抛出异常。
2,setOverrideDeadline方法比较特殊,我们为任务指定一个最晚的执行时间,该事件达到后,不管其他约束条件是否满足,任务都将会执行

//设置任务周期执行,其周期为intervalMillis参数
//你无法控制任务的执行时间,系统只保证在此时间间隔内,任务最多执行一次。
setPeriodic(long intervalMillis)
//任务将被延迟至少minLatencyMillis时间执行,和setPeriodic周期任务冲突。
setMinimumLatency(long minLatencyMillis)
//不管其他约束条件是否满足,任务最多于maxExecutionDelayMillis时间后被执行
//和setPeriodic周期任务冲突。
setOverrideDeadline(long maxExecutionDelayMillis)

监听Uri约束条件

该类约束条件可以让JobScheduler监听指定的uri,当uri发生变化时,启动任务。

//增加待监听的uri
addTriggerContentUri(@NonNull TriggerContentUri uri)
//设置uri变化时,延迟durationMs后执行任务。在此期间Uri再次变化,则重新计时
//设置该时间可以让我们过滤掉太频繁的变化,减少任务的执行次数。
setTriggerContentUpdateDelay(long durationMs)
//第一次uri变化后,我们任务可以等待的最大时间,和updateDelay配合使用。
setTriggerContentMaxDelay(long durationMs)

增加待监听的uri很好理解,我们主要解释一下设置时间的作用。假设我们现在的需求是监听联系人的uri,每当联系人变化时,我们就启动任务查询所有联系人的信息。

假设某个时候,例如我们批量删除联系人时,联系人数据库变化很快,我们的任务将会频繁运行,此时我们可以通过setTriggerContentUpdateDelay为我们的任务指定一个1s的启动延时,这样联系人数据库连续变化时,我们的任务不会启动,直到1s内联系人数据库不再变化时,我们的查询任务才会启动。

我们在假设删除联系人的过程总共用时30s,而在此期间由于我们设置了启动延时,此时任务都不执行,我们觉得这个时间太长了,怎么办呢?我们可以使用setTriggerContentMaxDelay方法为它设置一个10s的最长时间,这样即使联系人数据库连续变化30s,我们也会最多10s执行一次查询任务。

其他设置项

除了约束条件外,还有一些其他的设置项,我们在这里也一并介绍一下

//设置是否为持久任务,如果是持久任务,则系统会将该任务写入磁盘
//在关机重启后,该任务依然可以恢复执行。
setPersisted(boolean isPersisted)
//设置任务调度失败后的重新调度策略和时间
setBackoffCriteria(long initialBackoffMillis,
                @BackoffPolicy int backoffPolicy)
//设置持久化的数据				
setExtras(@NonNull PersistableBundle extras)
//设置非持久化的数据
setTransientExtras(@NonNull Bundle extras)

我们这里解释一下设置持久化和非持久化的数据这部分,我们设置了这些数据后。可以在我们的自定义JobService中的onStartJob和onStopJob的JobParameters参数中获取到这些我们设置的数据,也就是我们可以通过这2个设置数据方法,传递给我们的自定义JobService一些数据。

自定义JobService注意事项

首先我们看一下最简单的JobService的写法,这里假设我们的任务很简单,直接在主线程中执行了。

public class JobTestService extends JobService {
    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        int jobId = jobParameters.getJobId();
        Log.d("hyc"," start job "+jobId);
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        int jobId = jobParameters.getJobId();
        Log.d("hyc"," stop job "+jobId);
        return false;
    }
}

个人觉得这里有如下需要注意的点:
1,onStartJob和onStopJob方法是执行在主线程中的,我们不可以在其中做耗时操作,否则可能导致ANR。
2,onStartJob方法在系统判定达到约束条件时被调用,我们在此处执行我们的业务逻辑。
3,onStopJob方法在系统判定你需要停止你的任务时被调用,可能在你调用jobFinish停止任务之前,那么什么时候会发生该情况呢?一般为约束条件不满足的时候,例如我们设置约束条件为充电中,则我们的任务会在充电中开始执行,如果在执行过程中,我们拔下了充电线,则系统判定我们的约束条件失效了,就会回调onStopJob方法,通知我们停止任务,我们应该在此时立即停止当前正在执行的业务逻辑。
4,onStartJob方法的返回false,代表我们的工作已经处理完了,系统会自动结束该任务,适用于任务在主线程中执行的情况。返回true,代表我们在子线程中执行任务,在任务执行完成后,我们需要手动调用jobFinish方法,通知系统任务已经执行完成。
5,onStopJob方法返回false,代表我们直接丢弃该任务。返回true则代表,如果我们设置了重试策略,该任务将按照重试策略进行调度。

上面介绍了不耗时的JobService的写法以及注意事项,下面我们来看一看执行耗时任务的JobService的写法,示例代码如下:

public class JobTestService extends JobService {
    private Thread mThread;
    
    @Override
    public boolean onStartJob(final JobParameters jobParameters) {
        final int jobId = jobParameters.getJobId();
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d("hyc"," start job "+jobId);
                try {
                    Thread.sleep(15000);
                    jobFinished(jobParameters,false);
                } catch (InterruptedException e) {
                    
                }
            }
        });
        mThread.start();
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        int jobId = jobParameters.getJobId();
        if(mThread!=null){
            mThread.interrupt();
        }
        Log.d("hyc"," stop job "+jobId);
        return false;
    }
}

可以看到,我们在onStartJob回调中开启线程,执行一个耗时子任务,并且返回true,告诉系统我们将在子线程中执行任务,在任务完成后,手动调用jobFinish告知系统任务完成。
而我们在onStopJob中,将会尝试中断该子任务所在线程,让子任务退出执行,其返回false,表示该任务将被丢弃。

常见异常

在使用JobScheduler时,我们可能会发现一启动我们的JobScheduler,程序就奔溃了,这是为什么呢?这是因为系统为了防止我们不合理的设置JobScheduler的参数,对我们设置的参数进行了检查,如果不符合规范,就会抛出异常提醒我们。我们来看看,有哪些情况吧。

异常描述原因
IllegalArgumentExceptionYou’re trying to build a job with no constraints, this is not allowed.没有设置任何约束条件
IllegalArgumentExceptionCan’t call setOverrideDeadline() on a periodic job周期任务setPeriodic方法不能和任务最晚执行时间方法setOverrideDeadline共存
IllegalArgumentException"Can’t call setMinimumLatency() on a periodic job周期任务不能和设置任务最大执行时间方法setOverrideDeadline共存
IllegalArgumentExceptionCan’t call addTriggerContentUri() on a periodic job周期任务不能和监听uri方法addTriggerContentUri共存
IllegalArgumentExceptionCan’t call addTriggerContentUri() on a persisted job持久化任务setPersisted不能和监听uri方法addTriggerContentUri共存
IllegalArgumentExceptionCan’t call setTransientExtras() on a persisted job持久化任务不能设置非持久化数据setTransientExtras
IllegalArgumentExceptionCan’t call setClipData() on a persisted job持久化任务不能设置clipData
IllegalArgumentExceptionAn idle mode job will not respect any back-off policy, so calling setBackoffCriteria with setRequiresDeviceIdle is an error.重新调度策略setBackoffCriteria无法和设备闲置状态setRequiresDeviceIdle 并存

除了上述表格中的JobInfo参数设置不规范,可能引起奔溃之外。还有可能因为如下2个权限问题引起。
1,自定义的JobService需要申明 android:permission="android.permission.BIND_JOB_SERVICE"权限,否则将抛出异常。
2,调用setPersisted方法设置了持久化任务,需要申请android.permission.RECEIVE_BOOT_COMPLETED权限,否则将会抛出异常。这也很好理解,毕竟持久化任务需要设备重启后,依然能够执行,因此我们需要申明可以接收设备启动广播。

周期任务的几个问题

通过之前的学习,我们可以知道setPeriodic(long intervalMillis)方法可以设置一个任务的周期为intervalMillis时间,但是我们在实际中发现周期时间并不准确,这是什么原因呢?我们分析认为原因有如下几个
1,系统内置了最小的周期事件,为15分钟,如果我们设置的周期小于15分钟,则会被强制设置为15分钟。
2,系统进行周期性任务调度时,会有一个窗口时间flexMillis的概念,这是什么意思呢?假设我们的任务周期为60min,那么我们的任务执行时刻不是60n这个时间点,而是60n-flexMills到60*n这样一个时间区间。也即是从系统层面说,我们的周期任务的执行周期时间就不是完全精确的,它可能存在最大flexMills的误差。
3,setPeriodic(long intervalMillis)方法指定的flexMills窗口时间和周期时间是相同的,这也就意味着可能存在较大的误差,还是以任务周期60min计算,执行时间点可能在(n-1)60到n60之间,可以看到误差较大。我们可以使用setPeriodic(long intervalMillis, long flexMillis)方法手动指定窗口时间flexMills来减少误差。
4,flexMillis窗口时间同样存在最小值。其最小值为5min和周期时间的5%之间较大者,因此周期任务的误差窗口时间是始终存在的,即使我们把它设置为0,也没有用。

在这里插入图片描述

  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
JobSchedulerAndroid中用于调度和执行后台任务的系统服务。它可以在特定的时间间隔或满足特定条件时执行任务。以下是JobScheduler基本用法: 1. 创建JobService类: 创建一个继承自android.app.job.JobService的类,用于执行后台任务。 2. 在AndroidManifest.xml中注册JobService: 在<application>标签内添加以下代码: ```xml <service android:name=".YourJobService" android:permission="android.permission.BIND_JOB_SERVICE" /> ``` 3. 创建JobInfo对象: ```java int jobId = 1; // 用于标识任务的唯一ID JobInfo.Builder builder = new JobInfo.Builder(jobId, new ComponentName(this, YourJobService.class)); ``` 4. 设置触发条件和约束条件: ```java builder.setPeriodic(10000); // 设置任务的执行周期,单位为毫秒 builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); // 设置网络连接条件 // 可以设置更多的约束条件,如setRequiresCharging()、setRequiresDeviceIdle()等 ``` 5. 调用JobScheduler.schedule()方法来安排任务: ```java JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); jobScheduler.schedule(builder.build()); ``` 6. 处理任务逻辑: 在YourJobService类中重写onStartJob()方法,在该方法中执行后台任务逻辑。 ```java @Override public boolean onStartJob(JobParameters params) { // 执行后台任务逻辑 return false; // 返回false表示任务已完成,返回true表示任务需要继续执行 } @Override public boolean onStopJob(JobParameters params) { // 如果任务被取消,可在此方法中处理相关逻辑 return false; // 返回true表示任务应该重新计划执行,返回false表示任务已被完全处理 } ``` 注意事项: - JobSchedulerAndroid 5.0(API级别21)及更高版本引入的特性。 - 在使用JobScheduler时,需要在AndroidManifest.xml文件中添加相应的权限: ```xml <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> ``` - 任务的执行时间可能会受到系统资源和设备状态的影响,无法保证精确的执行时间。 以上是JobScheduler基本用法,你可以根据具体的需求和条件设置更多的参数和约束条件。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值