JSS 第 2 篇 - JobSchedulerService 初始化
基于Android 7.1.1 源码分析,本文为原创,转载请说明出处。。。
前言
服务端和客户端是通过 Binder 机制通信的,我们一般是通过如下方式来注册和执行一个任务:
public static int schedulePreOdexJob(int jobId, Context context) {
JobScheduler jobScheduler= (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(jobId, new ComponentName(context.getPackageName(), OdexJobService.class.getName()));
builder.setOppoJob(true);
builder.setRequiresProtectFore(true);
builder.setHasCpuConstraint(true);
builder.setOverrideDeadline(2*60*60*1000);
JobInfo jobInfo = builder.build();
return jobScheduler.schedule(jobInfo);
}
接下来,我们就继续来分析服务端是如何实现的!
JobSchedulerService 服务的启动,是在 SystemServer 的 startOtherServices 方法中:
private void startOtherServices() {
...
mSystemServiceManager.startService(JobSchedulerService.class);
...
}
这个方法首先会调用服务的 Constructer,然后调用服务的 onStart 方法!
下面我们一个一个看:
1 JobSchedulerService
我们先来看 JobSchedulerService 的构造器:
public JobSchedulerService(Context context) {
super(context);
// 创建主线程的 looper,创建对应的 Handler
mHandler = new JobHandler(context.getMainLooper());
mConstants = new Constants(mHandler);
// 创建 binder 服务端
mJobSchedulerStub = new JobSchedulerStub();
// 初始化 JobStore 对象!
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));
}
这里构造器其中,创建了一些很重要的成员变量,其中有一个控制器:
- ConnectivityController:注册监听网络连接状态的广播;
- DeviceIdleJobsController:
- ContentObserverController:
- AppIdleController:
- BatteryController:注册监听电池是否充电,电量状态的广播;
- IdleController:注册监听屏幕亮 / 灭,dream 进入 / 退出,状态改变的广播;
- TimeController:注册监听 job 时间到期的广播;
下图展示了 这几个主要的 Controller 之间的关系:
(图先省略啦,后续补上,哈哈)
可以看到,他们都继承了 StateController 类,我们后面来分析控制器!
1.1 JobStore
JobStore 方法的作用是存储了管理系统中存储的所有注册过的 JobStore:
public class JobStore {
private static final String TAG = "JobStore";
private static final boolean DEBUG = JobSchedulerService.DEBUG;
/** Threshold to adjust how often we want to write to the db. */
private static final int MAX_OPS_BEFORE_WRITE = 1;
final Object mLock;
final JobSet mJobSet; // per-caller-uid tracking // 存储 jobs.xml 中的数据
final Context mContext;
private int mDirtyOperations;
private static final Object sSingletonLock = new Object();
private final AtomicFile mJobsFile; // 只向本地文件: /system/job/jobs.xml
/** Handler backed by IoThread for writing to disk. */
private final Handler mIoHandler = IoThread.getHandler();
private static JobStore sSingleton;
/** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
static JobStore initAndGet(JobSchedulerService jobManagerService) {
synchronized (sSingletonLock) {
if (sSingleton == null) {
sSingleton = new JobStore(jobManagerService.getContext(),
jobManagerService.getLock(), Environment.getDataDirectory());
}
return sSingleton;
}
}
/**
* Construct the instance of the job store. This results in a blocking read from disk.
*/
private JobStore(Context context, Object lock, File dataDir) {
mLock = lock;
mContext = context;
mDirtyOperations = 0;
File systemDir = new File(dataDir, "system");
File jobDir = new File(systemDir, "job");
jobDir.mkdirs();
mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
mJobSet = new JobSet();
readJobMapFromDisk(mJobSet); // 从 Jobs.xml 中读取数据,数四化 JobSet!
}
上面是 JobStore 的初始化操作,该方法会创建job目录以及jobs.xml文件, 以及从文件中读取所有的JobStatus。
1.1.1 readJobMapFromDisk
这个方法创建了一个 Runnable 去实现具体的业务!
@VisibleForTesting
public void readJobMapFromDisk(JobSet jobSet) {
new ReadJobMapFromDiskRunnable(jobSet).run();
}
从 Jobs.xml 文件中读取数据:
private class ReadJobMapFromDiskRunnable implements Runnable {
private final JobSet jobSet;
/**
* @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
* so that after disk read we can populate it directly.
*/
ReadJobMapFromDiskRunnable(JobSet jobSet) {
this.jobSet = jobSet;
}
@Override
public void run() {
try {
List<JobStatus> jobs;
FileInputStream fis = mJobsFile.openRead();
synchronized (mLock) {
jobs = readJobMapImpl(fis);
if (jobs != null) {
for (int i=0; i<jobs.size(); i++) {
this.jobSet.add(jobs.get(i));
}
}
}
fis.close();
} catch (FileNotFoundException e) {
if (JobSchedulerService.DEBUG) {
Slog.d(TAG, "Could not find jobs file, probably there was nothing to load.");
}
} catch (XmlPullParserException e) {
if (JobSchedulerService.DEBUG) {
Slog.d(TAG, "Error parsing xml.", e);
}
} catch (IOException e) {
if (JobSchedulerService.DEBUG) {
Slog.d(TAG, "Error parsing xml.", e);
}
}
}
... ... ... ...
}
可以看出,调用了 readJobMapImpl 方法来解析 Jobs.xml !
1.1.2 readJobMapImpl
private List<JobStatus> readJobMapImpl(FileInputStream fis)
throws XmlPullParserException, IOException {
... ... ... ...
String tagName = parser.getName();
if ("job-info".equals(tagName)) { // 解析 <job-info> 标签,校验版本是否有问题
final List<JobStatus> jobs = new ArrayList<JobStatus>();
// Read in version info.
try {
int version = Integer.parseInt(parser.getAttributeValue(null, "version"));
if (version != JOBS_FILE_VERSION) {
Slog.d(TAG, "Invalid version number, aborting jobs file read.");
return null;
}
} catch (NumberFormatException e) {
Slog.e(TAG, "Invalid version number, aborting jobs file read.");
return null;
}
eventType = parser.next();
do {
// Read each <job/>
if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
// Start reading job.
if ("job".equals(tagName)) {
// 解析<job> 标签,获得每个job 对应的 jobStatus 对象
JobStatus persistedJob = restoreJobFromXml(parser);
if (persistedJob != null) {
if (DEBUG) {
Slog.d(TAG, "Read out " + persistedJob);
}
jobs.add(persistedJob); // 加入到缓存集合中,最后返回!
} else {
Slog.d(TAG, "Error reading job from file.");
}
}
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
return jobs;
}
return null;
}
这里是通过循环处理 jobs.xml 文件中的标签
1.1.3 restoreJobFromXml
这个方法负责解析每个<job>标签,返回每个任务对应的 JobStatus。
注意这里只有重启设备后需要保留的 Job,才会写入 jobs.xml 文件中,即:这个 Job 是 isPersisted 的!!
private JobStatus restoreJobFromXml(XmlPullParser parser) throws XmlPullParserException,
IOException {
JobInfo.Builder jobBuilder;
int uid, sourceUserId;
// Read out job identifier attributes and priority.
try {
jobBuilder = buildBuilderFromXml(parser);
jobBuilder.setPersisted(true);
// 解析基本信息: uid、priority、flags 和 sourceUserId !
uid = Integer.parseInt(parser.getAttributeValue(null, "uid"));
String val = parser.getAttributeValue(null, "priority");
if (val != null) {
jobBuilder.setPriority(Integer.parseInt(val));
}
val = parser.getAttributeValue(null, "flags");
if (val != null) {
jobBuilder.setFlags(Integer.parseInt(val));
}
val = parser.getAttributeValue(null, "sourceUserId");
sourceUserId = val == null ? -1 : Integer.parseInt(val);
} catch (NumberFormatException e) {
Slog.e(TAG, "Error parsing job's required fields, skipping");
return null;
}
// 解析获得 sourcePackageName 和 sourceTag!
String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
final String sourceTag = parser.getAttributeValue(null, "sourceTag");
int eventType;
// Read out constraints tag.
do {
eventType = parser.next();
} while (eventType == XmlPullParser.TEXT); // Push through to next START_TAG.
if (!(eventType == XmlPullParser.START_TAG &&
XML_TAG_PARAMS_CONSTRAINTS.equals(parser.getName()))) {
// Expecting a <constraints> start tag.
return null;
}
try {
buildConstraintsFromXml(jobBuilder, parser);
} catch (NumberFormatException e) {
Slog.d(TAG, "Error reading constraints, skipping.");
return null;
}
parser.next(); // Consume </constraints>
// Read out execution parameters tag.
do {
eventType = parser.next();
} while (eventType == XmlPullParser.TEXT);
if (eventType != XmlPullParser.START_TAG) {
return null;
}
// Tuple of (earliest runtime, latest runtime) in elapsed realtime after disk load.
Pair<Long, Long> elapsedRuntimes;
try {
elapsedRuntimes = buildExecutionTimesFromXml(parser);
} catch (NumberFormatException e) {
if (DEBUG) {
Slog.d(TAG, "Error parsing execution time parameters, skipping.");
}
return null;
}
final long elapsedNow = SystemClock.elapsedRealtime();
if (XML_TAG_PERIODIC.equals(parser.getName())) {
try {
String val = parser.getAttributeValue(null, "period");
final long periodMillis = Long.valueOf(val);
val = parser.getAttributeValue(null, "flex");
final long flexMillis = (val != null) ? Long.valueOf(val) : periodMillis;
jobBuilder.setPeriodic(periodMillis, flexMillis);
// As a sanity check, cap the recreated run time to be no later than flex+period
// from now. This is the latest the periodic could be pushed out. This could
// happen if the periodic ran early (at flex time before period), and then the
// device rebooted.
if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
+ periodMillis;
final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
- flexMillis;
... ... ... ...
elapsedRuntimes =
Pair.create(clampedEarlyRuntimeElapsed, clampedLateRuntimeElapsed);
}
} catch (NumberFormatException e) {
Slog.d(TAG, "Error reading periodic execution criteria, skipping.");
return null;
}
} else if (XML_TAG_ONEOFF.equals(parser.getName())) {
try {
if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
}
if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
jobBuilder.setOverrideDeadline(
elapsedRuntimes.second - elapsedNow);
}
} catch (NumberFormatException e) {
Slog.d(TAG, "Error reading job execution criteria, skipping.");
return null;
}
} else {
if (DEBUG) {
Slog.d(TAG, "Invalid parameter tag, skipping - " + parser.getName());
}
// Expecting a parameters start tag.
return null;
}
maybeBuildBackoffPolicyFromXml(jobBuilder, parser);
parser.nextTag(); // Consume parameters end tag.
// Read out extras Bundle.
do {
eventType = parser.next();
} while (eventType == XmlPullParser.TEXT);
if (!(eventType == XmlPullParser.START_TAG
&& XML_TAG_EXTRAS.equals(parser.getName()))) {
if (DEBUG) {
Slog.d(TAG, "Error reading extras, skipping.");
}
return null;
}
PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
jobBuilder.setExtras(extras);
parser.nextTag(); // Consume </extras>
// Migrate sync jobs forward from earlier, incomplete representation
if ("android".equals(sourcePackageName)
&& extras != null
&& extras.getBoolean("SyncManagerJob", false)) {
sourcePackageName = extras.getString("owningPackage", sourcePackageName);
if (DEBUG) {
Slog.i(TAG, "Fixing up sync job source package name from 'android' to '"
+ sourcePackageName + "'");
}
}
// And now we're done
JobStatus js = new JobStatus(
jobBuilder.build(), uid, sourcePackageName, sourceUserId, sourceTag,
elapsedRuntimes.first, elapsedRuntimes.second);
return js;
}
这里面以后很多的细节,继续看:
1.1.3.1 buildBuilderFromXml
用来创建 JobInfo:
private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
// Pull out required fields from <job> attributes.
int jobId = Integer.parseInt(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);
}
这里使用了建造者模式:
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;
public Builder(int jobId, ComponentName jobService) {
mJobService = jobService;
mJobId = jobId;
}
... ... ... ...
}
返回了 JobInfo 的内部类 Builder 的对象!
1.1.3.2 buildConstraintsFromXml
传入参数:JobInfo.Buidlder 和 XmlPullParser :
private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
String val = parser.getAttributeValue(null, "connectivity");
if (val != null) {
jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
}
val = parser.getAttributeValue(null, "unmetered");
if (val != null) {
jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
}
val = parser.getAttributeValue(null, "not-roaming");
if (val != null) {
jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
}
val = parser.getAttributeValue(null, "idle");
if (val != null) {
jobBuilder.setRequiresDeviceIdle(true);
}
val = parser.getAttributeValue(null, "charging");
if (val != null) {
jobBuilder.setRequiresCharging(true);
}
}
显然,这个方法也很简单,解析剩下的参数!
1.1.3.3 buildExecutionTimesFromXml
计算这个 Job 的最早运行时间和最晚的结束时间:
private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser)
throws NumberFormatException {
// Pull out execution time data.
final long nowWallclock = System.currentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
long earliestRunTimeElapsed = JobStatus.NO_EARLIEST_RUNTIME;
long latestRunTimeElapsed = JobStatus.NO_LATEST_RUNTIME;
String val = parser.getAttributeValue(null, "deadline");
if (val != null) {
long latestRuntimeWallclock = Long.valueOf(val);
long maxDelayElapsed =
Math.max(latestRuntimeWallclock - nowWallclock, 0);
latestRunTimeElapsed = nowElapsed + maxDelayElapsed;
}
val = parser.getAttributeValue(null, "delay");
if (val != null) {
long earliestRuntimeWallclock = Long.valueOf(val);
long minDelayElapsed =
Math.max(earliestRuntimeWallclock - nowWallclock, 0);
earliestRunTimeElapsed = nowElapsed + minDelayElapsed;
}
return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
}
}
接着来看:
1.1.3.4 maybeBuildBackoffPolicyFromXml
/**
* Builds the back-off policy out of the params tag. These attributes may not exist, depending
* on whether the back-off was set when the job was first scheduled.
*/
private void maybeBuildBackoffPolicyFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
String val = parser.getAttributeValue(null, "initial-backoff");
if (val != null) {
long initialBackoff = Long.valueOf(val);
val = parser.getAttributeValue(null, "backoff-policy");
int backoffPolicy = Integer.parseInt(val); // Will throw NFE which we catch higher up.
jobBuilder.setBackoffCriteria(initialBackoff, backoffPolicy);
}
}
接着:
1.1.3.5 PersistableBundle.restoreFromXml
/** @hide */
public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
XmlPullParserException {
final int outerDepth = in.getDepth();
final String startTag = in.getName();
final String[] tagName = new String[1];
int event;
while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
(event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
if (event == XmlPullParser.START_TAG) {
return new PersistableBundle((ArrayMap<String, Object>)
XmlUtils.readThisArrayMapXml(in, startTag, tagName,
new MyReadMapCallback()));
}
}
return EMPTY;
}
接着:
1.1.3.6 JobInfo.buidler.build
根据解析的结果,创建 JobInfo 对象,这里调用了 JobInfo.buidler 的 build 方法:
... ... ... ...
public JobInfo build() {
// Allow jobs with no constraints - What am I, a database?
if (!mHasEarlyConstraint && !mHasLateConstraint && !mRequiresCharging &&
!mRequiresDeviceIdle && mNetworkType == NETWORK_TYPE_NONE &&
mTriggerContentUris == null) {
throw new IllegalArgumentException("You're trying to build a job with no " +
"constraints, this is not allowed.");
}
mExtras = new PersistableBundle(mExtras); // Make our own copy.
// Check that a deadline was not set on a periodic job.
if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
"periodic job.");
}
if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
"periodic job");
}
if (mIsPeriodic && (mTriggerContentUris != null)) {
throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
"periodic job");
}
if (mIsPersisted && (mTriggerContentUris != null)) {
throw new IllegalArgumentException("Can't call addTriggerContentUri() on a " +
"persisted job");
}
if (mBackoffPolicySet && mRequiresDeviceIdle) {
throw new IllegalArgumentException("An idle mode job will not respect any" +
" back-off policy, so calling setBackoffCriteria with" +
" setRequiresDeviceIdle is an error.");
}
JobInfo job = new JobInfo(this);
if (job.isPeriodic()) {
if (job.intervalMillis != job.getIntervalMillis()) {
StringBuilder builder = new StringBuilder();
builder.append("Specified interval for ")
.append(String.valueOf(mJobId))
.append(" is ");
formatDuration(mIntervalMillis, builder);
builder.append(". Clamped to ");
formatDuration(job.getIntervalMillis(), builder);
Log.w(TAG, builder.toString());
}
if (job.flexMillis != job.getFlexMillis()) {
StringBuilder builder = new StringBuilder();
builder.append("Specified flex for ")
.append(String.valueOf(mJobId))
.append(" is ");
formatDuration(mFlexMillis, builder);
builder.append(". Clamped to ");
formatDuration(job.getFlexMillis(), builder);
Log.w(TAG, builder.toString());
}
}
return job;
}
... ... ... ...
这里返回了 JobInfo 对象!
1.1.3.7 new JobStatus
最后,根据创建的 JobInfo 对象和前面的解析结果,创建 JobStatus 对象!
public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId,
String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis);
}
这个构造器调用了下面这个:
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis,
long latestRunTimeElapsedMillis) {
this.job = job;
this.callingUid = callingUid;
int tempSourceUid = -1;
if (sourceUserId != -1 && sourcePackageName != null) {
try {
tempSourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0,
sourceUserId);
} catch (RemoteException ex) {
// Can't happen, PackageManager runs in the same process.
}
}
if (tempSourceUid == -1) {
this.sourceUid = callingUid;
this.sourceUserId = UserHandle.getUserId(callingUid);
this.sourcePackageName = job.getService().getPackageName();
this.sourceTag = null;
} else {
this.sourceUid = tempSourceUid;
this.sourceUserId = sourceUserId;
this.sourcePackageName = sourcePackageName;
this.sourceTag = tag;
}
this.batteryName = this.sourceTag != null
? this.sourceTag + ":" + job.getService().getPackageName()
: job.getService().flattenToShortString();
this.tag = "*job*/" + this.batteryName;
this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.numFailures = numFailures;
int requiredConstraints = 0;
if (job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY) {
requiredConstraints |= CONSTRAINT_CONNECTIVITY;
}
if (job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED) {
requiredConstraints |= CONSTRAINT_UNMETERED;
}
if (job.getNetworkType() == JobInfo.NETWORK_TYPE_NOT_ROAMING) {
requiredConstraints |= CONSTRAINT_NOT_ROAMING;
}
if (job.isRequireCharging()) {
requiredConstraints |= CONSTRAINT_CHARGING;
}
if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_TIMING_DELAY;
}
if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_DEADLINE;
}
if (job.isRequireDeviceIdle()) {
requiredConstraints |= CONSTRAINT_IDLE;
}
if (job.getTriggerContentUris() != null) {
requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
}
this.requiredConstraints = requiredConstraints;
}
暂时看到这里!
1.2 JobHandler
接下来,我们来看看 JobHander 的消息处理机制:
private class JobHandler extends Handler {
public JobHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message message) {
synchronized (mLock) {
// 在系统刚刚启动的时候,mReadyToRock 的值为 false,当系统启动到 phase 600,则 mReadyToRock=true.
if (!mReadyToRock) {
return;
}
}
switch (message.what) {
case MSG_JOB_EXPIRED:
synchronized (mLock) {
JobStatus runNow = (JobStatus) message.obj;
// runNow can be null, which is a controller's way of indicating that its
// state is such that all ready jobs should be run immediately.
if (runNow != null && !mPendingJobs.contains(runNow)
&& mJobs.containsJob(runNow)) {
mJobPackageTracker.notePending(runNow);
mPendingJobs.add(runNow);
}
queueReadyJobsForExecutionLockedH();
}
break;
case MSG_CHECK_JOB: // 检查任务
synchronized (mLock) {
if (mReportedActive) {
// if jobs are currently being run, queue all ready jobs for execution.
queueReadyJobsForExecutionLockedH();
} else {
// Check the list of jobs and run some of them if we feel inclined.
maybeQueueReadyJobsForExecutionLockedH();
}
}
break;
case MSG_CHECK_JOB_GREEDY:
synchronized (mLock) {
queueReadyJobsForExecutionLockedH();
}
break;
case MSG_STOP_JOB: // 停止任务
cancelJobImpl((JobStatus)message.obj, null);
break;
}
maybeRunPendingJobsH();
// Don't remove JOB_EXPIRED in case one came along while processing the queue.
removeMessages(MSG_CHECK_JOB);
}
... ... ... ...
private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
... ... ... ...
private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
... ... ... ...
}
这里,我们省略了一些方法,以后再慢慢分析!
1.3 Controllers
上面我们总结了 7.0 所有的控制器,下面,我们一个一个来看!
1.3.1 ConnectivityController
我们先来看一个控制器 ConnectivityController,先来看看他的代码:
public class ConnectivityController extends StateController implements
ConnectivityManager.OnNetworkActiveListener {
private static final String TAG = "JobScheduler.Conn";
private final ConnectivityManager mConnManager;
private final NetworkPolicyManager mNetPolicyManager;
@GuardedBy("mLock")
private final ArrayList<JobStatus> mTrackedJobs = new ArrayList<JobStatus>();
/** Singleton. */
private static ConnectivityController mSingleton;
private static Object sCreationLock = new Object();
public static ConnectivityController get(JobSchedulerService jms) {
synchronized (sCreationLock) {
if (mSingleton == null) {
mSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
}
return mSingleton;
}
}
private ConnectivityController(StateChangedListener stateChangedListener, Context context,
Object lock) {
super(stateChangedListener, context, lock);
mConnManager = mContext.getSystemService(ConnectivityManager.class);
mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
// 注册监听网络连接状态的广播,且采用 BackgroundThread 线程
final IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiverAsUser(
mConnectivityReceiver, UserHandle.SYSTEM, intentFilter, null, null);
mNetPolicyManager.registerListener(mNetPolicyListener);
}
当监听到 CONNECTIVITY_ACTION 广播,onReceive 方法的执行位于 “android.bg” 线程。
其他的控制器很类似,我们在后面的文章中,会集中分析这些控制器!
1.4 JobSchedulerStub
JobSchedulerStub 继承了 IJobScheduler.Stub,他是作为 Binder 机制的服务端:
final class JobSchedulerStub extends IJobScheduler.Stub {
/** Cache determination of whether a given app can persist jobs
* key is uid of the calling app; value is undetermined/true/false
*/
private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
// Enforce that only the app itself (or shared uid participant) can schedule a
// job that runs one of the app's services, as well as verifying that the
// named service properly requires the BIND_JOB_SERVICE permission
private void enforceValidJobRequest(int uid, JobInfo job) {
... ... ... ... ...
}
private boolean canPersistJobs(int pid, int uid) {
... ... ... ... ...
}
// IJobScheduler implementation
@Override
public int schedule(JobInfo job) throws RemoteException {
... ... ... ... ...
}
@Override
public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
throws RemoteException {
... ... ... ... ...
}
@Override
public List<JobInfo> getAllPendingJobs() throws RemoteException {
... ... ... ... ...
}
@Override
public JobInfo getPendingJob(int jobId) throws RemoteException {
... ... ... ... ...
}
@Override
public void cancelAll() throws RemoteException {
... ... ... ... ...
}
@Override
public void cancel(int jobId) throws RemoteException {
... ... ... ... ...
}
/**
* "dumpsys" infrastructure
*/
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
... ... ... ... ...
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ResultReceiver resultReceiver) throws RemoteException {
... ... ... ... ...
}
};
我们来分析上面的方法:
1.4.1 enforceValidJobRequest
当一个应用想通过 JobScheduler 来注册一个任务的时候,需要实现一个服务:JobService,当任务注册到 JobScheduler 后,JobScheduler 通过注册时,设置的条件,当满足条件时,就调用 onJobStart 方法拉起 JobService 来执行任务;当不满足条件,就调用 onJobStop 方法,挂起 JobService 来停止任务!
// Enforce that only the app itself (or shared uid participant) can schedule a
// job that runs one of the app's services, as well as verifying that the
// named service properly requires the BIND_JOB_SERVICE permission
private void enforceValidJobRequest(int uid, JobInfo job) {
final IPackageManager pm = AppGlobals.getPackageManager();
final ComponentName service = job.getService();
try {
ServiceInfo si = pm.getServiceInfo(service,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
UserHandle.getUserId(uid));
if (si == null) {
throw new IllegalArgumentException("No such service " + service);
}
if (si.applicationInfo.uid != uid) {
throw new IllegalArgumentException("uid " + uid +
" cannot schedule job in " + service.getPackageName());
}
if (!JobService.PERMISSION_BIND.equals(si.permission)) {
throw new IllegalArgumentException("Scheduled service " + service
+ " does not require android.permission.BIND_JOB_SERVICE permission");
}
} catch (RemoteException e) {
// Can't happen; the Package Manager is in this same process
}
}
这个方法主要的作用是校验如下几项:
1、是否有这个 JobService,
2、JobService 的 uid 是否等于调用者的 uid,就是说,这个 JobService 是否属于这个应用!
2、JobService 是否在 AndroidManifest.xml 文件中配置了相应的权限:
1.4.2 canPersistJobs
判断应用是否有 android.Manifest.permission.RECEIVE_BOOT_COMPLETED 的权限,如果有这个权限的话,设备重启后,能够保留该应用的任务!
private boolean canPersistJobs(int pid, int uid) {
// If we get this far we're good to go; all we need to do now is check
// whether the app is allowed to persist its scheduled work.
final boolean canPersist;
synchronized (mPersistCache) {
Boolean cached = mPersistCache.get(uid);
if (cached != null) {
canPersist = cached.booleanValue();
} else {
// Persisting jobs is tantamount to running at boot, so we permit
// it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
// permission
int result = getContext().checkPermission(
android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
canPersist = (result == PackageManager.PERMISSION_GRANTED);
mPersistCache.put(uid, canPersist);
}
}
return canPersist;
}
最终的结果会保存、在 mPersistCache 集合中!
1.4.3 schedule
这个方法是我们最常用的,来注册Job的!
// 我们是实际上调用的是这个方法!
@Override
public int schedule(JobInfo job) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "Scheduling job: " + job.toString());
}
// 获得调用方的 pid 和 uid!
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
// 校验操作
enforceValidJobRequest(uid, job);
// 如果这个Job是重启后可保留的,检查权限!
if (job.isPersisted()) {
if (!canPersistJobs(pid, uid)) {
throw new IllegalArgumentException("Error: requested job be persisted without"
+ " holding RECEIVE_BOOT_COMPLETED permission.");
}
}
long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.schedule(job, uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
最后 调用服务端的 schedule 方法继续注册!
1.4.4 scheduleAsPackage
接下来,我们来看一个和 schedule 方法很类似的方法:
@Override
public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
throws RemoteException {
final int callerUid = Binder.getCallingUid();
if (DEBUG) {
Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
+ " on behalf of " + packageName);
}
if (packageName == null) {
throw new NullPointerException("Must specify a package for scheduleAsPackage()");
}
int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
android.Manifest.permission.UPDATE_DEVICE_STATS);
if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller uid " + callerUid
+ " not permitted to schedule jobs for other apps");
}
if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
}
long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
packageName, userId, tag);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
最后 调用服务端的 scheduleAsPackage 方法继续注册!
1.4.5 getXXXPendingJobs
下面来看这两个方法:
getAllPendingJobs:获得应用注册的所有的正在等待的 Job!
getPendingJob:获得应用程序注册的 jobId 指定的正在等待的 Job!
@Override
public List<JobInfo> getAllPendingJobs() throws RemoteException {
final int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.getPendingJobs(uid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public JobInfo getPendingJob(int jobId) throws RemoteException {
final int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.getPendingJob(uid, jobId);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
最后 调用服务端的 scheduleAsPackage 方法继续get!
1.4.6 cancelXXX
接着是取消任务的相关方法:
cancelAll:取消应用注册的所有任务!
cancel:取消 jobId 对应的任务!
@Override
public void cancelAll() throws RemoteException {
final int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJobsForUid(uid, true);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void cancel(int jobId) throws RemoteException {
final int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJob(uid, jobId);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
最后分别调用的是客户端的 cancelJobForUid 方法和 cancelJob 方法!
1.4.7 dump
这个方法是用来为控制台 dumpsys 命令提供数据的:
/**
* "dumpsys" infrastructure
*/
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
long identityToken = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.dumpInternal(pw, args);
} finally {
Binder.restoreCallingIdentity(identityToken);
}
}
接着看:
1.4.8 onShellCommand
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ResultReceiver resultReceiver) throws RemoteException {
(new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
this, in, out, err, args, resultReceiver);
}
2 onStart
在 onStart 方法中,又调用了如下的方法:
@Override
public void onStart() {
publishLocalService(JobSchedulerInternal.class, new LocalService());
publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
}
主要有两个作用:
公开本地服务:LocalService,类型为 JobSchedulerInternal.class,只能被系统进程访问。
公开Binder通信服务,名称为:jobscheduler,服务的实体对象是:mJobSchedulerStub,实现了 IJobScheduler.Stub 接口,其实就是将 JobSchedulerService 自身注册进 SystemManager 中,是其能被其他服务和应用访问到!
接下来,我们一个一个来看:
2.1 publishLocalService
这个方法是继承自 SystemService 的:
/**
* Publish the service so it is only accessible to the system process.
*/
protected final <T> void publishLocalService(Class<T> type, T service) {
LocalServices.addService(type, service);
}
调用了 LocalServices 的静态方法,addService:
public final class LocalServices {
private LocalServices() {}
private static final ArrayMap<Class<?>, Object> sLocalServiceObjects =
new ArrayMap<Class<?>, Object>();
... ... ... ...
/**
* Adds a service instance of the specified interface to the global registry of local services.
*/
public static <T> void addService(Class<T> type, T service) {
synchronized (sLocalServiceObjects) {
if (sLocalServiceObjects.containsKey(type)) {
throw new IllegalStateException("Overriding service registration");
}
sLocalServiceObjects.put(type, service);
}
}
... ... ... ...
}
接着来看:
2.2 publishBinderService
这个方法也是继承了 SystemServer 的方法:
/**
* Publish the service so it is accessible to other services and apps.
*/
protected final void publishBinderService(String name, IBinder service) {
publishBinderService(name, service, false);
}
/**
* Publish the service so it is accessible to other services and apps.
*/
protected final void publishBinderService(String name, IBinder service,
boolean allowIsolated) {
ServiceManager.addService(name, service, allowIsolated);
}
其实就是将自身加入到 SystemManager 中去!
3 onBootPhase
设备开机的时候,会调用这个方法:
@Override
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) { // 500:表示系统服务准备就绪。
mConstants.start(getContext().getContentResolver());
// Register br for package removals and user removals.
// 注册第一个 BroadcastReceiver,监听 package 的操作!
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);
// 注册第二个 BroadcastReceiver,监听 user 的移除操作!
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 {
// 注册 uid 观察者,用于回调!
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) { // 600:表示服务可以被 start 和 binder 到第三方 app。
synchronized (mLock) {
// Let's go!
mReadyToRock = true; // JobSchedulerService 已经准备好了!
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
// 获得本地设备控制器!
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
// Create the "runners".
// 最多允许一次执行 16 个任务
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
// 需要被执行的任务,都会保存到 JobServiceContext 对象中!
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!
// 发送 MSG_CHECK_JOB 消息给 JobHandler,检查任务
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
}
}
这里的是根据开机的阶段,进行不同的初始化操作!
3.1 JobServiceContext
创建 JobServiceContext 对象,参数传递:
Context context: JobSchedulerService.getContext。
Object lock: JobSchedulerService.getObject。
IBatteryStats batteryStats: JobSchedulerService.mBatteryStats。
JobPackageTracker tracker: JobSchedulerService.tracker。
JobCompletedListener completedListener: JobSchedulerService,其本身实现了这个接口。
Looper looper: getContext().getMainLooper(),JobSchedulerService 所在主线程的 Looper。
JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
JobPackageTracker tracker, Looper looper) {
this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
}
@VisibleForTesting
JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
mContext = context;
mLock = lock;
mBatteryStats = batteryStats;
mJobPackageTracker = tracker;
mCallbackHandler = new JobServiceHandler(looper);
mCompletedListener = completedListener;
mAvailable = true;
mVerb = VERB_FINISHED;
mPreferredUid = NO_PREFERRED_UID;
}
此处的 JobServiceHandler 采用的是 system_server 进程的主线程。
3.1.1 JobServiceHandler
每一个 JobServiceContext 都有一个 JobServiceHandler 对象,JobSchedulerService 通过这个 handler 对象向 JSC 发送消息!
/**
* Handles the lifecycle of the JobService binding/callbacks, etc. The convention within this
* class is to append 'H' to each function name that can only be called on this handler. This
* isn't strictly necessary because all of these functions are private, but helps clarity.
*/
private class JobServiceHandler extends Handler {
JobServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_SERVICE_BOUND:
removeOpTimeOut();
handleServiceBoundH();
break;
case MSG_CALLBACK:
if (DEBUG) {
Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob
+ " v:" + VERB_STRINGS[mVerb]);
}
removeOpTimeOut();
if (mVerb == VERB_STARTING) {
final boolean workOngoing = message.arg2 == 1;
handleStartedH(workOngoing);
} else if (mVerb == VERB_EXECUTING ||
mVerb == VERB_STOPPING) {
final boolean reschedule = message.arg2 == 1;
handleFinishedH(reschedule);
} else {
if (DEBUG) {
Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
}
}
break;
case MSG_CANCEL:
if (mVerb == VERB_FINISHED) {
if (DEBUG) {
Slog.d(TAG,
"Trying to process cancel for torn-down context, ignoring.");
}
return;
}
mParams.setStopReason(message.arg1);
if (message.arg1 == JobParameters.REASON_PREEMPT) {
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
NO_PREFERRED_UID;
}
handleCancelH();
break;
case MSG_TIMEOUT:
handleOpTimeoutH();
break;
case MSG_SHUTDOWN_EXECUTION:
closeAndCleanupJobH(true /* needsReschedule */);
break;
default:
Slog.e(TAG, "Unrecognised message: " + message);
}
}
... ... ... ... ...
}
啦啦啦啦
4 总结
- JSS.JobHandler:运行在 system_server 进程的主线程;
- JobServiceContext.JobServiceHandler:运行在 system_server 进程的主线程;
- JobSchedulerStub:作为实现接口 IJobScheduler 的 binder 服务端;
- JobStore:其成员变量 mIoHandler 运行在 "android.io" 线程;
- JobStatus:JSS 从/data/system/job/jobs.xml 文件中读取每个 JobInfo,再解析成 JobStatus 对象,添加到 mJobSet。
可见 JobSchedulerService 启动过程,最主要工作是从 jobs.xml 文件收集所有的需要重启保留的 jobs,放入到 JobStore 的成员变量 mJobSet。