美文网首页Android 源码分析
JobSchedulerService 源码分析 -- 注册 J

JobSchedulerService 源码分析 -- 注册 J

作者: _夜 | 来源:发表于2017-11-23 19:03 被阅读0次

    一、调用流程

    Builder builder = new Builder(jobId, new ComponentName(context, JobSchedulerService.class));
    builder.setMinimumLatency(30*60*1000L);
    builder.setPersisted(true);
    JobScheduler jobScheduler = (JobScheduler)context.getSystemService("jobscheduler");
    int result = jobScheduler.schedule(builder.build());
    
    

    二、JobInfo 源码

    /**
     * 第一版的 JobSchedulerService 的处理比较简单
     * Specify that this job should recur with the provided interval, not more than once per
     * period. You have no control over when within this interval this job will be executed,
     * only the guarantee that it will be executed at most once within this interval.
     * Setting this function on the builder with {@link #setMinimumLatency(long)} or
     * {@link #setOverrideDeadline(long)} will result in an error.
     * @param intervalMillis Millisecond interval for which this job will repeat.
     */
    public JobInfo.Builder setPeriodic(long intervalMillis) {
        mIsPeriodic = true;
        mIntervalMillis = intervalMillis;
        mHasEarlyConstraint = mHasLateConstraint = true;
        return this;
    }
    
    

    三、jobScheduler.schedule(JobInfo)流程

    public abstract int schedule(JobInfo job);
    |
    JobSchedulerImpl.schedule(job);
    |
    public int schedule(JobInfo job) {
        try {
            return mBinder.schedule(job);
        } catch (RemoteException e) {
            return JobScheduler.RESULT_FAILURE;
        }
    }
    |
    IJobScheduler mBinder 进程间通讯
    |
    JobSchedulerStub extends IJobScheduler.Stub (in JobSchedulerService)
    |
    @Override
    public int schedule(JobInfo job) throws RemoteException {
        if (DEBUG) {
            Slog.d(TAG, "Scheduling job: " + job.toString());
        }
        final int pid = Binder.getCallingPid();
        final int uid = Binder.getCallingUid();
        /**
         * Enforce that only the app itself (or shared uid participant) can schedule a
         * job that runs one of the app's services, as well as verifying that the
         * named service properly requires the BIND_JOB_SERVICE permission
         */
        enforceValidJobRequest(uid, job);
        if (job.isPersisted()) {
            if (!canPersistJobs(pid, uid)) {
                throw new IllegalArgumentException("Error: requested job be persisted without"
                        + " holding RECEIVE_BOOT_COMPLETED permission.");
            }
        }
    
        long ident = Binder.clearCallingIdentity();
        try {
            return JobSchedulerService.this.schedule(job, uid);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
    
    

    四、JobSchedulerService.schedule(job, uid)流程

    /**
     * Entry point from client to schedule the provided job.
     * This cancels the job if it's already been scheduled, and replaces it with the one provided.
     * @param job JobInfo object containing execution parameters
     * @param uId The package identifier of the application this job is for.
     * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
     */
    public int schedule(JobInfo job, int uId) {
        JobStatus jobStatus = new JobStatus(job, uId);
        cancelJob(uId, job.getId());
        startTrackingJob(jobStatus);
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
        return JobScheduler.RESULT_SUCCESS;
    }
    
    

    1. 根据传入的 JobInfo 创建 JobStatus

    /** Create a newly scheduled job. */
    public JobStatus(JobInfo job, int uId) {
        this(job, uId, 0);
    
        final long elapsedNow = SystemClock.elapsedRealtime();
    
        if (job.isPeriodic()) {
            // 第一版的此处的处理比较简单
            earliestRunTimeElapsedMillis = elapsedNow;
            latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
        } else {
            earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ? elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
            latestRunTimeElapsedMillis = job.hasLateConstraint() ? elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
        }
    }
    
    

    2. 从磁盘及 Controller 中移除 Job, 若在执行则取消

    /**
     * Entry point from client to cancel the job corresponding to the jobId provided.
     * This will remove the job from the master list, and cancel the job if it was staged for
     * execution or being executed.
     * @param uid Uid of the calling client.
     * @param jobId Id of the job, provided at schedule-time.
     */
    public void cancelJob(int uid, int jobId) {
        JobStatus toCancel;
        synchronized (mJobs) {
            toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
        }
        if (toCancel != null) {
            cancelJobImpl(toCancel);
        }
    }
    |
    |
    private void cancelJobImpl(JobStatus cancelled) {
        if (DEBUG) {
            Slog.d(TAG, "Cancelling: " + cancelled);
        }
        // Remove from store as well as controllers.
        stopTrackingJob(cancelled);
        synchronized (mJobs) {
            // Remove from pending queue.
            mPendingJobs.remove(cancelled);
            // Cancel if running.
            stopJobOnServiceContextLocked(cancelled);
        }
    }
    
    

    3. 添加入追踪列表 mJobs,并通知各 Controller 追踪该 Job

    /**
     * Called when we have a job status object that we need to insert in our
     * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
     * about.
     */
    private void startTrackingJob(JobStatus jobStatus) {
        boolean update;
        boolean rocking;
        synchronized (mJobs) {
            /**
             * Add a job to the master list, persisting it if necessary.
             * If the JobStatus already exists, it will be replaced.
             */
            update = mJobs.add(jobStatus);
            rocking = mReadyToRock;
        }
        if (rocking) {
            for (int i=0; i<mControllers.size(); i++) {
                StateController controller = mControllers.get(i);
                if (update) {
                    controller.maybeStopTrackingJob(jobStatus);
                }
                controller.maybeStartTrackingJob(jobStatus);
            }
        }
    }
    
    

    4、JobHandler 处理 MSG_CHECK_JOB 消息流程

    private class JobHandler extends Handler{
        public void handleMessage(Message message) {
            switch (message.what) {
                case MSG_CHECK_JOB:
                    synchronized (mJobs) {
                        // Check the list of jobs and run some of them if we feel inclined.
                        // 检查所有满足执行条件的 Job,根据策略决定是否放入 mPendingJobs,随后执行 mPendingJobs 中的 Job
                        maybeQueueReadyJobsForExecutionLockedH();
                    }
                    break;
            }
            // 从 mPendingJobs 中取出 Job 执行
            maybeRunPendingJobsH();
            // Don't remove JOB_EXPIRED in case one came along while processing the queue.
            removeMessages(MSG_CHECK_JOB);
        }
    }
    
    
    

    5. 检查所有满足执行条件的 Job,根据策略决定是否放入 mPendingJobs

    /**
     * The state of at least one job has changed. Here is where we could enforce various
     * policies on when we want to execute jobs.
     * Right now the policy is such:
     * If >1 of the ready jobs is idle mode we send all of them off
     * if more than 2 network connectivity jobs are ready we send them all off.
     * If more than 4 jobs total are ready we send them all off.
     * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
     * 检查所有满足执行条件的 Job,根据策略决定是否放入 mPendingJobs,随后执行 mPendingJobs 中的 Job
     */
    private void maybeQueueReadyJobsForExecutionLockedH() {
        int chargingCount = 0;
        int idleCount =  0;
        int backoffCount = 0;
        int connectivityCount = 0;
        List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
        ArraySet<JobStatus> jobs = mJobs.getJobs();
        for (int i=0; i<jobs.size(); i++) {
            JobStatus job = jobs.valueAt(i);
            if (isReadyToBeExecutedLocked(job)) {
                if (job.getNumFailures() > 0) {
                    backoffCount++;
                }
                if (job.hasIdleConstraint()) {
                    idleCount++;
                }
                if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
                    connectivityCount++;
                }
                if (job.hasChargingConstraint()) {
                    chargingCount++;
                }
                // 把已经满足执行条件的 job 加入 runnableJobs
                runnableJobs.add(job);
            } else if (isReadyToBeCancelledLocked(job)) {
                stopJobOnServiceContextLocked(job);
            }
        }
        if (backoffCount > 0 || idleCount >= MIN_IDLE_COUNT || connectivityCount >= MIN_CONNECTIVITY_COUNT ||
                chargingCount >= MIN_CHARGING_COUNT || runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
            // 注意上面的判断条件,很重要,不满足上面的条件,就不会有 Job 被加入到 mPendingJobs
            for (int i = 0; i < runnableJobs.size(); i++) {
                // 把满足执行条件的 job 加入 mPendingJobs
                mPendingJobs.add(runnableJobs.get(i));
            }
        } 
    }
    |
    |
    /**
     * Criteria for moving a job into the pending queue:
     *      - It's ready.
     *      - It's not pending.
     *      - It's not already running on a JSC.
     *      - The user that requested the job is running.
     *      从这个条件看 JobScheduler 只在 App 进程活着的时候起作用?注意是 Uid(App 安装时确定), 不是Pid
     */
    private boolean isReadyToBeExecutedLocked(JobStatus job) {
        final boolean jobReady = job.isReady(); // deadlineConstraintSatisfied 得到满足或者其余约束条件都已经得到满足
        final boolean jobPending = mPendingJobs.contains(job);
        final boolean jobActive = isCurrentlyActiveLocked(job);
        final boolean userRunning = mStartedUsers.contains(job.getUserId());
    
        if (DEBUG) {
            Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
                    + " ready=" + jobReady + " pending=" + jobPending
                    + " active=" + jobActive + " userRunning=" + userRunning);
        }
        return userRunning && jobReady && !jobPending && !jobActive;
    }
    
    

    6. 从 mPendingJobs 中取出 Job 执行

    /**
     * Reconcile jobs in the pending queue against available execution contexts.
     * A controller can force a job into the pending queue even if it's already running, but
     * here is where we decide whether to actually execute it.
     * 从 mPendingJobs 中取出 Job 执行
     */
    private void maybeRunPendingJobsH() {
        synchronized (mJobs) {
            // 从 mPendingJobs 中取出 Job 执行
            Iterator<JobStatus> it = mPendingJobs.iterator();
            while (it.hasNext()) {
                JobStatus nextPending = it.next();
                JobServiceContext availableContext = null;
                // mActiveServices.size() == MAX_JOB_CONTEXTS_COUNT == 3
                for (int i=0; i<mActiveServices.size(); i++) {
                    JobServiceContext jsc = mActiveServices.get(i);
                    final JobStatus running = jsc.getRunningJob();
                    if (running != null && running.matches(nextPending.getUid(), nextPending.getJobId())) {
                        // Already running this job for this uId, skip.
                        availableContext = null;
                        break;
                    }
                    // 判断该 jsc 是否已被占用
                    if (jsc.isAvailable()) {
                        availableContext = jsc;
                    }
                }
                if (availableContext != null) {
                    // 真正执行 Job 的地方
                    if (!availableContext.executeRunnableJob(nextPending)) {
                        mJobs.remove(nextPending);
                    }
                    it.remove();
                }
            }
        }
    }
    
    
    

    五、JobServiceContext 执行 Job 流程

    /**
     * Give a job to this context for execution. Callers must first check {@link #isAvailable()}
     * to make sure this is a valid context.
     * @param job The status of the job that we are going to run.
     * @return True if the job is valid and is running. False if the job cannot be executed.
     */
    boolean executeRunnableJob(JobStatus job) {
        synchronized (mLock) {
            if (!mAvailable) {
                Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
                return false;
            }
    
            mRunningJob = job;
            mParams = new JobParameters(this, job.getJobId(), job.getExtras(), !job.isConstraintsSatisfied());
            mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
    
            mVerb = VERB_BINDING;
            scheduleOpTimeOut();
            final Intent intent = new Intent().setComponent(job.getServiceComponent());
            boolean binding = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
                    new UserHandle(job.getUserId()));
            if (!binding) {
                if (DEBUG) {
                    Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
                }
                mRunningJob = null;
                mParams = null;
                mExecutionStartTimeElapsed = 0L;
                removeOpTimeOut();
                return false;
            }
            try {
                mBatteryStats.noteJobStart(job.getName(), job.getUid());
            } catch (RemoteException e) {
                // Whatever.
            }
            mAvailable = false;
            return true;
        }
    }
    
    
    

    七、ContextImp bindServiceAsUser() 流程

    @Override
    public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user) {
        return bindServiceCommon(service, conn, flags, user);
    }
    
    
    private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
                                      UserHandle user) {
        IServiceConnection sd;
        if (conn == null) {
            throw new IllegalArgumentException("connection is null");
        }
        if (mPackageInfo != null) {
            sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
                    mMainThread.getHandler(), flags);
        } else {
            throw new RuntimeException("Not supported in system context");
        }
        validateServiceIntent(service);
        try {
            IBinder token = getActivityToken();
            if (token == null && (flags & BIND_AUTO_CREATE) == 0 && mPackageInfo != null
                    && mPackageInfo.getApplicationInfo().targetSdkVersion
                    < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                flags |= BIND_WAIVE_PRIORITY;
            }
            service.prepareToLeaveProcess();
            int res = ActivityManagerNative.getDefault().bindService(
                    mMainThread.getApplicationThread(), getActivityToken(),
                    service, service.resolveTypeIfNeeded(getContentResolver()),
                    sd, flags, user.getIdentifier());
            if (res < 0) {
                throw new SecurityException(
                        "Not allowed to bind to service " + service);
            }
            return res != 0;
        } catch (RemoteException e) {
            return false;
        }
    }
    
    

    相关文章

      网友评论

        本文标题:JobSchedulerService 源码分析 -- 注册 J

        本文链接:https://www.haomeiwen.com/subject/igctbxtx.html