Android开发笔记(一百四十三)任务调度JobScheduler

任务调度

App除了通过屏幕向用户展示可交互的界面元素之外,还经常需要在后台做些背地里做的事情,比如说精密计算、文件下载、统计分析、数据导入、状态监控等等,这些用户看不到的事一般放在Service中处理。

然而有时候我们希望在特定情况下再启动事务,比如说延迟若干时间之后,或者等手机空闲了再运行,这样一方面不会在系统资源紧张之时喧宾夺主,另一方面也起到削峰填谷提高系统效率的作用。对于这些额外的条件要求,Service并不能直接支持,往往需要加入其他手段,才能较好地满足相关的运行条件,比如:
一、对于延迟时间执行,通常考虑利用系统的闹钟管理器AlarmManager进行定时管理,有关AlarmManager的说明参见《Android开发笔记(五十)定时器AlarmManager》。
二、对于是否联网、是否充电、是否空闲,一般要监听系统的相应广播,常见的系统广播说明如下:
1、网络状态变化需要监听系统广播android.net.conn.CONNECTIVITY_CHANGE;
2、设备是否充电需要监听系统广播Intent.ACTION_POWER_CONNECTED也就是android.intent.action.ACTION_POWER_CONNECTED;
3、设备是否空闲需要监听系统广播Intent.ACTION_SCREEN_OFF也就是android.intent.action.SCREEN_OFF;

可是要想给Service补充以上条件,势必加大了程序逻辑的复杂度,一会儿注册这个事件,一会儿注册那个事件,工程代码将变得不易维护。有鉴于此,Android从5.0开始,增加支持一种特殊的机制,即任务调度JobScheduler,该工具集成了常见的几种运行条件,开发者只需添加少数几行代码,即可完成原来要多种组件配合的工作。

任务调度机制由三个工具组成,首先是JobInfo,它指定了一个任务的概要信息,比如何时启动,启动时需要满足什么条件等等;其次是JobScheduler,它是系统提供的任务调度服务,它的实例从系统服务Context.JOB_SCHEDULER_SERVICE中获得;最后是JobService,它描述了该任务内部的具体业务逻辑,它的运行时刻由JobScheduler根据JobInfo指定的条件而计算决定。下面分别说明这三个工具的编码过程:

 

JobInfo

任务信息的运行条件由JobInfo.Builder来构造,下面是Builder的函数说明:
构造函数:指定该任务来源与目的,与Intent类似,第二个参数指定了开发者自定义的JobService。
setRequiredNetworkType:设置需要的网络条件,有三个取值:JobInfo.NETWORK_TYPE_NONE(无网络时执行,默认)、JobInfo.NETWORK_TYPE_ANY(有网络时执行)、JobInfo.NETWORK_TYPE_UNMETERED(网络无需付费时执行)
setPersisted:重启后是否还要继续执行,此时需要声明权限RECEIVE_BOOT_COMPLETED,否则会报错“java.lang.IllegalArgumentException: Error: requested job be persisted without holding RECEIVE_BOOT_COMPLETED permission.”而且RECEIVE_BOOT_COMPLETED需要在安装的时候就要声明,如果一开始没声明,而在升级时才声明,那么依然会报权限不足的错误。
setRequiresCharging:是否在充电时执行
setRequiresDeviceIdle:是否在空闲时执行
setPeriodic:设置时间间隔,单位毫秒。该方法不能和setMinimumLatency、setOverrideDeadline这两个同时调用,否则会报错“java.lang.IllegalArgumentException: Can't call setMinimumLatency() on a periodic job”,或者报错“java.lang.IllegalArgumentException: Can't call setOverrideDeadline() on a periodic job”。
setMinimumLatency:设置至少延迟多久后执行,单位毫秒。
setOverrideDeadline:设置最多延迟多久后执行,单位毫秒。
build:完成条件设置,返回构建好的JobInfo对象。

 

JobScheduler

任务调度的实例从系统服务Context.JOB_SCHEDULER_SERVICE中获得,代码举例如下:

		JobScheduler js = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

获得任务调度实例后,即可进行任务调度操作,下面是任务调度的相关方法:
schedule:把指定的JobInfo对象放入调度队列,并在条件满足时触发该对象中定义的JobService。
cancel:取消指定编号的任务。
cancelAll:取消所有任务。
getAllPendingJobs:获取所有挂起(即尚未执行)的任务。

 

