在Android开发中,会存在这么些场景 : 你需要在稍后的某个时间点或者当满足某个特定的条件时执行一个任务,例如当设备接通电源适配器或者连接到WIFI。幸运的是在API 21 ( Android 5.0,即Lollipop )中,google提供了一个新叫做JobScheduler API的组件来处理这样的场景。
当一系列预置的条件被满足时,JobScheduler API为你的应用执行一个操作。与AlarmManager不同的是这个执行时间是不确定的。除此之外,JobScheduler API允许同时执行多个任务。这允许你的应用执行某些指定的任务时不需要考虑时机控制引起的电池消耗。
JobScheduler简单使用
1. 创建Job Service
public class MyJobService extends JobService {
private static final String LOG_TAG ="ms" ;
@Override
public boolean onStartJob(JobParameters params) {
if (isNetWorkConnected()){
//在这里执行下载任务
new SimpleDownloadTask().execute(params);
return true;
}
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
当任务开始时会执行onStartJob(JobParameters params)方法,因为这是系统用来触发已经被执行的任务。正如你所看到的,这个方法返回一个boolean值。如果返回值是false,系统假设这个方法返回时任务已经执行完毕。如果返回值是true,那么系统假定这个任务正要被执行,执行任务的重担就落在了你的肩上。当任务执行完毕时你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来通知系统
当系统接收到一个取消请求时,系统会调用onStopJob(JobParameters params)方法取消正在等待执行的任务。很重要的一点是如果onStartJob(JobParameters params)返回false,那么系统假定在接收到一个取消请求时已经没有正在运行的任务。换句话说,onStopJob(JobParameters params)在这种情况下不会被调用。
需要注意的是这个job service运行在你的主线程,这意味着你需要使用子线程,handler, 或者一个异步任务来运行耗时的操作以防止阻塞主线程
2. 创建一个JobScheduler对象,并关联起来
//他是系统服务
JobScheduler mJobScheduler= (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
当你想创建定时任务时,你可以使用JobInfo.Builder来构建一个JobInfo对象,然后传递给你的Service。JobInfo.Builder接收两个参数,第一个参数是你要运行的任务的标识符,第二个是这个Service组件的类名。
ComponentName componentName = new ComponentName(this,MyJobService.class);
JobInfo job=new JobInfo.Builder(i,componentName)
.setMinimumLatency(5000)//最小延时 5秒
.setOverrideDeadline(60000)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//任意网络
.build();
//调用schedule
mJobScheduler.schedule(job);
JobInfo其他的设置方法:
.setMinimumLatency(5000)//5秒 最小延时、
.setOverrideDeadline(60000)//maximum最多执行时间
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)//免费的网络---wifi 蓝牙 USB
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//任意网络---
/**
设置重试/退避策略,当一个任务调度失败的时候执行什么样的测量采取重试。
initialBackoffMillis:第一次尝试重试的等待时间间隔ms
*backoffPolicy:对应的退避策略。比如等待的间隔呈指数增长。
*/
.setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)
.setBackoffCriteria(JobInfo.MAX_BACKOFF_DELAY_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR)
//.setPeriodic (long intervalMillis)//设置执行周期,每隔一段时间间隔任务最多可以执行一次。
//.setPeriodic(long intervalMillis,long flexMillis)//在周期执行的末端有一个flexMiliis长度的窗口期,任务就可以在这个窗口期执行。
//设置设备重启后,这个任务是否还要保留。需要权限: RECEIVE_BOOT_COMPLETED //ctrl+shift+y/u x
//.setPersisted(boolean isPersisted);
// .setRequiresCharging(boolean )//是否需要充电
// .setRequiresDeviceIdle(boolean)//是否需要等设备出于空闲状态的时候
// .addTriggerContentUri(uri)//监听uri对应的数据发生改变,就会触发任务的执行。
// .setTriggerContentMaxDelay(long duration)//设置Content发生变化一直到任务被执行中间的最大延迟时间
//设置Content发生变化一直到任务被执行中间的延迟。如果在这个延迟时间内content发生了改变,延迟时间会重写计算。
// .setTriggerContentUpdateDelay(long durationMilimms)
JobScheduler源码分析
JobScheduler是系统服务JobSchedulerService,JobScheduler是一个抽象类;以下是JobScheduler的源码:
public abstract class JobScheduler {
public static final int RESULT_FAILURE = 0;
public static final int RESULT_SUCCESS = 1;
//这个是提交一个工作任务JobInfo
public abstract int schedule(JobInfo job);
public abstract int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag);
//这个是取消一个工作任务
public abstract void cancel(int jobId);
//这个是取消所有的工作任务
public abstract void cancelAll();
//得到所有将来要执行的工作任务
public abstract @NonNull List getAllPendingJobs();
//根据Jobid获得将来要执行的工作任务
public abstract @Nullable JobInfo getPendingJob(int jobId);
}
JobScheduler的实现类是JobSchedulerImpl;
public class JobSchedulerImpl extends JobScheduler {
IJobScheduler mBinder;
/* package */ JobSchedulerImpl(IJobScheduler binder) {
mBinder = binder;
}
@Override
public int schedule(JobInfo job) {
try {
//这个mBinder是JobSchedulerService中的IJobScheduler.Stub,
return mBinder.schedule(job);
} catch (RemoteException e) {
return JobScheduler.RESULT_FAILURE;
}
}
@Override
public void cancel(int jobId) {
try {
mBinder.cancel(jobId);
} catch (RemoteException e) {}
}
@Override
public void cancelAll() {
try {
mBinder.cancelAll();
} catch (RemoteException e) {}
}
@Override
public List getAllPendingJobs() {
try {
return mBinder.getAllPendingJobs();
} catch (RemoteException e) {
return null;
}
}
}
在代码中 调用mJobScheduler.schedule(job);其实是调了JobScheduler的实现类JobSchedulerImpl中的schedule方法;然后再调了mBinder.schedule(job);这个mBinder就是JobSchedulerService,调用了JobSchedulerService类里面IJobScheduler.Stub内部类的schedule方法;
JobSchedulerService是在哪里启动的呢?先看一下的源码,从源码分析JobSchedulerService是一个系统服务;
public class JobSchedulerService extends com.android.server.SystemService
implements StateChangedListener, JobCompletedListener {
static final boolean DEBUG = false;
/** The number of concurrent jobs we run at one time. */
private static final int MAX_JOB_CONTEXTS_COUNT
= ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
static final String TAG = "JobSchedulerService";
/** Master list of jobs. */
final JobStore mJobs;
static final int MSG_JOB_EXPIRED = 0;
static final int MSG_CHECK_JOB = 1;
//....省略了n多代码
}
是系统服务那应该就是在 SystemServer启动的,先看一下SystemServer的源码;
public final class SystemServer {
private static final String TAG = "SystemServer";
//手机开机启动后会走这个main方法,然后调用run方法
public static void main(String[] args) {
new SystemServer().run();
}
手机开机启动会走SystemServer中的主函数main方法,main方法调用了run方法,下面是run方法中的代码:
private void run() {
//....省略了n多代码
// Start services.
//这里启动一些系统服务
try {
startBootstrapServices();
startCoreServices();
//会走这个
startOtherServices();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
}
//....省略了n多代码
}
从run方法可以看出,这里启动了一系列的系统服务,里面调用了startOtherServices()方法,那接下看一下startOtherServices方法,向下看:
private void startOtherServices() {
//....省略了n多代码
mSystemServiceManager.startService(TwilightService.class);
//这里就是启动JobSchedulerService
mSystemServiceManager.startService(JobSchedulerService.class);
if (!disableNonCoreServices) {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
}
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) {
mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
}
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) {
mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
}
}
//....省略了n多代码
}
在startOtherServices方法方法中调用了mSystemServiceManager.startService(JobSchedulerService.class);这里就启动了JobSchedulerService服务;接下来我们分析JobSchedulerService的源码;
先看一下JobSchedulerService的构造方法:
public JobSchedulerService(Context context) {
super(context);
// Create the controllers.
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));
mHandler = new JobHandler(context.getMainLooper());
//new了IJobScheduler.Stub,这个其实就是前面JobSchedulerImpl类中mBinder,这里用到了进程间通信binder机制,不明白的可能先学习一下进程间通信机制;
mJobSchedulerStub = new JobSchedulerStub();
mJobs = JobStore.initAndGet(this);
}
前面提到调用mBinder.schedule(job);其实是调用了JobSchedulerService类里面IJobScheduler.Stub内部类的schedule方法;接下看一下这个方法:
final class JobSchedulerStub extends IJobScheduler.Stub {
//....省略了n多代码
@Override
public int schedule(JobInfo job) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "Scheduling job: " + job.toString());
}
//这里做了一个检验,非关键代码
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
//....省略了n多代码
long ident = Binder.clearCallingIdentity();
try {
//这个是关键代码,调用JobSchedulerService中的schedule
return JobSchedulerService.this.schedule(job, uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
从上面代码看出,是调用了调用JobSchedulerService中的schedule方法,好了,看一下JobSchedulerService中的schedule方法;
public int schedule(JobInfo job, int uId) {
JobStatus jobStatus = new JobStatus(job, uId);
cancelJob(uId, job.getId());
startTrackingJob(jobStatus);
//通过handler发消息
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
return JobScheduler.RESULT_SUCCESS;
}
从上面代码可以看出是通过Handler发消息,MSG_CHECK_JOB是目标源,看一下JobHandler 中的方法
private class JobHandler extends Handler {
public JobHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message message) {
synchronized (mJobs) {
if (!mReadyToRock) {
return;
}
}
switch (message.what) {
case MSG_JOB_EXPIRED:
synchronized (mJobs) {
JobStatus runNow = (JobStatus) message.obj;
if (runNow != null && !mPendingJobs.contains(runNow)
&& mJobs.containsJob(runNow)) {
mPendingJobs.add(runNow);
}
queueReadyJobsForExecutionLockedH();
}
break;
case MSG_CHECK_JOB:
synchronized (mJobs) {
maybeQueueReadyJobsForExecutionLockedH();
}
break;
}
maybeRunPendingJobsH();
removeMessages(MSG_CHECK_JOB);
}
通过Handler发消息,然后调用 maybeQueueReadyJobsForExecutionLockedH()方法,
private void queueReadyJobsForExecutionLockedH() {
ArraySet jobs = mJobs.getJobs();
if (DEBUG) {
Slog.d(TAG, "queuing all ready jobs for execution:");
}
//遍历要处理的目标任务,把目标任务加到集合PendingJobs中
for (int i=0; i
这个方法主要是遍历将来要处理的工作任务然后一个个加到待处理工作任务集合中去;这个方法执行完后就会执行JobHandler中的maybeRunPendingJobsH()方法;
private void maybeRunPendingJobsH() {
synchronized (mJobs) {
if (mDeviceIdleMode) {
// If device is idle, we will not schedule jobs to run.
return;
}
Iterator it = mPendingJobs.iterator();
if (DEBUG) {
Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
}
//通过遍历待处理任务集合,一个个处理待处理任务
while (it.hasNext()) {
JobStatus nextPending = it.next();
JobServiceContext availableContext = null;
for (int i=0; i
从上面源码分析可以得出,这个方法通过遍历待处理任务集合,处理任务,这里调用了availableContext.executeRunnableJob(nextPending)方法,这个就是处理待处理任务的方法,接下来我们一起看看这个方法的源码,分析下:
public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (!name.equals(mRunningJob.getServiceComponent())) {
mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
return;
}
this.service = IJobService.Stub.asInterface(service);
final PowerManager pm =
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mRunningJob.getTag());
//这里也用WakeLock锁,防止手机休眠
mWakeLock.setWorkSource(new WorkSource(mRunningJob.getUid()));
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
}
}
JobServiceContext是ServiceConnection,这个是进程间通讯ServiceConnection,通过调用availableContext.executeRunnableJob(nextPending)方法,会触发调用onServiceConnected,看到这里应该明白了,onServiceConnected方法中的service就是jobservice,里面还用了WakeLock锁,防止手机休眠,然后通过Handler发消息 调用了handleServiceBoundH()方法,
/** Start the job on the service. */
private void handleServiceBoundH() {
if (DEBUG) {
Slog.d(TAG, "MSG_SERVICE_BOUND for " + mRunningJob.toShortString());
}
if (mVerb != VERB_BINDING) {
Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
+ VERB_STRINGS[mVerb]);
closeAndCleanupJobH(false /* reschedule */);
return;
}
if (mCancelled.get()) {
if (DEBUG) {
Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
+ mRunningJob);
}
closeAndCleanupJobH(true /* reschedule */);
return;
}
try {
mVerb = VERB_STARTING;
scheduleOpTimeOut();
//我们就是要找这个方法, 看到这里明白了吧,这个就是调用了jobService中的startjob
service.startJob(mParams);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending onStart message to '" +
mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
}
}
从上面源码可以看出,最终是调用了jobService中的startjob方法, 这样就明白了,是如何触发调用jobService中的startjob方法的;
前面在JobSchedulerService中提到了控件类StateController类,这个是一个抽象类,有很多实现类,这个我只分析一个ConnectivityController实现类,其他都差不多,接下来分析一下ConnectivityController源码:
public class ConnectivityController extends StateController implements
ConnectivityManager.OnNetworkActiveListener {
private static final String TAG = "JobScheduler.Conn";
//工作任务状态集合
private final List mTrackedJobs = new LinkedList();
//这个是手机网络连接改变广播,网络发生改变,会触发这个广播
private final BroadcastReceiver mConnectivityChangedReceiver =
new ConnectivityChangedReceiver();
/** Singleton. */
private static ConnectivityController mSingleton;
private static Object sCreationLock = new Object();
/** Track whether the latest active network is metered. */
private boolean mNetworkUnmetered;
/** Track whether the latest active network is connected. */
private boolean mNetworkConnected;
public static ConnectivityController get(JobSchedulerService jms) {
synchronized (sCreationLock) {
if (mSingleton == null) {
mSingleton = new ConnectivityController(jms, jms.getContext());
}
return mSingleton;
}
}
private ConnectivityController(StateChangedListener stateChangedListener, Context context) {
super(stateChangedListener, context);
// Register connectivity changed BR.
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiverAsUser(
mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null);
ConnectivityService cs =
(ConnectivityService)ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
if (cs != null) {
if (cs.getActiveNetworkInfo() != null) {
mNetworkConnected = cs.getActiveNetworkInfo().isConnected();
}
mNetworkUnmetered = mNetworkConnected && !cs.isActiveNetworkMetered();
}
}
@Override
public void maybeStartTrackingJob(JobStatus jobStatus) {
if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
synchronized (mTrackedJobs) {
jobStatus.connectivityConstraintSatisfied.set(mNetworkConnected);
jobStatus.unmeteredConstraintSatisfied.set(mNetworkUnmetered);
mTrackedJobs.add(jobStatus);
}
}
}
@Override
public void maybeStopTrackingJob(JobStatus jobStatus) {
if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
synchronized (mTrackedJobs) {
mTrackedJobs.remove(jobStatus);
}
}
}
/**
* @param userId Id of the user for whom we are updating the connectivity state.
*/
private void updateTrackedJobs(int userId) {
synchronized (mTrackedJobs) {
boolean changed = false;
for (JobStatus js : mTrackedJobs) {
if (js.getUserId() != userId) {
continue;
}
boolean prevIsConnected =
js.connectivityConstraintSatisfied.getAndSet(mNetworkConnected);
boolean prevIsMetered = js.unmeteredConstraintSatisfied.getAndSet(mNetworkUnmetered);
if (prevIsConnected != mNetworkConnected || prevIsMetered != mNetworkUnmetered) {
changed = true;
}
}
if (changed) {
mStateChangedListener.onControllerStateChanged();
}
}
}
/**
* We know the network has just come up. We want to run any jobs that are ready.
*/
public synchronized void onNetworkActive() {
synchronized (mTrackedJobs) {
for (JobStatus js : mTrackedJobs) {
if (js.isReady()) {
if (DEBUG) {
Slog.d(TAG, "Running " + js + " due to network activity.");
}
mStateChangedListener.onRunJobNow(js);
}
}
}
}
class ConnectivityChangedReceiver extends BroadcastReceiver {
/**
* We'll receive connectivity changes for each user here, which we process independently.
* We are only interested in the active network here. We're only interested in the active
* network, b/c the end result of this will be for apps to try to hit the network.
* @param context The Context in which the receiver is running.
* @param intent The Intent being received.
*/
// TODO: Test whether this will be called twice for each user.
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) {
Slog.d(TAG, "Received connectivity event: " + intent.getAction() + " u"
+ context.getUserId());
}
final String action = intent.getAction();
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
final int networkType =
intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
ConnectivityManager.TYPE_NONE);
// Connectivity manager for THIS context - important!
final ConnectivityManager connManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
final int userid = context.getUserId();
// This broadcast gets sent a lot, only update if the active network has changed.
if (activeNetwork == null) {
//网络未联接
mNetworkUnmetered = false;
mNetworkConnected = false;
updateTrackedJobs(userid);
} else if (activeNetwork.getType() == networkType) {
mNetworkUnmetered = false;
mNetworkConnected = !intent.getBooleanExtra(
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (mNetworkConnected) { // No point making the call if we know there's no conn.
mNetworkUnmetered = !connManager.isActiveNetworkMetered();
}
//更新工作任务
updateTrackedJobs(userid);
}
} else {
if (DEBUG) {
Slog.d(TAG, "Unrecognised action in intent: " + action);
}
}
}
};
@Override
public void dumpControllerState(PrintWriter pw) {
pw.println("Conn.");
pw.println("connected: " + mNetworkConnected + " unmetered: " + mNetworkUnmetered);
for (JobStatus js: mTrackedJobs) {
pw.println(String.valueOf(js.hashCode()).substring(0, 3) + ".."
+ ": C=" + js.hasConnectivityConstraint()
+ ", UM=" + js.hasUnmeteredConstraint());
}
}
}
上面是网络联接控制类ConnectivityController,当网络发生改变时,会触发网络连接改变广播,然后调用updateTrackedJobs(userid)方法,在updateTrackedJobs方法中,会判断网络是否有改变,有改变的会调 mStateChangedListener.onControllerStateChanged()方法;这样又调用了JobSchedulerService类中onControllerStateChanged方法:
@Override
public void onControllerStateChanged() {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
在onControllerStateChanged方法中通过handler发消息,然后调用了maybeQueueReadyJobsForExecutionLockedH();
private void maybeQueueReadyJobsForExecutionLockedH() {
int chargingCount = 0;
int idleCount = 0;
int backoffCount = 0;
int connectivityCount = 0;
List runnableJobs = new ArrayList();
ArraySet jobs = mJobs.getJobs();
for (int i=0; i 0) {
backoffCount++;
}
if (job.hasIdleConstraint()) {
idleCount++;
}
if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
connectivityCount++;
}
if (job.hasChargingConstraint()) {
chargingCount++;
}
runnableJobs.add(job);
} else if (isReadyToBeCancelledLocked(job)) {
stopJobOnServiceContextLocked(job);
}
}
if (backoffCount > 0 ||
idleCount >= MIN_IDLE_COUNT ||
connectivityCount >= MIN_CONNECTIVITY_COUNT ||
chargingCount >= MIN_CHARGING_COUNT ||
runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
}
for (int i=0; i