前言
最近在调查一个 JobScheduler 的问题,看了不少博客和源码,为了防止撂爪就忘,将自己学习到的东西整理一下。
先给出一个自己写的 demo :
private static ComponentName sService = new ComponentName("com.example.mi.myjobtest",
MyJobService.class.getName());
public static void schedule(Context context) {
JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo job = new JobInfo.Builder(JOB_ID, sService)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)//网络条件,默认值NETWORK_TYPE_NONE
.setPeriodic(DAY_TIME)//任务执行周期
.setPersisted(true)//设备重启后是否继续执行
.setRequiresCharging(true)//设置是否需要充电
.build();
js.schedule(job);
}
注意:
/**
* Set whether or not to persist this job across device reboots. This will only have an
* effect if your application holds the permission
* {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED}. Otherwise an exception will
* be thrown.
* @param isPersisted True to indicate that the job will be written to disk and loaded at
* boot.
*/
public Builder setPersisted(boolean isPersisted) {
mIsPersisted = isPersisted;
return this;
}
设置此条件需要 holds the permission {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED}
一、JobScheduler 服务启动
1.1 SystemServer.startOtherServices
SystemServer.java
private void startOtherServices() {
...
mSystemServiceManager.startService(JobSchedulerService.class);
...
}
其会调用 JobSchedulerService.class 的构造函数
1.2 JobSchedulerService
JobSchedulerService.java
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));
// 创建主线程的looper
mHandler = new JobHandler(context.getMainLooper());
// 创建binder服务端
mJobSchedulerStub = new JobSchedulerStub();
mJobs = JobStore.initAndGet(this);
}
创建了5个不同的 StateController,分别添加到 mControllers 中
接下来,整篇文章将以 TimeController 为例进行讲解
1.2.1TimeController
TimeController.java
/** Singleton. */
private static TimeController mSingleton;
public static synchronized TimeController get(JobSchedulerService jms) {
if (mSingleton == null) {
mSingleton = new TimeController(jms, jms.getContext());
}
return mSingleton;
}
private TimeController(StateChangedListener stateChangedListener, Context context) {
super(stateChangedListener, context);
mDeadlineExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
new Intent(ACTION_JOB_EXPIRED), 0);
mNextDelayExpiredAlarmIntent =
PendingIntent.getBroadcast(mContext, 0 /* ignored */,
new Intent(ACTION_JOB_DELAY_EXPIRED), 0);
mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
// Register BR for these intents.
IntentFilter intentFilter = new IntentFilter(ACTION_JOB_EXPIRED);
intentFilter.addAction(ACTION_JOB_DELAY_EXPIRED);
mContext.registerReceiver(mAlarmExpiredReceiver, intentFilter);
}
1.3 JobStore.initAndGet
JobStore.java
/** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
static JobStore initAndGet(JobSchedulerService jobManagerService) {
synchronized (sSingletonLock) {
if (sSingleton == null) {
sSingleton = new JobStore(jobManagerService.getContext(),
Environment.getDataDirectory());
}
return sSingleton;
}
}
1.4 创建 JobStore
JobStore.java
/**
* Construct the instance of the job store. This results in a blocking read from disk.
*/
private JobStore(Context context, File dataDir) {
mContext = context;
mDirtyOperations = 0;
File systemDir = new File(dataDir, "system");
File jobDir = new File(systemDir, "job");
jobDir.mkdirs();
// 创建/data/system/job/jobs.xml
mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
mJobSet = new ArraySet<JobStatus>();
// 从 jobs.xml 中读取 JobMap
readJobMapFromDisk(mJobSet);
}
1.5 xml 解析
1.5.1 ReadJobMapFromDiskRunnable
JobStore.java
/**
* Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
* need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
*/
private class ReadJobMapFromDiskRunnable implements Runnable {
private final ArraySet<JobStatus> jobSet;
ReadJobMapFromDiskRunnable(ArraySet<JobStatus> jobSet) {
this.jobSet = jobSet;
}
@Override
public void run() {
try {
List<JobStatus> jobs;
// mJobsFile 即为 /data/system/job/jobs.xml
FileInputStream fis = mJobsFile.openRead();
synchronized (JobStore.this) {
// 主要功能执行方法
jobs = readJobMapImpl(fis);
if (jobs != null) {
for (int i=0; i<jobs.size(); i++) {
this.jobSet.add(jobs.get(i));
}
}
}
fis.close();
}
...
}
“reads list of persisted job from xml” 这里只会读取 setPersisted(true) 的 jobs
1.5.2 xml 示例 (以上面 demo 为例)
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<job-info version="0">
...
<job jobid="11111111" package="com.example.mi.myjobtest" class="com.example.mi.myjobtest.MyJobService" uid="10128">
<constraints unmetered="true" charging="true" />
<periodic period="3600000" deadline="1504155742651" delay="1504152142651" />
<extras />
</job>
...
</job-info>
1.5.3 readJobMapImpl
JobStore.java
private List<JobStatus> readJobMapImpl(FileInputStream fis)
throws XmlPullParserException, IOException {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, StandardCharsets.UTF_8.name());
...
String tagName = parser.getName();
if ("job-info".equals(tagName)) {
final List<JobStatus> jobs = new ArrayList<JobStatus>();
// Read in version info.
...
eventType = parser.next();
do {
// Read each <job/>
if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
// Start reading job.
if ("job".equals(tagName)) {
// 读取 job
JobStatus persistedJob = restoreJobFromXml(parser);
if (persistedJob != null) {
jobs.add(persistedJob);
}
}
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
return jobs;
}
return null;
}
1.5.4 restoreJobFromXml
JobStore.java
private JobStatus restoreJobFromXml(XmlPullParser parser) throws XmlPullParserException,
IOException {
JobInfo.Builder jobBuilder;
int uid;
// 解析 job identifier attributes.即 <job jobid="11111111" package="com.example.mi.myjobtest" class="com.example.mi.myjobtest.MyJobService" uid="10128">
jobBuilder = buildBuilderFromXml(parser);
jobBuilder.setPersisted(true);
uid = Integer.valueOf(parser.getAttributeValue(null, "uid"));
int eventType;
// 解析 constraints tag.即 <constraints unmetered="true" charging="true" />
buildConstraintsFromXml(jobBuilder, parser);
// Tuple of (earliest runtime, latest runtime) in elapsed realtime after disk load.
// 解析 deadline="1504155742651" delay="1504152142651" 为 elapsedtime 形式
elapsedRuntimes = buildExecutionTimesFromXml(parser);
final long elapsedNow = SystemClock.elapsedRealtime();
if (XML_TAG_PERIODIC.equals(parser.getName())) {
// 解析 XML_TAG_PERIODIC
...
} else if (XML_TAG_ONEOFF.equals(parser.getName())) {
// 解析 XML_TAG_ONEOFF
...
} else {
}
maybeBuildBackoffPolicyFromXml(jobBuilder, parser);
// Read out extras Bundle.
...
return new JobStatus(
jobBuilder.build(), uid, elapsedRuntimes.first, elapsedRuntimes.second);
}
1.5.5 buildBuilderFromXml
JobStore.java
private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
// Pull out required fields from <job> attributes.
// 即解析 <job jobid="11111111" package="com.example.mi.myjobtest" class="com.example.mi.myjobtest.MyJobService" uid="10128"> 此行
int jobId = Integer.valueOf(parser.getAttributeValue(null, "jobid"));
String packageName = parser.getAttributeValue(null, "package");
String className = parser.getAttributeValue(null, "class");
ComponentName cname = new ComponentName(packageName, className);
return new JobInfo.Builder(jobId, cname);
}
1.5.6 创建 JobInfo
JobInfo.java
private JobInfo(JobInfo.Builder b) {
jobId = b.mJobId;
extras = b.mExtras;
...//形式与上面相同
}
public static final class Builder {
public Builder(int jobId, ComponentName jobService) {
mJobService = jobService;
mJobId = jobId;
}
public JobInfo build() {
...
return new JobInfo(this);
}
}
1.5.7 创建 JobStatus
JobStatus.java
/** How many times this job has failed, used to compute back-off. */
private final int numFailures;
private JobStatus(JobInfo job, int uId, int numFailures) {
this.job = job;
this.uId = uId;
this.name = job.getService().flattenToShortString();
this.tag = "*job*/" + this.name;
this.numFailures = numFailures;
}
public JobStatus(JobInfo job, int uId, long earliestRunTimeElapsedMillis,
long latestRunTimeElapsedMillis) {
this(job, uId, 0);
this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
}
到这里从 jobs.xml 中解析出 JobStatus 并创建出 JobStore 就讲解完了
1.6 JobSchedulerService.onBootPhase
JobSchedulerService.java
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) {
// 500, 注册广播 for package removals and user removals.
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null);
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mJobs) {
// 阶段 600
mReadyToRock = true;
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
// 创建 JobServiceContext 对象
mActiveServices.add(
new JobServiceContext(this, mBatteryStats,
getContext().getMainLooper()));
}
// Attach jobs to their controllers.开始 track jobs
ArraySet<JobStatus> jobs = mJobs.getJobs();
for (int i=0; i<jobs.size(); i++) {
JobStatus job = jobs.valueAt(i);
for (int controller=0; controller<mControllers.size(); controller++) {
mControllers.get(controller).deviceIdleModeChanged(mDeviceIdleMode);
mControllers.get(controller).maybeStartTrackingJob(job);
}
}
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
}
}
MAX_JOB_CONTEXTS_COUNT = ActivityManager.isLowRamDeviceStatic() ? 1 : 3,对于低内存的设备,则只创建一个创建JobServiceContext对象,否则创建3个该对象
1.7 创建 JobServiceContext
JobServiceContext.java
JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, Looper looper) {
this(service.getContext(), batteryStats, service, looper);
}
@VisibleForTesting
JobServiceContext(Context context, IBatteryStats batteryStats,
JobCompletedListener completedListener, Looper looper) {
mContext = context;
mBatteryStats = batteryStats;
mCallbackHandler = new JobServiceHandler(looper);
mCompletedListener = completedListener;
mAvailable = true;
}
此处的 JobServiceHandler 采用的是 system_server 进程的主线程
1.8 小结
- JobSchedulerService.JobHandler 和 JobServiceContext.JobServiceHandler 都运行在 system_server 进程的主线程
- JobSchedulerService 的启动过程主要是:创建了5个不同的 StateController,分别添加到 mControllers 中;从 /data/system/job/jobs.xml 文件中读取每个 JobInfo,再解析成 JobStatus 对象,添加到 JobStore 的成员变量 mJobSet 中;注册广播,绑定 jobs 和 controllers,发送 MSG_CHECK_JOB 开始运转