JobSchedulerService
一、使用JobIntentService 就是调用的JobSchedulerService
1、xxxService继承JobIntentService后,在外面调用 xxxService 的enqueueWork
Intent intent = new Intent(Main.this, xxxService.class);
//在这里用startService方法,而enqueueWork方法最后会让JobSchedulerService bind这个服务
// 而startService 和 bindService 同时调用会让这个service在后台长期存在
//当然在这里也可以不需要startServiceAsUser 那么JobSchedulerService 每次调用的都是新创建的service
startServiceAsUser(intent,UserHandle.CURRENT);
xxxService.enqueueWork(Main.this, intent);
static void enqueueWork(Context context, Intent work) {
enqueueWork(context, xxxService.class, JOB_ID, work);
}
@Override
protected void onHandleWork(Intent intent) {
// 这里执行具体的任务
}
2、enqueueWork的调用流程
- JobIntentService
public static void enqueueWork(@NonNull Context context, @NonNull ComponentName component, int jobId, @NonNull Intent work) {
if (work == null) {
throw new IllegalArgumentException("work must not be null");
} else {
synchronized(sLock) {
//这里拿到JobSchedulerService
JobIntentService.WorkEnqueuer we = getWorkEnqueuer(context, component, true, jobId);
we.ensureJobId(jobId);
//这里让JobSchedulerService执行工作
we.enqueueWork(work);
}
}
}
@RequiresApi(26)
static final class JobWorkEnqueuer extends JobIntentService.WorkEnqueuer {
private final JobInfo mJobInfo;
private final JobScheduler mJobScheduler;
JobWorkEnqueuer(Context context, ComponentName cn, int jobId) {
super(context, cn);
this.ensureJobId(jobId);
Builder b = new Builder(jobId, this.mComponentName);
this.mJobInfo = b.setOverrideDeadline(0L).build();
//拿到JobSchedulerService
this.mJobScheduler = (JobScheduler)context.getApplicationContext().getSystemService("jobscheduler");
}
void enqueueWork(Intent work) {
//JobSchedulerService执行工作
this.mJobScheduler.enqueue(this.mJobInfo, new JobWorkItem(work));
}
}
二、JobSchedulerService 里的流程
1、jSS 1
// IJobScheduler implementation
@Override
public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work);
}
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
enforceValidJobRequest(uid, job);
if (job.isPersisted()) {
throw new IllegalArgumentException("Can't enqueue work for persisted jobs");
}
if (work == null) {
throw new NullPointerException("work is null");
}
work.enforceValidity();
validateJobFlags(job, uid);
final long ident = Binder.clearCallingIdentity();
try {
//zsg here
return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
2、jSS 2
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
// Rate limit excessive schedule() calls.
final String servicePkg = job.getService().getPackageName();
if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
// Only limit schedule calls for persisted jobs scheduled by the app itself.
final String pkg = packageName == null ? servicePkg : packageName;
if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
if (mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED)) {
// Don't log too frequently
Slog.wtf(TAG, userId + "-" + pkg + " has called schedule() too many times");
mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED);
}
mAppStandbyInternal.restrictApp(
pkg, userId, UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION) {
final boolean isDebuggable;
synchronized (mLock) {
if (!mDebuggableApps.containsKey(packageName)) {
try {
final ApplicationInfo appInfo = AppGlobals.getPackageManager()
.getApplicationInfo(pkg, 0, userId);
if (appInfo != null) {
mDebuggableApps.put(packageName,
(appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
} else {
return JobScheduler.RESULT_FAILURE;
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
isDebuggable = mDebuggableApps.get(packageName);
}
if (isDebuggable) {
// Only throw the exception for debuggable apps.
throw new LimitExceededException(
"schedule()/enqueue() called more than "
+ mQuotaTracker.getLimit(
QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED)
+ " times in the past "
+ mQuotaTracker.getWindowSizeMs(
QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED)
+ "ms. See the documentation for more information.");
}
}
if (mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT) {
return JobScheduler.RESULT_FAILURE;
}
}
mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
}
if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) {
Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ " -- package not allowed to start");
return JobScheduler.RESULT_FAILURE;
}
synchronized (mLock) {
final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
if (work != null && toCancel != null) {
// Fast path: we are adding work to an existing job, and the JobInfo is not
// changing. We can just directly enqueue this work in to the job.
if (toCancel.getJob().equals(job)) {
toCancel.enqueueWorkLocked(work);
// If any of work item is enqueued when the source is in the foreground,
// exempt the entire job.
toCancel.maybeAddForegroundExemption(mIsUidActivePredicate);
return JobScheduler.RESULT_SUCCESS;
}
}
JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
// Return failure early if expedited job quota used up.
if (jobStatus.isRequestedExpeditedJob()) {
if ((mConstants.USE_TARE_POLICY && !mTareController.canScheduleEJ(jobStatus))
|| (!mConstants.USE_TARE_POLICY
&& !mQuotaController.isWithinEJQuotaLocked(jobStatus))) {
return JobScheduler.RESULT_FAILURE;
}
}
// Give exemption if the source is in the foreground just now.
// Note if it's a sync job, this method is called on the handler so it's not exactly
// the state when requestSync() was called, but that should be fine because of the
// 1 minute foreground grace period.
jobStatus.maybeAddForegroundExemption(mIsUidActivePredicate);
if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
// Jobs on behalf of others don't apply to the per-app job cap
if (packageName == null) {
if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
Slog.w(TAG, "Too many jobs for uid " + uId);
throw new IllegalStateException("Apps may not schedule more than "
+ MAX_JOBS_PER_APP + " distinct jobs");
}
}
// This may throw a SecurityException.
jobStatus.prepareLocked();
if (toCancel != null) {
// Implicitly replaces the existing job record with the new instance
cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
JobParameters.INTERNAL_STOP_REASON_CANCELED, "job rescheduled by app");
} else {
startTrackingJobLocked(jobStatus, null);
}
if (work != null) {
// If work has been supplied, enqueue it into the new job.
jobStatus.enqueueWorkLocked(work);
}
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
uId, null, jobStatus.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED,
JobProtoEnums.INTERNAL_STOP_REASON_UNKNOWN, jobStatus.getStandbyBucket(),
jobStatus.getJobId(),
jobStatus.hasChargingConstraint(),
jobStatus.hasBatteryNotLowConstraint(),
jobStatus.hasStorageNotLowConstraint(),
jobStatus.hasTimingDelayConstraint(),
jobStatus.hasDeadlineConstraint(),
jobStatus.hasIdleConstraint(),
jobStatus.hasConnectivityConstraint(),
jobStatus.hasContentTriggerConstraint(),
jobStatus.isRequestedExpeditedJob(),
/* isRunningAsExpeditedJob */ false,
JobProtoEnums.STOP_REASON_UNDEFINED,
jobStatus.getJob().isPrefetch(),
jobStatus.getJob().getPriority(),
jobStatus.getEffectivePriority(),
jobStatus.getNumFailures());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
// important for jobs with a 0 deadline constraint, since they will happen a fair
// amount, we want to handle them as quickly as possible, and semantically we want to
// make sure we have started holding the wake lock for the job before returning to
// the caller.
// If the job is not yet ready to run, there is nothing more to do -- we are
// now just waiting for one of its controllers to change state and schedule
// the job appropriately.
if (isReadyToBeExecutedLocked(jobStatus)) {
// This is a new job, we can just immediately put it on the pending
// list and try to run it.
mJobPackageTracker.notePending(jobStatus);
mPendingJobQueue.add(jobStatus);
//zsg here
maybeRunPendingJobsLocked();
} else {
evaluateControllerStatesLocked(jobStatus);
}
}
return JobScheduler.RESULT_SUCCESS;
}
3、JSS 3
void maybeRunPendingJobsLocked() {
if (DEBUG) {
Slog.d(TAG, "pending queue: " + mPendingJobQueue.size() + " jobs.");
}
//zsg here
mConcurrencyManager.assignJobsToContextsLocked();
reportActiveLocked();
}
4、JSS 4
/**
* Takes jobs from pending queue and runs them on available contexts.
* If no contexts are available, preempts lower bias jobs to run higher bias ones.
* Lock on mLock before calling this function.
*/
@GuardedBy("mLock")
void assignJobsToContextsLocked() {
final long start = mStatLogger.getTime();
//zsg here
assignJobsToContextsInternalLocked();
mStatLogger.logDurationStat(Stats.ASSIGN_JOBS_TO_CONTEXTS, start);
}
5、JSS 5
@GuardedBy("mLock")
private void assignJobsToContextsInternalLocked() {
if (DEBUG) {
Slog.d(TAG, printPendingQueueLocked());
}
if (mService.getPendingJobQueue().size() == 0) {
// Nothing to do.
return;
}
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
final List<JobServiceContext> activeServices = mActiveServices;
// To avoid GC churn, we recycle the arrays.
final ArraySet<ContextAssignment> changed = mRecycledChanged;
final ArraySet<ContextAssignment> idle = mRecycledIdle;
final ArrayList<ContextAssignment> preferredUidOnly = mRecycledPreferredUidOnly;
final ArrayList<ContextAssignment> stoppable = mRecycledStoppable;
updateCounterConfigLocked();
// Reset everything since we'll re-evaluate the current state.
mWorkCountTracker.resetCounts();
// Update the priorities of jobs that aren't running, and also count the pending work types.
// Do this before the following loop to hopefully reduce the cost of
// shouldStopRunningJobLocked().
updateNonRunningPrioritiesLocked(pendingJobQueue, true);
final int numRunningJobs = activeServices.size();
for (int i = 0; i < numRunningJobs; ++i) {
final JobServiceContext jsc = activeServices.get(i);
final JobStatus js = jsc.getRunningJobLocked();
ContextAssignment assignment = mContextAssignmentPool.acquire();
if (assignment == null) {
assignment = new ContextAssignment();
}
assignment.context = jsc;
if (js != null) {
mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
assignment.workType = jsc.getRunningJobWorkType();
}
assignment.preferredUid = jsc.getPreferredUid();
if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) {
stoppable.add(assignment);
} else {
preferredUidOnly.add(assignment);
}
}
preferredUidOnly.sort(sDeterminationComparator);
stoppable.sort(sDeterminationComparator);
for (int i = numRunningJobs; i < STANDARD_CONCURRENCY_LIMIT; ++i) {
final JobServiceContext jsc;
final int numIdleContexts = mIdleContexts.size();
if (numIdleContexts > 0) {
jsc = mIdleContexts.removeAt(numIdleContexts - 1);
} else {
Slog.wtf(TAG, "Had fewer than " + STANDARD_CONCURRENCY_LIMIT + " in existence");
jsc = createNewJobServiceContext();
}
ContextAssignment assignment = mContextAssignmentPool.acquire();
if (assignment == null) {
assignment = new ContextAssignment();
}
assignment.context = jsc;
idle.add(assignment);
}
if (DEBUG) {
Slog.d(TAG, printAssignments("running jobs initial", stoppable, preferredUidOnly));
}
mWorkCountTracker.onCountDone();
JobStatus nextPending;
pendingJobQueue.resetIterator();
int projectedRunningCount = numRunningJobs;
while ((nextPending = pendingJobQueue.next()) != null) {
if (mRunningJobs.contains(nextPending)) {
// Should never happen.
Slog.wtf(TAG, "Pending queue contained a running job");
if (DEBUG) {
Slog.e(TAG, "Pending+running job: " + nextPending);
}
pendingJobQueue.remove(nextPending);
continue;
}
final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
&& nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar " + (isTopEj ? "TOP-EJ" : "job")
+ " to: " + nextPending);
}
// Find an available slot for nextPending. The context should be one of the following:
// 1. Unused
// 2. Its job should have used up its minimum execution guarantee so it
// 3. Its job should have the lowest bias among all running jobs (sharing the same UID
// as nextPending)
ContextAssignment selectedContext = null;
final int allWorkTypes = getJobWorkTypes(nextPending);
final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
final boolean isInOverage = projectedRunningCount > STANDARD_CONCURRENCY_LIMIT;
boolean startingJob = false;
if (idle.size() > 0) {
final int idx = idle.size() - 1;
final ContextAssignment assignment = idle.valueAt(idx);
final boolean preferredUidOkay = (assignment.preferredUid == nextPending.getUid())
|| (assignment.preferredUid == JobServiceContext.NO_PREFERRED_UID);
int workType = mWorkCountTracker.canJobStart(allWorkTypes);
if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) {
// This slot is free, and we haven't yet hit the limit on
// concurrent jobs... we can just throw the job in to here.
selectedContext = assignment;
startingJob = true;
idle.removeAt(idx);
assignment.newJob = nextPending;
assignment.newWorkType = workType;
}
}
if (selectedContext == null && stoppable.size() > 0) {
int topEjCount = 0;
for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
JobStatus js = mRunningJobs.valueAt(r);
if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
topEjCount++;
}
}
for (int s = stoppable.size() - 1; s >= 0; --s) {
final ContextAssignment assignment = stoppable.get(s);
final JobStatus runningJob = assignment.context.getRunningJobLocked();
// Maybe stop the job if it has had its day in the sun. Only allow replacing
// for one of the following conditions:
// 1. We're putting in the current TOP app's EJ
// 2. There aren't too many jobs running AND the current job started when the
// app was in the background
// 3. There aren't too many jobs running AND the current job started when the
// app was on TOP, but the app has since left TOP
// 4. There aren't too many jobs running AND the current job started when the
// app was on TOP, the app is still TOP, but there are too many TOP+EJs
// running (because we don't want them to starve out other apps and the
// current job has already run for the minimum guaranteed time).
boolean canReplace = isTopEj; // Case 1
if (!canReplace && !isInOverage) {
final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
|| currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
|| topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4
}
if (canReplace) {
int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes,
assignment.context.getRunningJobWorkType());
if (replaceWorkType != WORK_TYPE_NONE) {
// Right now, the way the code is set up, we don't need to explicitly
// assign the new job to this context since we'll reassign when the
// preempted job finally stops.
assignment.preemptReason = assignment.shouldStopJobReason;
assignment.preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
selectedContext = assignment;
stoppable.remove(s);
assignment.newJob = nextPending;
assignment.newWorkType = replaceWorkType;
break;
}
}
}
}
if (selectedContext == null && (!isInOverage || isTopEj)) {
int lowestBiasSeen = Integer.MAX_VALUE;
for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
final ContextAssignment assignment = preferredUidOnly.get(p);
final JobStatus runningJob = assignment.context.getRunningJobLocked();
if (runningJob.getUid() != nextPending.getUid()) {
continue;
}
final int jobBias = mService.evaluateJobBiasLocked(runningJob);
if (jobBias >= nextPending.lastEvaluatedBias) {
continue;
}
if (selectedContext == null || lowestBiasSeen > jobBias) {
// Step down the preemption threshold - wind up replacing
// the lowest-bias running job
lowestBiasSeen = jobBias;
selectedContext = assignment;
assignment.preemptReason = "higher bias job found";
assignment.preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
// In this case, we're just going to preempt a low bias job, we're not
// actually starting a job, so don't set startingJob to true.
}
}
if (selectedContext != null) {
selectedContext.newJob = nextPending;
preferredUidOnly.remove(selectedContext);
}
}
// Make sure to run EJs for the TOP app immediately.
if (isTopEj) {
if (selectedContext != null
&& selectedContext.context.getRunningJobLocked() != null) {
// We're "replacing" a currently running job, but we want TOP EJs to start
// immediately, so we'll start the EJ on a fresh available context and
// stop this currently running job to replace in two steps.
changed.add(selectedContext);
projectedRunningCount--;
selectedContext.newJob = null;
selectedContext.newWorkType = WORK_TYPE_NONE;
selectedContext = null;
}
if (selectedContext == null) {
selectedContext = mContextAssignmentPool.acquire();
if (selectedContext == null) {
selectedContext = new ContextAssignment();
}
selectedContext.context = mIdleContexts.size() > 0
? mIdleContexts.removeAt(mIdleContexts.size() - 1)
: createNewJobServiceContext();
selectedContext.newJob = nextPending;
final int workType = mWorkCountTracker.canJobStart(allWorkTypes);
selectedContext.newWorkType =
(workType != WORK_TYPE_NONE) ? workType : WORK_TYPE_TOP;
}
}
final PackageStats packageStats = getPkgStatsLocked(
nextPending.getSourceUserId(), nextPending.getSourcePackageName());
if (selectedContext != null) {
changed.add(selectedContext);
if (selectedContext.context.getRunningJobLocked() != null) {
projectedRunningCount--;
}
if (selectedContext.newJob != null) {
projectedRunningCount++;
}
packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
}
if (startingJob) {
// Increase the counters when we're going to start a job.
mWorkCountTracker.stageJob(selectedContext.newWorkType, allWorkTypes);
mActivePkgStats.add(
nextPending.getSourceUserId(), nextPending.getSourcePackageName(),
packageStats);
}
}
if (DEBUG) {
Slog.d(TAG, printAssignments("running jobs final",
stoppable, preferredUidOnly, changed));
Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
}
for (int c = changed.size() - 1; c >= 0; --c) {
final ContextAssignment assignment = changed.valueAt(c);
final JobStatus js = assignment.context.getRunningJobLocked();
if (js != null) {
if (DEBUG) {
Slog.d(TAG, "preempting job: " + js);
}
// preferredUid will be set to uid of currently running job, if appropriate.
assignment.context.cancelExecutingJobLocked(
assignment.preemptReasonCode,
JobParameters.INTERNAL_STOP_REASON_PREEMPT, assignment.preemptReason);
} else {
final JobStatus pendingJob = assignment.newJob;
if (DEBUG) {
Slog.d(TAG, "About to run job on context "
+ assignment.context.getId() + ", job: " + pendingJob);
}
// zsg here
startJobLocked(assignment.context, pendingJob, assignment.newWorkType);
}
assignment.clear();
mContextAssignmentPool.release(assignment);
}
for (int s = stoppable.size() - 1; s >= 0; --s) {
final ContextAssignment assignment = stoppable.get(s);
assignment.clear();
mContextAssignmentPool.release(assignment);
}
for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
final ContextAssignment assignment = preferredUidOnly.get(p);
assignment.clear();
mContextAssignmentPool.release(assignment);
}
for (int i = idle.size() - 1; i >= 0; --i) {
final ContextAssignment assignment = idle.valueAt(i);
mIdleContexts.add(assignment.context);
assignment.clear();
mContextAssignmentPool.release(assignment);
}
changed.clear();
idle.clear();
stoppable.clear();
preferredUidOnly.clear();
mWorkCountTracker.resetStagingCount();
mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
noteConcurrency();
}
6、JSS 6
@GuardedBy("mLock")
private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
@WorkType final int workType) {
final List<StateController> controllers = mService.mControllers;
final int numControllers = controllers.size();
final PowerManager.WakeLock wl =
mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, jobStatus.getTag());
wl.setWorkSource(mService.deriveWorkSource(
jobStatus.getSourceUid(), jobStatus.getSourcePackageName()));
wl.setReferenceCounted(false);
// Since the quota controller will start counting from the time prepareForExecutionLocked()
// is called, hold a wakelock to make sure the CPU doesn't suspend between that call and
// when the service actually starts.
wl.acquire();
try {
for (int ic = 0; ic < numControllers; ic++) {
controllers.get(ic).prepareForExecutionLocked(jobStatus);
}
final PackageStats packageStats = getPkgStatsLocked(
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob());
//zsg here
if (!worker.executeRunnableJob(jobStatus, workType)) {
Slog.e(TAG, "Error executing " + jobStatus);
mWorkCountTracker.onStagedJobFailed(workType);
for (int ic = 0; ic < numControllers; ic++) {
controllers.get(ic).unprepareFromExecutionLocked(jobStatus);
}
} else {
mRunningJobs.add(jobStatus);
mActiveServices.add(worker);
mIdleContexts.remove(worker);
mWorkCountTracker.onJobStarted(workType);
packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob());
mActivePkgStats.add(
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
packageStats);
}
if (mService.getPendingJobQueue().remove(jobStatus)) {
mService.mJobPackageTracker.noteNonpending(jobStatus);
}
} finally {
wl.release();
}
}
7、 JSS 7
/**
* Give a job to this context for execution. Callers must first check {@link
* #getRunningJobLocked()}
* and ensure it is null 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, @JobConcurrencyManager.WorkType int workType) {
synchronized (mLock) {
if (!mAvailable) {
Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
return false;
}
mPreferredUid = NO_PREFERRED_UID;
mRunningJob = job;
mRunningJobWorkType = workType;
mRunningCallback = new JobCallback();
final boolean isDeadlineExpired =
job.hasDeadlineConstraint() &&
(job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());
Uri[] triggeredUris = null;
if (job.changedUris != null) {
triggeredUris = new Uri[job.changedUris.size()];
job.changedUris.toArray(triggeredUris);
}
String[] triggeredAuthorities = null;
if (job.changedAuthorities != null) {
triggeredAuthorities = new String[job.changedAuthorities.size()];
job.changedAuthorities.toArray(triggeredAuthorities);
}
final JobInfo ji = job.getJob();
mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
isDeadlineExpired, job.shouldTreatAsExpeditedJob(),
triggeredUris, triggeredAuthorities, job.network);
mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job);
mMaxExecutionTimeMillis =
Math.max(mService.getMaxJobExecutionTimeMs(job), mMinExecutionGuaranteeMillis);
final long whenDeferred = job.getWhenStandbyDeferred();
if (whenDeferred > 0) {
final long deferral = mExecutionStartTimeElapsed - whenDeferred;
EventLog.writeEvent(EventLogTags.JOB_DEFERRED_EXECUTION, deferral);
if (DEBUG_STANDBY) {
StringBuilder sb = new StringBuilder(128);
sb.append("Starting job deferred for standby by ");
TimeUtils.formatDuration(deferral, sb);
sb.append(" ms : ");
sb.append(job.toShortString());
Slog.v(TAG, sb.toString());
}
}
// Once we'e begun executing a job, we by definition no longer care whether
// it was inflated from disk with not-yet-coherent delay/deadline bounds.
job.clearPersistedUtcTimes();
mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, job.getTag());
mWakeLock.setWorkSource(
mService.deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
// Note the start when we try to bind so that the app is charged for some processing
// even if binding fails.
mEconomyManagerInternal.noteInstantaneousEvent(
job.getSourceUserId(), job.getSourcePackageName(),
getStartActionId(job), String.valueOf(job.getJobId()));
mVerb = VERB_BINDING;
scheduleOpTimeOutLocked();
final Intent intent = new Intent().setComponent(job.getServiceComponent());
Log.i("zsg", "job.getServiceComponent(): " + job.getServiceComponent());
boolean binding = false;
try {
final int bindFlags;
if (job.shouldTreatAsExpeditedJob()) {
bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
| Context.BIND_ALMOST_PERCEPTIBLE
| Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
| Context.BIND_NOT_APP_COMPONENT_USAGE;
} else {
bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
| Context.BIND_NOT_PERCEPTIBLE
| Context.BIND_NOT_APP_COMPONENT_USAGE;
}
//zsg 在这里绑定的APP 端的service
binding = mContext.bindServiceAsUser(intent, this, bindFlags,
UserHandle.of(job.getUserId()));
} catch (SecurityException e) {
// Some permission policy, for example INTERACT_ACROSS_USERS and
// android:singleUser, can result in a SecurityException being thrown from
// bindServiceAsUser(). If this happens, catch it and fail gracefully.
Slog.w(TAG, "Job service " + job.getServiceComponent().getShortClassName()
+ " cannot be executed: " + e.getMessage());
binding = false;
}
if (!binding) {
if (DEBUG) {
Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
}
mRunningJob = null;
mRunningJobWorkType = WORK_TYPE_NONE;
mRunningCallback = null;
mParams = null;
mExecutionStartTimeElapsed = 0L;
mWakeLock.release();
mVerb = VERB_FINISHED;
removeOpTimeOutLocked();
return false;
}
mJobPackageTracker.noteActive(job);
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
job.getSourceUid(), null, job.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__STARTED,
JobProtoEnums.INTERNAL_STOP_REASON_UNKNOWN,
job.getStandbyBucket(), job.getJobId(),
job.hasChargingConstraint(),
job.hasBatteryNotLowConstraint(),
job.hasStorageNotLowConstraint(),
job.hasTimingDelayConstraint(),
job.hasDeadlineConstraint(),
job.hasIdleConstraint(),
job.hasConnectivityConstraint(),
job.hasContentTriggerConstraint(),
job.isRequestedExpeditedJob(),
job.shouldTreatAsExpeditedJob(),
JobProtoEnums.STOP_REASON_UNDEFINED,
job.getJob().isPrefetch(),
job.getJob().getPriority(),
job.getEffectivePriority(),
job.getNumFailures());
// Use the context's ID to distinguish traces since there'll only be one job running
// per context.
Trace.asyncTraceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, job.getTag(), getId());
try {
mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
} catch (RemoteException e) {
// Whatever.
}
final String jobPackage = job.getSourcePackageName();
final int jobUserId = job.getSourceUserId();
UsageStatsManagerInternal usageStats =
LocalServices.getService(UsageStatsManagerInternal.class);
usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed);
mAvailable = false;
mStoppedReason = null;
mStoppedTime = 0;
job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob();
return true;
}
}
8、JSS
/**
* We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
* we intend to send to the client - we stop sending work when the service is unbound so until
* then we keep the wakelock.
* @param name The concrete component name of the service that has been connected.
* @param service The IBinder of the Service's communication channel,
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
JobStatus runningJob;
synchronized (mLock) {
// This isn't strictly necessary b/c the JobServiceHandler is running on the main
// looper and at this point we can't get any binder callbacks from the client. Better
// safe than sorry.
runningJob = mRunningJob;
if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
closeAndCleanupJobLocked(true /* needsReschedule */,
"connected for different component");
return;
}
//zsg 如果上面的服务绑定成功
this.service = IJobService.Stub.asInterface(service);
doServiceBoundLocked();
}
}
9、JSS
/** Start the job on the service. */
@GuardedBy("mLock")
private void handleServiceBoundLocked() {
if (DEBUG) {
Slog.d(TAG, "handleServiceBound for " + getRunningJobNameLocked());
}
if (mVerb != VERB_BINDING) {
Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
+ VERB_STRINGS[mVerb]);
closeAndCleanupJobLocked(false /* reschedule */, "started job not pending");
return;
}
if (mCancelled) {
if (DEBUG) {
Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
+ mRunningJob);
}
closeAndCleanupJobLocked(true /* reschedule */, "cancelled while waiting for bind");
return;
}
try {
mVerb = VERB_STARTING;
scheduleOpTimeOutLocked();
Log.i("zsg", "handleServiceBoundLocked mParams: "+mParams.toString());
//zsg 这里去调用APP开始执行job的内容
service.startJob(mParams);
} catch (Exception e) {
// We catch 'Exception' because client-app malice or bugs might induce a wide
// range of possible exception-throw outcomes from startJob() and its handling
// of the client's ParcelableBundle extras.
Slog.e(TAG, "Error sending onStart message to '" +
mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
}
}
10 JSS 调用回APP里
static final class JobInterface extends IJobService.Stub {
final WeakReference<JobServiceEngine> mService;
JobInterface(JobServiceEngine service) {
mService = new WeakReference<>(service);
}
//zsg 调用回这里,是因为在JobIntentService 里的OnBind 方法里返回的是这个服务
@Override
public void startJob(JobParameters jobParams) throws RemoteException {
JobServiceEngine service = mService.get();
if (service != null) {
Message m = Message.obtain(service.mHandler, MSG_EXECUTE_JOB, jobParams);
m.sendToTarget();
}
}
@Override
public void stopJob(JobParameters jobParams) throws RemoteException {
JobServiceEngine service = mService.get();
if (service != null) {
Message m = Message.obtain(service.mHandler, MSG_STOP_JOB, jobParams);
m.sendToTarget();
}
}
}
11 JSS
/**
* Runs on application's main thread - callbacks are meant to offboard work to some other
* (app-specified) mechanism.
* @hide
*/
class JobHandler extends Handler {
JobHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
final JobParameters params = (JobParameters) msg.obj;
switch (msg.what) {
case MSG_EXECUTE_JOB:
try {
//zsg here
boolean workOngoing = JobServiceEngine.this.onStartJob(params);
ackStartMessage(params, workOngoing);
} catch (Exception e) {
Log.e(TAG, "Error while executing job: " + params.getJobId());
throw new RuntimeException(e);
}
break;
case MSG_STOP_JOB:
try {
boolean ret = JobServiceEngine.this.onStopJob(params);
ackStopMessage(params, ret);
} catch (Exception e) {
Log.e(TAG, "Application unable to handle onStopJob.", e);
throw new RuntimeException(e);
}
break;
case MSG_JOB_FINISHED:
final boolean needsReschedule = (msg.arg2 == 1);
IJobCallback callback = params.getCallback();
if (callback != null) {
try {
callback.jobFinished(params.getJobId(), needsReschedule);
} catch (RemoteException e) {
Log.e(TAG, "Error reporting job finish to system: binder has gone" +
"away.");
}
} else {
Log.e(TAG, "finishJob() called for a nonexistent job id.");
}
break;
default:
Log.e(TAG, "Unrecognised message received.");
break;
}
}
12 JSS
@RequiresApi(26)
static final class JobServiceEngineImpl extends JobServiceEngine implements JobIntentService.CompatJobEngine {
static final String TAG = "JobServiceEngineImpl";
static final boolean DEBUG = false;
final JobIntentService mService;
final Object mLock = new Object();
JobParameters mParams;
JobServiceEngineImpl(JobIntentService service) {
super(service);
this.mService = service;
}
public IBinder compatGetBinder() {
return this.getBinder();
}
//zsg here 这里的mService就是APP中我们自己的service
//在JobIntentService的onCreate里就传入了
public boolean onStartJob(JobParameters params) {
this.mParams = params;
this.mService.ensureProcessorRunningLocked(false);
return true;
}
public boolean onStopJob(JobParameters params) {
boolean result = this.mService.doStopCurrentWork();
synchronized(this.mLock) {
this.mParams = null;
return result;
}
}
public JobIntentService.GenericWorkItem dequeueWork() {
JobWorkItem work;
synchronized(this.mLock) {
if (this.mParams == null) {
return null;
}
work = this.mParams.dequeueWork();
}
if (work != null) {
work.getIntent().setExtrasClassLoader(this.mService.getClassLoader());
return new JobIntentService.JobServiceEngineImpl.WrapperWorkItem(work);
} else {
return null;
}
}
final class WrapperWorkItem implements JobIntentService.GenericWorkItem {
final JobWorkItem mJobWork;
WrapperWorkItem(JobWorkItem jobWork) {
this.mJobWork = jobWork;
}
public Intent getIntent() {
return this.mJobWork.getIntent();
}
public void complete() {
synchronized(JobServiceEngineImpl.this.mLock) {
if (JobServiceEngineImpl.this.mParams != null) {
JobServiceEngineImpl.this.mParams.completeWork(this.mJobWork);
}
}
}
}
}
13 JSS
void ensureProcessorRunningLocked(boolean reportStarted) {
if (this.mCurProcessor == null) {
//zsg 执行这个AsyncTask
this.mCurProcessor = new JobIntentService.CommandProcessor();
if (this.mCompatWorkEnqueuer != null && reportStarted) {
this.mCompatWorkEnqueuer.serviceProcessingStarted();
}
this.mCurProcessor.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Void[0]);
}
}
14 JSS
final class CommandProcessor extends AsyncTask<Void, Void, Void> {
CommandProcessor() {
}
protected Void doInBackground(Void... params) {
JobIntentService.GenericWorkItem work;
while((work = JobIntentService.this.dequeueWork()) != null) {
//zsg 最终执行到自己service的onHandleWork
JobIntentService.this.onHandleWork(work.getIntent());
work.complete();
}
return null;
}
protected void onCancelled(Void aVoid) {
JobIntentService.this.processorFinished();
}
protected void onPostExecute(Void aVoid) {
JobIntentService.this.processorFinished();
}
}