JobService:
Android 5.0以后,Google推出了一个JobService,用来执行一些并非即时执行的后台进程。
场景:
需要在稍后的某个时间点或者当满足某个特定的条件时执行一个任务。
使用:
在JobService中有两个抽象方法onStartJob(JobParameters)和onStopJob(JobParameters)。onStartJob在JobService被调度到的时候会执行,我们只需要继承JobService然后重写onStartJob方法,并在里面执行我们的后台任务就可以了。
注册:
<service
android:name=".service.TestService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
JobService:
/*
* 需要重写,开始jobScheduler的方法
*/
public abstract boolean onStartJob(JobParameters params);
/*
* 停止JobScheduler的方法
*/
public abstract boolean onStopJob(JobParameters params);
/*
* 完成JobScheduler的方法
*/
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();
}
JobScheduler是Job的调度类:
/*
* 参数:JobInfo采用Builder的设计模式,对需要执行的Job任务信息进行的封装。
* 返回值:RESULT_SUCCESS=1 RESULT_FAILURE=0 表示执行成功或失败
*/
public abstract int schedule(JobInfo job);
/**通过指定的jobId取消Job任务*/
public abstract void cancel(int jobId);
/**取消所有的Job任务*/
public abstract void cancelAll();
/**获取所有的未执行的Job任务*/
public abstract @NonNull List<JobInfo> getAllPendingJobs();
/**获取指定的Job未执行的任务*/
public abstract @Nullable JobInfo getPendingJob(int jobId);
JobInfo:
// jobId每个Job任务的id
int jobId = 1;
// 指定你需要执行的JobService
ComponentName name = new ComponentName(getPackageName(), MyJobService.class.getName()));
JobInfo.Builder builder = new JobInfo.Bulider(jobId, name);
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE); //设置需要的网络条件,默认NETWORK_TYPE_NONE
builder.setPeriodic(3000);//设置间隔时间
builder.setMinimumLatency(3000);// 设置任务运行最少延迟时间
builder.setOverrideDeadline(50000);// 设置deadline,若到期还没有达到规定的条件则会开始执行
builder.setRequiresCharging(true);// 设置是否充电的条件,默认false
builder.setRequiresDeviceIdle(false);// 设置手机是否空闲的条件,默认false
builder.setPersisted(true);//设备重启之后你的任务是否还要继续执行
JobInfo info = builder.build();
重试方案:
JobInfo.BACKOFF_POLICY_LINEAR限行方案
BACKOFF_POLICY_EXPONENTIAL指数方案
执行时网络状态 setRequiredNetworkType
NETWORK_TYPE_NONE 默认
NETWORK_TYPE_ANY 任何网络状态
NETWORK_TYPE_UNMETERED 不需要计量的网络状态
NETWORK_TYPE_NOT_ROAMING 非漫游状态
重复每周期间隔时间
setPeriodic setPeriodic(3000) 每隔3秒执行一次任务
设备重启后是否继续后台操作
setPersisted,前提是具有权限
setTriggerContentMaxDelay
设置从第一个时间检测到内容更改被允许的最大总延迟(以毫秒为单位),直到作业被调度为止
说明:
在onStartJob方法中,将要执行我们的定时服务,主要依靠的是JobScheduler。
JobScheduler jobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(this, TestService.class)); //指定哪个JobService执行操作
builder.setMinimumLatency(TimeUnit.MILLISECONDS.toMillis(10)); //执行的最小延迟时间
builder.setOverrideDeadline(TimeUnit.MILLISECONDS.toMillis(15)); //执行的最长延时时间
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING); //非漫游网络状态
builder.setBackoffCriteria(TimeUnit.MINUTES.toMillis(10), JobInfo.BACKOFF_POLICY_LINEAR); //线性重试方案
builder.setRequiresCharging(false); // 未充电状态
jobScheduler.schedule(builder.build()); //启动计划(条件符合则调用 onStartJob)
注意:
1. obService默认在主线程中处理传入的每个操作。
2. onStartJob(JobParameters params)方法的返回值。
因为这是系统用来触发已经被执行的任务。这个方法返回一个boolean值。如果返回值是false,系统假设这个方法返回时任务已经执行完毕,在取消任务的时候,onStopJob()方法就不会执行;如果返回值是true,那么系统假定这个任务正要被执行,执行任务的重担就落在了你的肩上,取消任务的时候,onStopJob()方法会执行。返回true,你会让系统知道你会手动地调用jobFinished(JobParameters params, boolean needsRescheduled)方法。
3. jobFinished(JobParameters params,boolean needsReschedule)
当任务执行完毕之后,你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来让系统知道这个任务已经结束,系统可以将下一个任务添加到队列中。如果你没有调用jobFinished(JobParameters params, boolean needsRescheduled),你的任务只会执行一次,而应用中的其他任务就不会被执行。
4. jobFinished(JobParameters params, boolean needsRescheduled)的两个参数中的params参数是从JobService的onStartJob(JobParameters params)的params传递过来的,needsRescheduled参数是让系统知道这个任务是否应该在最处的条件下被重复执行。这个boolean值很有用,因为它指明了你如何处理由于其他原因导致任务执行失败的情况。
源码分析:
JobSchedulerService启动过程
public JobSchedulerService(Context context) {
super(context);
// 创建控制器
mControllers = new ArrayList<StateController>();
// 添加网络控制器
mControllers.add(ConnectivityController.get(this));
// 添加时间控制器
mControllers.add(TimeController.get(this));
// 添加空闲控制器
mControllers.add(IdleController.get(this));
// 添加电池控制器
mControllers.add(BatteryController.get(this));
// 添加应用空闲控制器
mControllers.add(AppIdleController.get(this));
// 初始化JobHandler,主要处理任务到期和检查任务的消息
mHandler = new JobHandler(context.getMainLooper());
// 初始化mJobSchedulerStub
mJobSchedulerStub = new JobSchedulerStub();
// 初始化JobStore并返回从data/system/job/jobs.xml文件中读取的永久性任务
mJobs = JobStore.initAndGet(this);
}
创建服务有下面几个步骤:
1.初始化各种控制器并添加到列表中
2.初始化JobHandler
3.初始化JobSchedulerStub代理对象
4.初始化任务主列表
各控制器作用:
-
ConnectivityController
动态注册接收网络变化的广播
工作流程:
动态注册接收网络变化的广播,并给mNetworkConnected变量和mNetworkUnmetered变量赋初值,收到广播后会修改这两个参数的值并调用updateTrackedJobs方法,该方法主要是遍历保存在追踪列表中的任务,查看是否有任务的两个参数值相对于之前保存的值有变化,如果有则调用mStateChangedListener监听器的onControllerStateChanged()方法通知JobSchedulerService约束任务的条件状态发生改变,这里的mStateChangedListener就是构造方法传递来的JobSchedulerService的实例。由于控制器实现了ConnectivityManage.OnNetworkActiveListener接口,故当网络可用时会调用该接口中的onNetworkActive方法,在该方法中会调用监听器的onRunJobNow方法通知JobSchedulerService执行任务。 -
TimeController
该控制器跟任务执行时间相关,初始化控制器时,初始化任务的deadline到期和延迟到期时发送广播的操作,动态注册这两个广播,根据接收到不同的广播执行不同的检查机制。 -
IdleController
动态注册监听息屏/亮屏,进入休眠/退出休眠以及进入空闲状态的广播。 -
BatteryController
动态注册监听电量低/电量OK,充电/非充电的广播。 -
AppIdleController
初始化该控制器时,添加一个继承了UsageStatsManagerInternal.AppIdleStateChangeListener的APP空闲状态监听器来监听APP的空闲状态。
JobService源码:
/**
* 从JobScheduler回调方法的入口。这是处理之前安排的异步请求的基类。
* 使用时需要重写onStartJob方法来写任务的执行逻辑。
* 该服务执行运行在应用程序主线程的Handler传递过来的每个任务。这意味着你必须在thread/handler/AsyncTask
* 中实现执行的逻辑。不这样做将导致来自JobManager的任务阻塞,无法继续执行。
*/
// JobService是实现了Service的抽象类
public abstract class JobService extends Service {
private static final String TAG = "JobService";
/**
* JobService的子类必须在Manifest中声明该权限,如:
* <service android:name="MyJobService"
* android:permission="android.permission.BIND_JOB_SERVICE" >
* ...
* </service>
*/
public static final String PERMISSION_BIND =
"android.permission.BIND_JOB_SERVICE";
// 调用onStartJob方法的标识符
private final int MSG_EXECUTE_JOB = 0;
// 调用onStopJob方法的标识符
private final int MSG_STOP_JOB = 1;
// 任务完成的标识符
private final int MSG_JOB_FINISHED = 2;
// mHandler的锁对象
private final Object mHandlerLock = new Object();
// 处理post传来的任务:负责调用到客户端的逻辑和处理系统回调
@GuardedBy("mHandlerLock")
JobHandler mHandler;
// 服务的Binder,后面重写的onBind方法中会返回该Binder的实例,
// mBinder是IJobService.aidl的接口的一个对象
IJobService mBinder = new IJobService.Stub() {
@Override
public void startJob(JobParameters jobParams) {
ensureHandler();
// 把jobParams封装成一个消息
Message m = Message.obtain(mHandler, MSG_EXECUTE_JOB, jobParams);
// 把消息发送给mHandler
m.sendToTarget();
}
@Override
public void stopJob(JobParameters jobParams) {
ensureHandler();
Message m = Message.obtain(mHandler, MSG_STOP_JOB, jobParams);
m.sendToTarget();
}
};
/** @hide */
void ensureHandler() {
synchronized (mHandlerLock) {
if (mHandler == null) {
// 创建Handler对象时传递的是主线程的looper,即消息会在主线程中处理
mHandler = new JobHandler(getMainLooper());
}
}
}
/**
* 运行在应用程序的主线程 - callbacks are meant to offboard work to some other
* (app-specified) mechanism.
* @hide
*/
class JobHandler extends Handler {
JobHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// 从msg消息中取出JobParameters对象
final JobParameters params = (JobParameters) msg.obj;
switch (msg.what) {
case MSG_EXECUTE_JOB:
try {
// 执行JobService中的onStartJob方法,该方法是一个抽象方法,需要子类重写
boolean workOngoing = JobService.this.onStartJob(params);
ackStartMessage(params, workOngoing);
} catch (Exception e) {
Log.e(TAG, "Error while executing job: " + params.getJobId());
throw new RuntimeException(e);
}
break;
case MSG_STOP_JOB:
try {
// 执行JobService中的onStopJob方法,该方法是一个抽象方法,需要子类重写
boolean ret = JobService.this.onStopJob(params);
ackStopMessage(params, ret);
} catch (Exception e) {
Log.e(TAG, "Application unable to handle onStopJob.", e);
throw new RuntimeException(e);
}
break;
case MSG_JOB_FINISHED:
// 从消息中获取任务是否需要重试
final boolean needsReschedule = (msg.arg2 == 1);
// 从参数中获取回调接口
IJobCallback callback = params.getCallback();
if (callback != null) {
try {
// 调用回调接口中的jobFinished方法
callback.jobFinished(params.getJobId(), needsReschedule);
} catch (RemoteException e) {
Log.e(TAG, "Error reporting job finish to system: binder has gone" +
"away.");
}
} else {
Log.e(TAG, "finishJob() called for a nonexistent job id.");
}
break;
default:
Log.e(TAG, "Unrecognised message received.");
break;
}
}
private void ackStartMessage(JobParameters params,
boolean workOngoing) {
// 获取params的回调接口
final IJobCallback callback = params.getCallback();
final int jobId = params.getJobId();
if (callback != null) {
try {
//调用回调接口中的acknowledgeStartMessage方法
callback.acknowledgeStartMessage(jobId, workOngoing);
} catch (RemoteException e) {
Log.e(TAG, "System unreachable for starting job.");
}
} else {
// 任务已经处理完了
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG,
"Attempting to ack a job that has already been processed.");
}
}
}
private void ackStopMessage(JobParameters params, boolean reschedule) {
final IJobCallback callback = params.getCallback();
final int jobId = params.getJobId();
if (callback != null) {
try {
callback.acknowledgeStopMessage(jobId, reschedule);
} catch(RemoteException e) {
Log.e(TAG, "System unreachable for stopping job.");
}
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Attempting to ack a job that has already been processed.");
}
}
}
}
// JobService重写了onBind方法,返回mBinder的一个对象
/** @hide */
public final IBinder onBind(Intent intent) {
return mBinder.asBinder();
}
/**
* 抽象方法,需要子类重写该方法,在该方法中写执行任务的逻辑,该方法运行在主线程
*
* @param params 任务相关的参数,包括在创建任务时自己设定的所有参数
* @return true:如果要执行的任务比较耗时,并且当任务完成时需要调用jobFinished()方法来通知系统
* false:如果要执行的任务不耗时
*
*/
public abstract boolean onStartJob(JobParameters params);
/**
* 抽象方法,需要子类重写该方法,在该方法中写任务停止时的逻辑,该方法运行在主线程
* 该方法被调用场景:在执行任务时,规定的要求已不再满足。
* 如果不重写这个方法,系统可能不再为你的应用程序持有wakelock
*
* @param params 任务相关的参数.
* @return true:基于在创建任务时设定的重试机制指示JobManager是否要重新安排任务。
* false:丢弃本次任务。
* 无论返回什么值,任务必须停止执行。
*/
public abstract boolean onStopJob(JobParameters params);
/**
* 通知JobManager任务已经执行完成的回调方法。该方法可以从任何线程中调用,因为该方法最终会在应用程序的主线程中执行。
* 当系统收到这个消息时,它会释放持有的wakelock锁。
* 可以根据needsReschedule参数写执行后的操作,基于默认值或使用setBackoffCriteria方法设置的值回退任务的定时器。
* 运行在空闲模式的任务是不能回退的。这可能会导致任务重复添加到任务队列中,并且在未来的某个空闲时间内重复执行该任务。
*
* @param params 通过onStartJob方法传递的JobParameters参数
* @param needsReschedule true:如果该任务需要重写安排
* false:其他情况
*/
public final void jobFinished(JobParameters params, boolean needsReschedule) {
ensureHandler();
// 把参数封装成消息
Message m = Message.obtain(mHandler, MSG_JOB_FINISHED, params);
m.arg2 = needsReschedule ? 1 : 0;
// 把消息发送给mHandler
m.sendToTarget();
}
}
IJobService.aidl的定义:
package android.app.job;
import android.app.job.JobParameters;
/**
* 这是一个framework用来与继承自JobService的应用程序代码进行通信的接口。
* 应用程序代码不直接实现该接口,而是继承自JobService。
* {@hide}
*/
oneway interface IJobService {
/** Begin execution of application's job. */
void startJob(in JobParameters jobParams);
/** Stop execution of application's job. */
void stopJob(in JobParameters jobParams);
}
说明:
上面接口定义中用到了oneway,意思是如果调用者和被调用者在同一个进程则同步执行,如果不在同一个进程则异步执行,即:调用该接口中的方法后不需要等待方法执行完成。