JobSchedulerService 源码分析 -- 注册 Job 流程(API 21)

一、调用流程

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 JobScheduler#RESULT_* 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

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 runnableJobs = new ArrayList();
    ArraySet jobs = mJobs.getJobs();
    for (int i=0; i 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 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

五、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;
    }
}

你可能感兴趣的:(JobSchedulerService 源码分析 -- 注册 Job 流程(API 21))