JobService

任务服务是一种特殊的Service,它描述了该任务内部的具体业务逻辑,需要开发者重写的方法如下:
onStartJob:在任务开始执行时触发。返回false表示执行完毕,返回true表示需要开发者自己调用jobFinished方法通知系统已执行完成。
onStopJob:在任务停止执行时触发。

JobService内部另外实现了两个方法,说明如下

1、onBind方法,源码如下所示

    public final IBinder onBind(Intent intent) {
        return mBinder.asBinder();
    }

JobService实现了onBind方法,表示任务调度在工作的时候,JobService是通过绑定方式启动的。

2、jobFinished方法,源码如下所示

    public final void jobFinished(JobParameters params, boolean needsReschedule) {
        ensureHandler();
        Message m = Message.obtain(mHandler, MSG_JOB_FINISHED, params);
        m.arg2 = needsReschedule ? 1 : 0;
        m.sendToTarget();
    }

因为JobService由系统触发,不是在App的主线程中,所以这里通过Message机制与主线程进行通信。

 

启动方式

由于JobService继承自Service,因此既可以把它当作专门的调度服务来启动,也可以把它当作普通的服务来启动。

 

在Service外部进行调度

在Activity代码中增加任务调度,需要声明JobInfo对象,并通过JobScheduler进行调度,具体代码如下所示:

	//将任务作业发送到作业调度中去
	public void scheduleJob() {
		Log.d(TAG, "scheduleJob");
		JobInfo.Builder builder = new JobInfo.Builder(0, 
				new ComponentName(this, SimpleJobService.class));
		//设置需要的网络条件,默认为JobInfo.NETWORK_TYPE_NONE即无网络时执行
		//NETWORK_TYPE_NONE
		builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
		//builder.setPersisted(true); //重启后是否还要继续执行
		builder.setRequiresCharging(false); //是否在充电时执行
		builder.setRequiresDeviceIdle(false); //是否在空闲时执行
		//builder.setPeriodic(1000); //设置时间间隔,单位毫秒
		//setPeriodic不能和setMinimumLatency、setOverrideDeadline这两个同时调用
		//否则会报错“java.lang.IllegalArgumentException: Can't call setMinimumLatency() on a periodic job”
		//“java.lang.IllegalArgumentException: Can't call setOverrideDeadline() on a periodic job”
		builder.setMinimumLatency(500); //设置至少延迟多久后执行,单位毫秒
		builder.setOverrideDeadline(3000); //设置最多延迟多久后执行,单位毫秒
		JobInfo ji = builder.build();
		
		JobScheduler js = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
		js.schedule(ji);
	}


该方式用到的JobService代码例子如下所示:

	public class SimpleJobService extends JobService {
	private final static String TAG = "SimpleJobService";

	private Handler mHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			JobParameters param = (JobParameters) msg.obj;
			jobFinished(param, true);
			Log.d(TAG, "jobFinished");
		}
	};

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.d(TAG, "onStartCommand");
		return START_NOT_STICKY;
	}
	
	@Override
	public boolean onStartJob(JobParameters params) {
		Log.d(TAG, "onStartJob");
		Message message = Message.obtain();
		message.obj = params;
		mHandler.sendMessage(message);
		//返回false表示执行完毕,返回true表示需要开发者自己调用jobFinished方法通知系统已执行完成
		return true;
	}

	@Override
	public boolean onStopJob(JobParameters params) {
		//停止,不是结束。jobFinished不会直接触发onStopJob
		//必须在“onStartJob之后,jobFinished之前”取消任务,才会在jobFinished之后触发onStopJob
		Log.d(TAG, "onStopJob");
		mHandler.removeMessages(0);
		return true;
	}
	
}


以上代码需要注意的是:
1、因为系统服务是通过绑定方式启动JobService,所以此时onStartCommand方法永远不会执行;
2、onStopJob顾名思义是在任务停止时触发,但是直接调用jobFinished方法并不能触发onStopJob。原因是onStopJob的触发是有条件的,首先这里的停止指的是取消任务而不是完成任务;其次必须在“onStartJob之后,jobFinished之前”取消任务,才会在jobFinished之后触发onStopJob。

