JobSchedulerService

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();
        }
    }
  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值