美文网首页
JobScheduler相关-使用及源码学习

JobScheduler相关-使用及源码学习

作者: weiinter105 | 来源:发表于2019-02-21 18:55 被阅读0次

    前言

    JobScheduler是Android5.0 开始引入了一个新系统服务。它将后台任务调度直接交给系统服务(JobSchedulerSevice)管理,并且可以设置许多约束条件,如周期调度,延迟调度,网络连接,电源插入,还有AndroidL引入的空闲模式,在条件符合的情况下,系统服务BindService的方式把应用内Manifest中配置的JobService启动起来,并通过进程间通信Binder方式调用JobService的onStartJob、onStopJob等方法来进行Job的管理。即便在执行任务之前应用程序进程被杀,也不会导致任务中断,Jobservice不会因应用退出而退出,但确实是运行在该应用进程中;常用于有条件的后台任务(约束条件为空不能执行),无约束条件的直接用IntentService;本质还是个Service,因此主线程,如(onStartJob)不能进行耗时操作,因此需要配合多线程的方式,如Handler,AsyncTask等使用

    使用范例

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
    
    
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
    
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
    
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".TestJobService"
                 android:permission="android.permission.BIND_JOB_SERVICE"
            />
    </application>
    
    public class MainActivity extends AppCompatActivity {
    
        private Button btn;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            btn = (Button)findViewById(R.id.button);
            ComponentName JobServicename = new ComponentName(this,TestJobService.class);
            final JobInfo.Builder job_builder = new JobInfo.Builder(0,JobServicename).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
            final JobScheduler jobScheduler = (JobScheduler)getSystemService(Context.JOB_SCHEDULER_SERVICE);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    jobScheduler.schedule(job_builder.build());
                }
            });
    
        }
    }
    
    import android.annotation.SuppressLint;
    import android.app.job.JobParameters;
    import android.app.job.JobService;
    import android.content.Context;
    import android.content.Intent;
    import android.net.wifi.WifiManager;
    import android.util.Log;
    
    import java.util.concurrent.ExecutionException;
    
    public class TestJobService extends JobService {
    
        private TestAsyncTask task = null;
    
        public TestJobService() {
            super();
        }
      
     //JobService本质还是个Service,因此onStartJob,onStopJob也会在主线程调用;因此耗时操作不能在onStartJob中执行,可以在其中用AsyncTask来进行耗时操作
        @Override
        public void onCreate() {
            Log.i("weijuncheng","----------------------------->onCreate");
            super.onCreate();
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.i("weijuncheng","----------------------------->onStartCommand");
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onDestroy() {
            Log.i("weijuncheng","----------------------------->onDestory");
            super.onDestroy();
        }
    
        @Override
        public boolean onStartJob(JobParameters params) {
            Log.i("weijuncheng","----------------------------->onStartJob");
    
    
            task = new TestAsyncTask();
            task.execute();
            //wifi确实disable了,但还是不调用onStopJob
            /*
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            WifiManager wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
            if(wifiManager.isWifiEnabled()){
                wifiManager.setWifiEnabled(false);
                Log.i("weijuncheng","----------------------------->wifi disable");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            */
    
            /*
            try {
                task.get(); //用于阻塞,得到AysncTask的返回结果(注意,在主线程不能调用get,因为是阻塞式的,如果调用就相当于主线程耗时,会触发ANR)
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            */
            //不要这段就是非阻塞的
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //WifiManager wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
            //if(wifiManager.isWifiEnabled()){
            //    wifiManager.setWifiEnabled(false);
            //    Log.i("weijuncheng","----------------------------->wifi disable");
            //}
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.i("weijuncheng","----------------------------->onStartJob done");
            return true; //return false;就不会调用onStopJob;即使条件不再满足,也不会走到终止流程
       // 在任务开始执行时触发。返回false表示执行完毕,返回true表示需要开发者自己调用jobFinished方法通知系统已执行完成。
       
        }
    
        @Override
        public boolean onStopJob(JobParameters params) {
            Log.i("weijuncheng","----------------------------->onStopJob");
            task.cancel(true);
            return false; //在任务停止执行时触发。返回true 为重新调度,返回false 表示不会重新调度,job完全被停止了
        }
    }
    
    import android.os.AsyncTask;
    import android.util.Log;
    
    public class TestAsyncTask extends AsyncTask<Void,Void,Void> {
        private boolean flag = true;
    
        @Override
        protected void onCancelled(Void aVoid) {
            flag = false;
            Log.i("weijuncheng","TestAsyncTask onCancelled 1");
            super.onCancelled(aVoid);
        }
    
        @Override
        protected void onCancelled() {
            flag = false;
            Log.i("weijuncheng","TestAsyncTask onCancelled 2");
            super.onCancelled();
        }
    
        @Override
        protected Void doInBackground(Void... voids) {
            Log.i("weijuncheng","TestAsyncTask doInBackground");
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    for(int i = 0;i<5*60;i++){
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if(flag) {
                            Log.i("weijuncheng", "TestAsyncTask doInBackground doing");
                        }else{
                            Log.i("weijuncheng","TestAsyncTask doInBackground cancelled");
                            break;
                        }
    
    
    
                    }
                }
            }.start();
            try {
                Thread.sleep(1000*60*5); //模拟子线程中的耗时操作,这个操作需要5min
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
            Log.i("weijuncheng","TestAsyncTask doInBackground done");
            return null;
        }
    
        @Override
        protected void onPostExecute(Void aVoid) {
            Log.i("weijuncheng","TestAsyncTask onPostExecute done");
            super.onPostExecute(aVoid);
        }
    }
    

    执行一会关掉wifi,log如下

    02-21 07:52:45.170  1755  1755 I weijuncheng: ----------------------------->onCreate
    02-21 07:52:45.175  1755  1755 I weijuncheng: ----------------------------->onStartJob
    02-21 07:52:45.177  1755  1789 I weijuncheng: TestAsyncTask doInBackground
    02-21 07:52:46.178  1755  1791 I weijuncheng: TestAsyncTask doInBackground doing
    02-21 07:52:47.177  1755  1755 I weijuncheng: ----------------------------->onStartJob done
    02-21 07:52:47.179  1755  1791 I weijuncheng: TestAsyncTask doInBackground doing
    02-21 07:52:48.179  1755  1791 I weijuncheng: TestAsyncTask doInBackground doing
    02-21 07:52:49.179  1755  1791 I weijuncheng: TestAsyncTask doInBackground doing
    02-21 07:52:52.180  1755  1791 I chatty  : uid=10106(com.test.weijuncheng.testjobscheduler) Thread-2 identical 3 lines
    02-21 07:52:53.180  1755  1791 I weijuncheng: TestAsyncTask doInBackground doing
    02-21 07:52:54.181  1755  1791 I weijuncheng: TestAsyncTask doInBackground doing
    02-21 07:52:55.168  1755  1755 I weijuncheng: ----------------------------->onStopJob
    02-21 07:52:55.170  1755  1789 W System.err: java.lang.InterruptedException
    02-21 07:52:55.179  1755  1755 I weijuncheng: ----------------------------->onDestory
    02-21 07:52:55.181  1755  1791 I weijuncheng: TestAsyncTask doInBackground doing
    02-21 07:52:55.182  1755  1789 W System.err:    at java.lang.Thread.sleep(Native Method)
    02-21 07:52:55.183  1755  1789 W System.err:    at java.lang.Thread.sleep(Thread.java:373)
    02-21 07:52:55.183  1755  1789 W System.err:    at java.lang.Thread.sleep(Thread.java:314)
    02-21 07:52:55.184  1755  1789 W System.err:    at com.test.weijuncheng.testjobscheduler.TestAsyncTask.doInBackground(TestAsyncTask.java:49)
    02-21 07:52:55.184  1755  1789 W System.err:    at com.test.weijuncheng.testjobscheduler.TestAsyncTask.doInBackground(TestAsyncTask.java:6)
    02-21 07:52:55.184  1755  1789 W System.err:    at android.os.AsyncTask$2.call(AsyncTask.java:333)
    02-21 07:52:55.185  1755  1789 W System.err:    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    02-21 07:52:55.186  1755  1789 W System.err:    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
    02-21 07:52:55.186  1755  1789 W System.err:    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
    02-21 07:52:55.186  1755  1789 W System.err:    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
    02-21 07:52:55.187  1755  1789 W System.err:    at java.lang.Thread.run(Thread.java:764)
    02-21 07:52:55.187  1755  1789 I weijuncheng: TestAsyncTask doInBackground done
    02-21 07:52:55.187  1755  1755 I weijuncheng: TestAsyncTask onCancelled 1
    02-21 07:52:55.187  1755  1755 I weijuncheng: TestAsyncTask onCancelled 2
    02-21 07:52:56.182  1755  1791 I weijuncheng: TestAsyncTask doInBackground cancelled
    

    源码分析

    jobscheduler的调用是一个典型的跨进程通信的流程
    客户端(JobScheduler.scheduler)-system_server系统进程(中转)-服务端(提供JobService)

    服务端-JobService

    public abstract class JobService extends Service {
        private static final String TAG = "JobService";
    
        /**
         * Job services must be protected with this permission:
         *
         * <pre class="prettyprint">
         *     &#60;service android:name="MyJobService"
         *              android:permission="android.permission.BIND_JOB_SERVICE" &#62;
         *         ...
         *     &#60;/service&#62;
         * </pre>
         *
         * <p>If a job service is declared in the manifest but not protected with this
         * permission, that service will be ignored by the system.
         */
        public static final String PERMISSION_BIND =
                "android.permission.BIND_JOB_SERVICE";
    
        private JobServiceEngine mEngine;
    
        /** @hide */
        public final IBinder onBind(Intent intent) {
            if (mEngine == null) {
                mEngine = new JobServiceEngine(this) {
                    @Override
                    public boolean onStartJob(JobParameters params) {
                        return JobService.this.onStartJob(params);
                    }
    
                    @Override
                    public boolean onStopJob(JobParameters params) {
                        return JobService.this.onStopJob(params);
                    }
                };
            }
            return mEngine.getBinder();
        }
    
    public abstract boolean onStartJob(JobParameters params);
    
    public abstract boolean onStopJob(JobParameters params);
    

    服务端本质上还是个Service,可以被bindService,从而通过返回的binder对象调用JobService中重载的onStartJob和onStopJob

    客户端-JobScheduler.scheduler

    当调用scheduler() 便开始启动该job 的任务,但是不一定立马执行

    @SystemService(Context.JOB_SCHEDULER_SERVICE)
    public abstract class JobScheduler {
        /** @hide */
        @IntDef(prefix = { "RESULT_" }, value = {
                RESULT_FAILURE,
                RESULT_SUCCESS,
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface Result {}
    
        /**
         * Returned from {@link #schedule(JobInfo)} when an invalid parameter was supplied. This can occur
         * if the run-time for your job is too short, or perhaps the system can't resolve the
         * requisite {@link JobService} in your package.
         */
        public static final int RESULT_FAILURE = 0;
        /**
         * Returned from {@link #schedule(JobInfo)} if this job has been successfully scheduled.
         */
        public static final int RESULT_SUCCESS = 1;
    
        /**
         * Schedule a job to be executed.  Will replace any currently scheduled job with the same
         * ID with the new information in the {@link JobInfo}.  If a job with the given ID is currently
         * running, it will be stopped.
         *
         * @param job The job you wish scheduled. See
         * {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs
         * you can schedule.
         * @return the result of the schedule request.
         */
        public abstract @Result int schedule(@NonNull JobInfo job);
    

    其实现类为JobSchedulerImpl

    /**
     * Concrete implementation of the JobScheduler interface
     * @hide 
     */
    public class JobSchedulerImpl extends JobScheduler {
        IJobScheduler mBinder;
    
        /* package */ JobSchedulerImpl(IJobScheduler binder) {
            mBinder = binder;
        }
    
        @Override
        public int schedule(JobInfo job) {
            try {
                return mBinder.schedule(job);
            } catch (RemoteException e) {
                return JobScheduler.RESULT_FAILURE;
            }
        }
    

    通过binder call调用到system_server中的JobSchedulerService

    system_server端-JobSchedulerService

    构造函数

    /**
     * Initializes the system service.
     * <p>
     * Subclasses must define a single argument constructor that accepts the context
     * and passes it to super.
     * </p>
     *
     * @param context The system server context.
     */
    public JobSchedulerService(Context context) {
        super(context);
    
        mLocalPM = LocalServices.getService(PackageManagerInternal.class);
        mActivityManagerInternal = Preconditions.checkNotNull(
                LocalServices.getService(ActivityManagerInternal.class));
    
     
        mHandler = new JobHandler(context.getMainLooper());
        mConstants = new Constants();
        mConstantsObserver = new ConstantsObserver(mHandler);
        mJobSchedulerStub = new JobSchedulerStub();
    
        // Set up the app standby bucketing tracker
        mStandbyTracker = new StandbyTracker();
        mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
        mUsageStats.addAppIdleStateChangeListener(mStandbyTracker);
    
        // The job store needs to call back
        publishLocalService(JobSchedulerInternal.class, new LocalService());
    
        // Initialize the job store and set up any persisted jobs
        mJobs = JobStore.initAndGet(this); // 初始化JobStore ,Jobstore 是干嘛的呢,主要是存放Job 任务的一个列表,并记录Job 的运行情况,时长等详细信息到/data/system/job/jobs.xml
    
        // Create the controllers.
        mControllers = new ArrayList<StateController>();
        mControllers.add(new ConnectivityController(this));
        mControllers.add(new TimeController(this));
        mControllers.add(new IdleController(this));
        mBatteryController = new BatteryController(this);
        mControllers.add(mBatteryController);
        mStorageController = new StorageController(this);
        mControllers.add(mStorageController);
        mControllers.add(new BackgroundJobsController(this));
        mControllers.add(new ContentObserverController(this));
        mDeviceIdleJobsController = new DeviceIdleJobsController(this);
        mControllers.add(mDeviceIdleJobsController); // 初始化各种controller ,每个controller 都对应着JobInfo 里面的一个set 的Job运行条件,目前共有连接,时间,idle(设备空闲),充电,存储,AppIdle,ContentObserver,DeviceIdle(Doze) 的控制器
    
        // If the job store determined that it can't yet reschedule persisted jobs,
        // we need to start watching the clock.
        if (!mJobs.jobTimesInflatedValid()) {
            Slog.w(TAG, "!!! RTC not yet good; tracking time updates for job scheduling");
            context.registerReceiver(mTimeSetReceiver, new IntentFilter(Intent.ACTION_TIME_CHANGED));
        }
    }
    

    JobSchedulerService#schedule

    // IJobScheduler implementation
    @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();
        final int userId = UserHandle.getUserId(uid);
    
        enforceValidJobRequest(uid, job);
        if (job.isPersisted()) {
            if (!canPersistJobs(pid, uid)) {
                throw new IllegalArgumentException("Error: requested job be persisted without"
                        + " holding RECEIVE_BOOT_COMPLETED permission.");
            }
        }
    
        validateJobFlags(job, uid);
    
        long ident = Binder.clearCallingIdentity();
        try {
            return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
                    null);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
    

    JobSchedulerService#scheduleAsPackage

    public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
            int userId, String tag) {
        try {
            if (ActivityManager.getService().isAppStartModeDisabled(uId,
                    job.getService().getPackageName())) {  //这里通过AMS来判断packageName该应该是否允许启动该服务
                Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
                        + " -- package not allowed to start");
                return JobScheduler.RESULT_FAILURE;
            }
        } catch (RemoteException e) {
        }
    
        synchronized (mLock) {
            final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId()); // 判断系统中该Uid 的App对应的Jobid 是否已经存在系统中
    
            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(ActivityManager.getService(), 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); //根据jobInfo创建jobStatus
    
            // 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 (ENFORCE_MAX_JOBS && 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(ActivityManager.getService());
    
            if (toCancel != null) {
                cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app"); // 如果系统中已经存在了同一个uid 里面的同一个jobId 的job ,那么先cancle 这个job
            }
            if (work != null) {
                // If work has been supplied, enqueue it into the new job.
                jobStatus.enqueueWorkLocked(ActivityManager.getService(), work); //如果执行了work队列那么 将jobStatus 放入指定的work队列里
            }
            startTrackingJobLocked(jobStatus, toCancel); // 开始将App 的所有的Job的放到mJobs里表里,并且对每个job 指定对应的不同Controller
            StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED,
                    uId, null, jobStatus.getBatteryName(),
                    StatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED,
                    JobProtoEnums.STOP_REASON_CANCELLED);
    
            // 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)) {
               //如果一个job 满足一定条件需要立即执行,那么会将其放在pending 列表中,并且在后面马上处理
                // This is a new job, we can just immediately put it on the pending
                // list and try to run it.
                mJobPackageTracker.notePending(jobStatus);
                addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
                maybeRunPendingJobsLocked();
            }
        }
        return JobScheduler.RESULT_SUCCESS;
    }
    

    JobSchedulerService#startTrackingJobLocked

    当设置一个Job 的时候,会循环所有的Controller 来为其指定限制该Job 的Controller

    private void startTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
        if (!jobStatus.isPreparedLocked()) {
            Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
        }
        jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
        final boolean update = mJobs.add(jobStatus);
        if (mReadyToRock) {
            for (int i = 0; i < mControllers.size(); i++) {
                StateController controller = mControllers.get(i);
                if (update) {
                    controller.maybeStopTrackingJobLocked(jobStatus, null, true);
                }
                controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
            }
        }
    }
    

    如ConnectivityController

    ConnectivityController#maybeStartTrackingJobLocked

    @GuardedBy("mLock")
    @Override
    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
        if (jobStatus.hasConnectivityConstraint()) {
            updateConstraintsSatisfied(jobStatus);
            mTrackedJobs.add(jobStatus); //添加到ConnectivityController的mTrackedJobs中
            jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY); //为JobStatus添加相应Controller
        }
    }
    

    监听条件变化,当条件变化时;ConnectivityController便是在注册ConnectivityManager.OnNetworkActiveListener,当连接状态发生改变时候,更新状态参数

    /**
     * Callback for use with {@link ConnectivityManager#addDefaultNetworkActiveListener}
     * to find out when the system default network has gone in to a high power state.
     */
    public interface OnNetworkActiveListener {
        /**
         * Called on the main thread of the process to report that the current data network
         * has become active, and it is now a good time to perform any pending network
         * operations.  Note that this listener only tells you when the network becomes
         * active; if at any other time you want to know whether it is active (and thus okay
         * to initiate network traffic), you can retrieve its instantaneous state with
         * {@link ConnectivityManager#isDefaultNetworkActive}.
         */
        public void onNetworkActive();
    }
    
    /**
     * We know the network has just come up. We want to run any jobs that are ready.
     */
    @Override
    public void onNetworkActive() {
        synchronized (mLock) {
            for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
                final JobStatus js = mTrackedJobs.valueAt(i); //保存Controller track的JobStatus
                if (js.isReady()) {
                    if (DEBUG) {
                        Slog.d(TAG, "Running " + js + " due to network activity.");
                    }
                    mStateChangedListener.onRunJobNow(js);
                }
            }
        }
    }
    

    JobSchedulerService#maybeRunPendingJobsLocked

    执行pending list中的待执行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.
     */
    private void maybeRunPendingJobsLocked() {
        if (DEBUG) {
            Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
        }
        assignJobsToContextsLocked();
        reportActiveLocked();
    }
    
    /**
     * Takes jobs from pending queue and runs them on available contexts.
     * If no contexts are available, preempts lower priority jobs to
     * run higher priority ones.
     * Lock on mJobs before calling this function.
     */
    private void assignJobsToContextsLocked() {
        if (DEBUG) {
            Slog.d(TAG, printPendingQueue());
        }
    
        int memLevel;
        try {
            memLevel = ActivityManager.getService().getMemoryTrimLevel();
        } catch (RemoteException e) {
            memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
        }
        switch (memLevel) {
            case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
                mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
                break;
            case ProcessStats.ADJ_MEM_FACTOR_LOW:
                mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
                break;
            case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
                mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
                break;
            default:
                mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
                break;
        }
    
        JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
        boolean[] act = mTmpAssignAct;
        int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
        int numActive = 0;
        int numForeground = 0;
        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
            final JobServiceContext js = mActiveServices.get(i);
            final JobStatus status = js.getRunningJobLocked();
            if ((contextIdToJobMap[i] = status) != null) {
                numActive++;
                if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
                    numForeground++;
                }
            }
            act[i] = false;
            preferredUidForContext[i] = js.getPreferredUid();
        }
        if (DEBUG) {
            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
        }
        for (int i=0; i<mPendingJobs.size(); i++) {
            JobStatus nextPending = mPendingJobs.get(i);
    
            // If job is already running, go to next job.
            int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
            if (jobRunningContext != -1) {
                continue;
            }
    
            final int priority = evaluateJobPriorityLocked(nextPending);
            nextPending.lastEvaluatedPriority = priority;
    
            // Find a context for nextPending. The context should be available OR
            // it should have lowest priority among all running jobs
            // (sharing the same Uid as nextPending)
            int minPriority = Integer.MAX_VALUE;
            int minPriorityContextId = -1;
            for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
                JobStatus job = contextIdToJobMap[j];
                int preferredUid = preferredUidForContext[j];
                if (job == null) {
                    if ((numActive < mMaxActiveJobs ||
                            (priority >= JobInfo.PRIORITY_TOP_APP &&
                                    numForeground < mConstants.FG_JOB_COUNT)) &&
                            (preferredUid == nextPending.getUid() ||
                                    preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
                        // This slot is free, and we haven't yet hit the limit on
                        // concurrent jobs...  we can just throw the job in to here.
                        minPriorityContextId = j;
                        break;
                    }
                    // No job on this context, but nextPending can't run here because
                    // the context has a preferred Uid or we have reached the limit on
                    // concurrent jobs.
                    continue;
                }
                if (job.getUid() != nextPending.getUid()) {
                    continue;
                }
                if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
                    continue;
                }
                if (minPriority > nextPending.lastEvaluatedPriority) {
                    minPriority = nextPending.lastEvaluatedPriority;
                    minPriorityContextId = j;
                }
            }
            if (minPriorityContextId != -1) {
                contextIdToJobMap[minPriorityContextId] = nextPending;
                act[minPriorityContextId] = true;
                numActive++;
                if (priority >= JobInfo.PRIORITY_TOP_APP) {
                    numForeground++;
                }
            }
        }
        if (DEBUG) {
            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
        }
        mJobPackageTracker.noteConcurrency(numActive, numForeground);
        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
            boolean preservePreferredUid = false;
            if (act[i]) {
                JobStatus js = mActiveServices.get(i).getRunningJobLocked();
                if (js != null) {
                    if (DEBUG) {
                        Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJobLocked());
                    }
                    // preferredUid will be set to uid of currently running job.
                    mActiveServices.get(i).preemptExecutingJobLocked();
                    preservePreferredUid = true;
                } else {
                    final JobStatus pendingJob = contextIdToJobMap[i];
                    if (DEBUG) {
                        Slog.d(TAG, "About to run job on context "
                                + String.valueOf(i) + ", job: " + pendingJob);
                    }
                    for (int ic=0; ic<mControllers.size(); ic++) {
                        mControllers.get(ic).prepareForExecutionLocked(pendingJob);
                    }
                    if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
                        Slog.d(TAG, "Error executing " + pendingJob);
                    }
                    if (mPendingJobs.remove(pendingJob)) {
                        mJobPackageTracker.noteNonpending(pendingJob);
                    }
                }
            }
            if (!preservePreferredUid) {
                mActiveServices.get(i).clearPreferredUid();
            }
        }
    

    在assignJobsToContextsLocked 完成各种计算后,将mActiveService的可以运行的Job,调用executeRunnableJob 方法;调用到JobServiceContext 中,开始启动服务bindService。在当服务binder上App端的JobService 服务,回调回onServiceConnected()

    JobServiceContext#executeRunnableJob

    /**
     * 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) {
        synchronized (mLock) {
            if (!mAvailable) {
                Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
                return false;
            }
    
            mPreferredUid = NO_PREFERRED_UID;
    
            mRunningJob = job;
            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, triggeredUris, triggeredAuthorities, job.network);
            mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
    
            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();
    
            mVerb = VERB_BINDING;
            scheduleOpTimeOutLocked();
            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()));; //根据jobStatus中的信息调用bindService
    
    
            if (!binding) {
                if (DEBUG) {
                    Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
                }
                mRunningJob = null;
                mRunningCallback = null;
                mParams = null;
                mExecutionStartTimeElapsed = 0L;
                mVerb = VERB_FINISHED;
                removeOpTimeOutLocked();
                return false;
            }
            mJobPackageTracker.noteActive(job);
            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);
            JobSchedulerInternal jobScheduler =
                    LocalServices.getService(JobSchedulerInternal.class);
            jobScheduler.noteJobStart(jobPackage, jobUserId);
            mAvailable = false;
            mStoppedReason = null;
            mStoppedTime = 0;
            return true;
        }
    }
    

    JobServiceContext#onServiceConnected

    /**
     * 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;
            }
            this.service = IJobService.Stub.asInterface(service);
            final PowerManager pm =
                    (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
            PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    runningJob.getTag());
            wl.setWorkSource(deriveWorkSource(runningJob));
            wl.setReferenceCounted(false);
            wl.acquire();
    
            // We use a new wakelock instance per job.  In rare cases there is a race between
            // teardown following job completion/cancellation and new job service spin-up
            // such that if we simply assign mWakeLock to be the new instance, we orphan
            // the currently-live lock instead of cleanly replacing it.  Watch for this and
            // explicitly fast-forward the release if we're in that situation.
            if (mWakeLock != null) {
                Slog.w(TAG, "Bound new job " + runningJob + " but live wakelock " + mWakeLock
                        + " tag=" + mWakeLock.getTag());
                mWakeLock.release();
            }
            mWakeLock = wl;
            doServiceBoundLocked();
        }
    }
    
    @GuardedBy("mLock")
    void doServiceBoundLocked() {
        removeOpTimeOutLocked();
        handleServiceBoundLocked();
    }
    
    /** 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();
            service.startJob(mParams); //调用onStartJob
        } 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);
        }
    }
    

    总结

    源码的大致流程如图


    JobScheduler.png

    使用时需要注意,需要注意的是这个job service运行在你的主线程,这意味着你需要使用子线程,handler, 或者一个异步任务来运行耗时的操作以防止阻塞主线程

    相关文章

      网友评论

          本文标题:JobScheduler相关-使用及源码学习

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