另外注意在AndroidManifest.xml中补充服务声明:

        <service
            android:name=".service.SimpleJobService"
            android:permission="android.permission.BIND_JOB_SERVICE" />

在Service内部进行调度

如果Activity通过常规的startService方法启动JobService,那么就得JobService自己在onStartCommand方法中进行任务调度了。除了对onStartCommand的处理存在区别之外,其它代码与上一种方式基本相同,完整的JobService代码如下所示:

public class MultiJobService extends JobService {
	private final static String TAG = "MultiJobService";

	private Handler mHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			JobParameters param = (JobParameters) msg.obj;
			jobFinished(param, true);
			Intent intent = new Intent(getApplicationContext(), MainActivity.class);
			intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			startActivity(intent);
		}
	};

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.d(TAG, "onStartCommand");
		scheduleJob();
		return START_NOT_STICKY;
	}

	@Override
	public boolean onStartJob(JobParameters params) {
		Log.d(TAG, "onStartJob");
		Message message = Message.obtain();
		message.obj = params;
		mHandler.sendMessage(message);
		//返回false表示执行完毕,返回true表示需要开发者自己调用jobFinished方法通知系统已执行完成
		return true;
	}

	@Override
	public boolean onStopJob(JobParameters params) {
		Log.d(TAG, "onStopJob");
		mHandler.removeMessages(0);
		return true;
	}
	
	//将任务作业发送到作业调度中去
	public void scheduleJob() {
		Log.d(TAG, "scheduleJob");
		JobInfo.Builder builder = new JobInfo.Builder(0, 
				new ComponentName(this, MultiJobService.class));
	  //设置需要的网络条件,默认为JobInfo.NETWORK_TYPE_NONE即无网络时执行
		builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
		//重启后是否还要继续执行,此时需要声明权限RECEIVE_BOOT_COMPLETED
		//否则会报错“java.lang.IllegalArgumentException: Error: requested job be persisted without holding RECEIVE_BOOT_COMPLETED permission.”
		//而且RECEIVE_BOOT_COMPLETED需要在安装的时候就要声明,如果一开始没声明,在升级时才声明,那么依然会报权限不足的错误
		builder.setPersisted(true);
		builder.setRequiresCharging(false); //是否在充电时执行
		builder.setRequiresDeviceIdle(false); //是否在空闲时执行
		builder.setPeriodic(1000); //设置时间间隔,单位毫秒
		JobInfo ji = builder.build();
		
		JobScheduler js = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
		js.schedule(ji);
	}

}


另外注意在AndroidManifest.xml中补充服务声明:

        <service
            android:name=".service.MultiJobService"
            android:permission="android.permission.BIND_JOB_SERVICE" />



点击下载本文用到的任务调度机制的代码


点此查看Android开发笔记的完整目录

JobSchedulerAndroid中的一个调度框架,它可以用来在特定的时间间隔或特定的条件下执行任务,同时也可以用来保持应用的活跃状态。 在Android系统中,应用程序在后台运行时有一定概率被系统回收释放内存,为了提高应用的用户体验和功能完整性,我们可以使用JobScheduler来保活应用。 具体实现方法如下: 1. 在AndroidManifest.xml文件中注册JobService,用于执行任务和保活应用。设置jobService的权限,以及设置该service的处理逻辑。 2. 在需要保活的地方调用JobScheduler的schedule()方法,创建一个JobInfo对象,并设置好相关参数。其中包括指定要执行的JobService、执行的触发条件(如设备空闲时、特定时间间隔、特定网络状态等)、设置重试或重复执行等。 3. 通过JobScheduler.schedule()方法将JobInfo对象提交给系统进行调度。系统会根据设置的条件和触发机制来执行任务。 需要注意的是,JobScheduler的保活机制是通过系统的调度来实现的,并不能保证100%的成功保活。因为在一些特殊的情况下,如低内存、电池低、系统启动等情况下,系统可能会暂停或取消JobScheduler的任务。所以我们还需要配合其他的保活机制来提高保活的成功率,如使用前台服务、双进程守护、自启动、推送等。 总结起来,JobSchedulerAndroid系统提供的一种调度框架,可以在一定程度上保活应用。通过设置JobInfo的相关参数,然后由系统进行调度执行,以确保应用的持续运行和活跃状态。同时,为了提高保活的成功率,我们还需要结合其他的保活机制来综合应用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值