JasonWang's Blog

如何用JobScheduler进行任务调度

字数统计: 1.7k阅读时长: 7 min
2017/12/23

Android从5.0开始添加了一个任何调度服务JobSchedulerService,APP可以通过该服务进行各种任务的调度,如定时任务,与服务器同步资源,下载特定信息等。由于JobScheduler通过收集各个应用的调度任务,采用批处理的方式,允许多个任务同时运行,可以让设备具有更长的睡眠时间, 从而延长了电池使用。这篇文章,主要从应用与原理两个方面讲述分析JobScheduler

如何使用JobScheduler

这里假定我们有一个定时任务,需要每隔一段时间就同步本地与服务器的数据。通过JobScheduelr发起任务调度,第一步是要创建一个JobService用于处理定时同步服务器的任务请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

/**
* data sync job service
*/

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

@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.v(TAG,"onStartJob()");
startSyncData();
return false;
}

@Override
public boolean onStopJob(JobParameters jobParameters) {
Log.v(TAG,"onStopJob()");
showMessage(getString(R.string.stop_job_scheduler));
return false;
}

private void startSyncData() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// data sync done
showMessage(getString(R.string.data_sync_complete));
}
}).start();
}

private void showMessage(String message) {
Intent intent = new Intent(getBaseContext(), MessageActivity.class);
intent.putExtra("message" , message);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}


同时需要在AndroidManifest.xml中声明该servce, 并且该service必须添加一个权限android:permission="android.permission.BIND_JOB_SERVICE, 这样当定时时间到后,系统会主动绑定该服务,从而发起任务调度:

1
2
3
4

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

建立了任务处理的service之后,就可以通过JobScheduler来发起任务调度的请求了, Android提供了如下几个接口供用户调度任务:

  • int schedule(in JobInfo job) : 发起一个任务job,如成功则返回1,失败则返回0;
  • enqueue(JobInfo job, JobWorkItem worker): 与schedule类似,但允许将一个新的任务放入job的队列;
  • void cancel(int jobId) : 取消一个任务;
  • void cancelAll(): 取消所有任务;
  • List<JobInfo> getAllPendingJobs():获取当前未处理的任务列表;
  • JobInfo getPendingJob(int jobId): 根据jobId来获取对应未处理任务的信息;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public abstract class JobScheduler {
public static final int RESULT_FAILURE = 0;
public static final int RESULT_SUCCESS = 1;

public abstract int schedule(JobInfo var1);

public abstract int enqueue(JobInfo job, JobWorkItem worker);

public abstract void cancel(int jobId);

public abstract void cancelAll();

public abstract List<JobInfo> getAllPendingJobs();

public abstract JobInfo getPendingJob(int jobId);
}

JobScheduler无法实例化,只能通过getSystemService接口来获取实例,在下面这个函数中, 首先构建一个任务用于定时同步服务器数据:

  • setPeriordic: 设置定时任务的时间间隔,如果没有设置则不是一个定时任务;
  • setPersisted: 持久化任务数据,下次开机时该任务会自动启动,如果设置了该选项,则需要在AndroidManifest.xml中添加一个用户权限声明:
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>,这样任务在开机启动后,立刻启动任务调度;
  • setRequiredNetworkType: 是否需要设置调度所需要的网络模式,比如可以设置是否在收费或者漫游情况下发起任务调度;
1
2
3
4
5
6
7
8
9
10
11
12

private void startDataSyncJobScheduler() {
ComponentName jobService = new ComponentName(this, DataSyncJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(DATA_SYNC_JOB_ID, jobService);
builder.setPeriodic(DATA_SYNC_INTERVAL)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);

JobScheduler js = (JobScheduler)getSystemService(JOB_SCHEDULER_SERVICE);
js.schedule(builder.build());
}

这样系统就会定时向DataSyncJobService发起任务请求,这里需要注意的是,当Android系统进入低电量的睡眠模式时,JobScheuduler是不允许执行的,详细可以参考休眠与待机

