一、调用流程
Builder builder = new Builder(jobId, new ComponentName(context, JobSchedulerService.class));
builder.setMinimumLatency(30*60*1000L);
builder.setPersisted(true);
JobScheduler jobScheduler = (JobScheduler)context.getSystemService("jobscheduler");
int result = jobScheduler.schedule(builder.build());
二、JobInfo 源码
/**
* 第一版的 JobSchedulerService 的处理比较简单
* Specify that this job should recur with the provided interval, not more than once per
* period. You have no control over when within this interval this job will be executed,
* only the guarantee that it will be executed at most once within this interval.
* Setting this function on the builder with {@link #setMinimumLatency(long)} or
* {@link #setOverrideDeadline(long)} will result in an error.
* @param intervalMillis Millisecond interval for which this job will repeat.
*/
public JobInfo.Builder setPeriodic(long intervalMillis) {
mIsPeriodic = true;
mIntervalMillis = intervalMillis;
mHasEarlyConstraint = mHasLateConstraint = true;
return this;
}
三、jobScheduler.schedule(JobInfo)流程
public abstract int schedule(JobInfo job);
|
JobSchedulerImpl.schedule(job);
|
public int schedule(JobInfo job) {
try {
return mBinder.schedule(job);
} catch (RemoteException e) {
return JobScheduler.RESULT_FAILURE;
}
}
|
IJobScheduler mBinder 进程间通讯
|
JobSchedulerStub extends IJobScheduler.Stub (in JobSchedulerService)
|
@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();
/**
* 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
*/
enforceValidJobRequest(uid, 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);
}
}
四、JobSchedulerService.schedule(job, uid)流程
/**
* Entry point from client to schedule the provided job.
* This cancels the job if it's already been scheduled, and replaces it with the one provided.
* @param job JobInfo object containing execution parameters
* @param uId The package identifier of the application this job is for.
* @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
*/
public int schedule(JobInfo job, int uId) {
JobStatus jobStatus = new JobStatus(job, uId);
cancelJob(uId, job.getId());
startTrackingJob(jobStatus);
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
return JobScheduler.RESULT_SUCCESS;
}
1. 根据传入的 JobInfo 创建 JobStatus
/** Create a newly scheduled job. */
public JobStatus(JobInfo job, int uId) {
this(job, uId, 0);
final long elapsedNow = SystemClock.elapsedRealtime();
if (job.isPeriodic()) {
// 第一版的此处的处理比较简单
earliestRunTimeElapsedMillis = elapsedNow;
latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
} else {
earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ? elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
latestRunTimeElapsedMillis = job.hasLateConstraint() ? elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
}
}
2. 从磁盘及 Controller 中移除 Job, 若在执行则取消
/**
* Entry point from client to cancel the job corresponding to the jobId provided.
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
* @param uid Uid of the calling client.
* @param jobId Id of the job, provided at schedule-time.
*/
public void cancelJob(int uid, int jobId) {
JobStatus toCancel;
synchronized (mJobs) {
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
}
if (toCancel != null) {
cancelJobImpl(toCancel);
}
}
|
|
private void cancelJobImpl(JobStatus cancelled) {
if (DEBUG) {
Slog.d(TAG, "Cancelling: " + cancelled);
}
// Remove from store as well as controllers.
stopTrackingJob(cancelled);
synchronized (mJobs) {
// Remove from pending queue.
mPendingJobs.remove(cancelled);
// Cancel if running.
stopJobOnServiceContextLocked(cancelled);
}
}
3. 添加入追踪列表 mJobs,并通知各 Controller 追踪该 Job
/**
* Called when we have a job status object that we need to insert in our
* {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
* about.
*/
private void startTrackingJob(JobStatus jobStatus) {
boolean update;
boolean rocking;
synchronized (mJobs) {
/**
* Add a job to the master list, persisting it if necessary.
* If the JobStatus already exists, it will be replaced.
*/
update = mJobs.add(jobStatus);
rocking = mReadyToRock;
}
if (rocking) {
for (int i=0; i<mControllers.size(); i++) {
StateController controller = mControllers.get(i);
if (update) {
controller.maybeStopTrackingJob(jobStatus);
}
controller.maybeStartTrackingJob(jobStatus);
}
}
}
4、JobHandler 处理 MSG_CHECK_JOB 消息流程
private class JobHandler extends Handler{
public void handleMessage(Message message) {
switch (message.what) {
case MSG_CHECK_JOB:
synchronized (mJobs) {
// Check the list of jobs and run some of them if we feel inclined.
// 检查所有满足执行条件的 Job,根据策略决定是否放入 mPendingJobs,随后执行 mPendingJobs 中的 Job
maybeQueueReadyJobsForExecutionLockedH();
}
break;
}
// 从 mPendingJobs 中取出 Job 执行
maybeRunPendingJobsH();
// Don't remove JOB_EXPIRED in case one came along while processing the queue.
removeMessages(MSG_CHECK_JOB);
}
}
5. 检查所有满足执行条件的 Job,根据策略决定是否放入 mPendingJobs
/**
* The state of at least one job has changed. Here is where we could enforce various
* policies on when we want to execute jobs.
* Right now the policy is such:
* If >1 of the ready jobs is idle mode we send all of them off
* if more than 2 network connectivity jobs are ready we send them all off.
* If more than 4 jobs total are ready we send them all off.
* TODO: It would be nice to consolidate these sort of high-level policies somewhere.
* 检查所有满足执行条件的 Job,根据策略决定是否放入 mPendingJobs,随后执行 mPendingJobs 中的 Job
*/
private void maybeQueueReadyJobsForExecutionLockedH() {
int chargingCount = 0;
int idleCount = 0;
int backoffCount = 0;
int connectivityCount = 0;
List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
ArraySet<JobStatus> jobs = mJobs.getJobs();
for (int i=0; i<jobs.size(); i++) {
JobStatus job = jobs.valueAt(i);
if (isReadyToBeExecutedLocked(job)) {
if (job.getNumFailures() > 0) {
backoffCount++;
}
if (job.hasIdleConstraint()) {
idleCount++;
}
if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
connectivityCount++;
}
if (job.hasChargingConstraint()) {
chargingCount++;
}
// 把已经满足执行条件的 job 加入 runnableJobs
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) {
// 注意上面的判断条件,很重要,不满足上面的条件,就不会有 Job 被加入到 mPendingJobs
for (int i = 0; i < runnableJobs.size(); i++) {
// 把满足执行条件的 job 加入 mPendingJobs
mPendingJobs.add(runnableJobs.get(i));
}
}
}
|
|
/**
* Criteria for moving a job into the pending queue:
* - It's ready.
* - It's not pending.
* - It's not already running on a JSC.
* - The user that requested the job is running.
* 从这个条件看 JobScheduler 只在 App 进程活着的时候起作用?注意是 Uid(App 安装时确定), 不是Pid
*/
private boolean isReadyToBeExecutedLocked(JobStatus job) {
final boolean jobReady = job.isReady(); // deadlineConstraintSatisfied 得到满足或者其余约束条件都已经得到满足
final boolean jobPending = mPendingJobs.contains(job);
final boolean jobActive = isCurrentlyActiveLocked(job);
final boolean userRunning = mStartedUsers.contains(job.getUserId());
if (DEBUG) {
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
+ " ready=" + jobReady + " pending=" + jobPending
+ " active=" + jobActive + " userRunning=" + userRunning);
}
return userRunning && jobReady && !jobPending && !jobActive;
}
6. 从 mPendingJobs 中取出 Job 执行
/**
* Reconcile jobs in the pending queue against available execution contexts.
* A controller can force a job into the pending queue even if it's already running, but
* here is where we decide whether to actually execute it.
* 从 mPendingJobs 中取出 Job 执行
*/
private void maybeRunPendingJobsH() {
synchronized (mJobs) {
// 从 mPendingJobs 中取出 Job 执行
Iterator<JobStatus> it = mPendingJobs.iterator();
while (it.hasNext()) {
JobStatus nextPending = it.next();
JobServiceContext availableContext = null;
// mActiveServices.size() == MAX_JOB_CONTEXTS_COUNT == 3
for (int i=0; i<mActiveServices.size(); i++) {
JobServiceContext jsc = mActiveServices.get(i);
final JobStatus running = jsc.getRunningJob();
if (running != null && running.matches(nextPending.getUid(), nextPending.getJobId())) {
// Already running this job for this uId, skip.
availableContext = null;
break;
}
// 判断该 jsc 是否已被占用
if (jsc.isAvailable()) {
availableContext = jsc;
}
}
if (availableContext != null) {
// 真正执行 Job 的地方
if (!availableContext.executeRunnableJob(nextPending)) {
mJobs.remove(nextPending);
}
it.remove();
}
}
}
}
五、JobServiceContext 执行 Job 流程
/**
* Give a job to this context for execution. Callers must first check {@link #isAvailable()}
* to make sure this is a valid context.
* @param job The status of the job that we are going to run.
* @return True if the job is valid and is running. False if the job cannot be executed.
*/
boolean executeRunnableJob(JobStatus job) {
synchronized (mLock) {
if (!mAvailable) {
Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
return false;
}
mRunningJob = job;
mParams = new JobParameters(this, job.getJobId(), job.getExtras(), !job.isConstraintsSatisfied());
mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
mVerb = VERB_BINDING;
scheduleOpTimeOut();
final Intent intent = new Intent().setComponent(job.getServiceComponent());
boolean binding = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
new UserHandle(job.getUserId()));
if (!binding) {
if (DEBUG) {
Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
}
mRunningJob = null;
mParams = null;
mExecutionStartTimeElapsed = 0L;
removeOpTimeOut();
return false;
}
try {
mBatteryStats.noteJobStart(job.getName(), job.getUid());
} catch (RemoteException e) {
// Whatever.
}
mAvailable = false;
return true;
}
}
七、ContextImp bindServiceAsUser() 流程
@Override
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user) {
return bindServiceCommon(service, conn, flags, user);
}
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
UserHandle user) {
IServiceConnection sd;
if (conn == null) {
throw new IllegalArgumentException("connection is null");
}
if (mPackageInfo != null) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
mMainThread.getHandler(), flags);
} else {
throw new RuntimeException("Not supported in system context");
}
validateServiceIntent(service);
try {
IBinder token = getActivityToken();
if (token == null && (flags & BIND_AUTO_CREATE) == 0 && mPackageInfo != null
&& mPackageInfo.getApplicationInfo().targetSdkVersion
< android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
flags |= BIND_WAIVE_PRIORITY;
}
service.prepareToLeaveProcess();
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(),
service, service.resolveTypeIfNeeded(getContentResolver()),
sd, flags, user.getIdentifier());
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
}
return res != 0;
} catch (RemoteException e) {
return false;
}
}
网友评论