JobScheduler的原理

JobSchedulerService初始化

JobSchedulerService属于系统服务,因此在手机启动时,SystemServer进程会主动加载该服务。创建时,JobSchedulerService会依次初始化:

  • JobHandler: 用于处理请求的Handler;
  • JobSchedulerStub:服务端stub类,用于处理客户端请求;
  • JobStore: 持久化客户端请求数据
  • StateController: 监听系统状态,以此触发任务调度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public JobSchedulerService(Context context) {
super(context);
mHandler = new JobHandler(context.getMainLooper());
mConstants = new Constants(mHandler);
mJobSchedulerStub = new JobSchedulerStub();
mJobs = JobStore.initAndGet(this);

// 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));
mControllers.add(ContentObserverController.get(this));
mControllers.add(DeviceIdleJobsController.get(this));
}


系统启动阶段,JobSchedulerService需要注册应用程序安装包监听器(监听APP是否被卸载、重启),同时需要将持久化的任务数据从存储中加载,重新启动这些任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

@Override
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) {
mConstants.start(getContext().getContentResolver());
// Register br for package removals and user removals.
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
filter.addDataScheme("package");
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null);
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
try {
ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
| ActivityManager.UID_OBSERVER_IDLE);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mLock) {
// Let's go!
mReadyToRock = true;
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
// Create the "runners".
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
mActiveServices.add(
new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
getContext().getMainLooper()));
}
// Attach jobs to their controllers.
mJobs.forEachJob(new JobStatusFunctor() {
@Override
public void process(JobStatus job) {
for (int controller = 0; controller < mControllers.size(); controller++) {
final StateController sc = mControllers.get(controller);
sc.maybeStartTrackingJobLocked(job, null);
}
}
});
// GO GO GO!
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
}
}

JobSchedulerService中共启动了16个JobServiceContext服务负责处理任务请求。接下来,就来看下JobSchedulerService是如何进行工作调度的。

工作调度

目前JobSchedulerService支持的任务调度主要有如下几个属性:

  • 周期性:给定时间间隔触发一次任务调度;
  • 设备充电:是否需要设备当前正在充电;
  • 设备空闲:要求任务调度时设备处于空闲状态;
  • 数据库内容更改: 监听数据库内容状态,如有改变则触发任务调度;
  • 网络连接: 指定网络链接时触发任务调度

通过指定任务属性,我们可以发起一个定时任务,也可以指定某个网络条件下触发任务调度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

public class JobInfo implements Parcelable {
....
/** Builder class for constructing {@link JobInfo} objects. */
public static final class Builder {
private final int mJobId;
private final ComponentName mJobService;
private PersistableBundle mExtras = PersistableBundle.EMPTY;
private int mPriority = PRIORITY_DEFAULT;
private int mFlags;
// Requirements.
private boolean mRequiresCharging;
private boolean mRequiresDeviceIdle;
private int mNetworkType;
private ArrayList<TriggerContentUri> mTriggerContentUris;
private long mTriggerContentUpdateDelay = -1;
private long mTriggerContentMaxDelay = -1;
private boolean mIsPersisted;
// One-off parameters.
private long mMinLatencyMillis;
private long mMaxExecutionDelayMillis;
// Periodic parameters.
private boolean mIsPeriodic;
private boolean mHasEarlyConstraint;
private boolean mHasLateConstraint;
private long mIntervalMillis;
private long mFlexMillis;
// Back-off parameters.
private long mInitialBackoffMillis = DEFAULT_INITIAL_BACKOFF_MILLIS;
private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY;
/** Easy way to track whether the client has tried to set a back-off policy. */
private boolean mBackoffPolicySet = false;

....
}
}


**参考文献 **


原文作者:Jason Wang

更新日期:2021-08-11, 14:59:38

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 如何使用JobScheduler
  2. 2. JobScheduler的原理
    1. 2.1. JobSchedulerService初始化
    2. 2.2. 工作调度
  3. 3. **参考文献 **