Android JobService和JobScheduler 原理剖析

2024-02-04 14:10

本文主要是介绍Android JobService和JobScheduler 原理剖析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

请支持原创~~

版本基于:Android R 

0. 前言

最近在研究Iorap 的时候碰到了JobScheduler,这套机制是在Android L中就提出来的,而之前没有使用到也就不知其存在。简单过滤了下framework 的代码,发现虽然是一个小模块,却是五脏俱全、环环相扣。由于时间原因,本文主要结合实际情形简单分析JobScheduler 的实现过程。

1. 框架图

 本图来源:JobService源码探究之 onStartJob()返回false立马被destroy_TechMerger的博客-CSDN博客

本来该自己动的,但想想即使自己动手内容未必有这个好,索性就站在巨人的肩膀上继续深入发展吧!!这里有需要对 TechMerger 说声辛苦和感谢!

2.  基本概念

框架图已经准备好了,但还是要结合代码才能深入了解。在深入分析之前需要知道,JobScheduler 中涉及模块比较多,心中有个概念才可以深入了解,当然,现在所说的概念也都是在完全了解原理后才深刻总结出来的。

下面将这些概念笼统的分为:应用端、传输端、系统端。

当然,这些划分只是为了更好的理解JobScheduler 中概念,并不是一定可以划清界限。例如,IJobCallback 本身就串联App 和 system,但因为实现在system ,所以就将其分到system 端。

2.1 应用端

2.1.1 JobService

frameworks/base/apex/jobscheduler/framework/java/android/app/job/JobService.java

这个是整个框架的核心和源头,App 想要使用这套机制,则需要创建一个JobService

public abstract class JobService extends Service {

它是一个抽象类,也是Service 的子类。得知,App 最终实现的就是普通的service。

这个类主要处理的是JobScheduler 的回调。

public abstract boolean onStartJob(JobParameters params);
public abstract boolean onStopJob(JobParameters params);

App 为了Job 会设定一些条件(详细看JobInfo),当条件满足后,JobScheduler就会安排此Job 进入schedule,从而触发onStartJob,App 被回调时处理自己的逻辑。

JobScheduler 的调用后面会详细说明,这里需要了解JobService 被回调的源头:

    public final IBinder onBind(Intent intent) {if (mEngine == null) {mEngine = new JobServiceEngine(this) {@Overridepublic boolean onStartJob(JobParameters params) {return JobService.this.onStartJob(params);}@Overridepublic boolean onStopJob(JobParameters params) {return JobService.this.onStopJob(params);}};}return mEngine.getBinder();}

 Service 的onBind 时会创建JobServiceEngine,JobScheduler 就是通过它拿到了binder proxy。

详细的JobServiceEngine 可以看下一节,这里需要注意的是onStartJob 和 onStopJob 的返回值,不同的返回值触发不同的逻辑,影响job 是否继续执行等。详细的后面会说明。

除此,JobService 中还提供了一个函数用于结束当前Job:

    public final void jobFinished(JobParameters params, boolean wantsReschedule) {mEngine.jobFinished(params, wantsReschedule);}

这里只是简单提一下基本概念,函数详细使用方法,下面会详细说明。

2.1.2  JobServiceEngine

frameworks/base/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java

这里是JobScheduler(JobService 的client端) 调用的服务端,JobScheduler 通过aidl 通知JobServiceEngine 是start job 还是stop job,由JobServiceEngine 回调JobService 进行逻辑处理,详细看IJobService.aidl:

package android.app.job;import android.app.job.JobParameters;oneway interface IJobService {/** Begin execution of application's job. */@UnsupportedAppUsagevoid startJob(in JobParameters jobParams);/** Stop execution of application's job. */@UnsupportedAppUsagevoid stopJob(in JobParameters jobParams);
}

在JobServiceEngine 中实现了stub 端:

    static final class JobInterface extends IJobService.Stub {...}

详细可以在里面的实现,这个类只是个过渡,JobInterface 在收到JobScheduler 的startJob和stopJob 会通过handler 进行异步处理,最终调用JobService 的onStartJob 和onStopJob。

这里需要特别注意的是:

    public JobServiceEngine(Service service) {mBinder = new JobInterface(this);mHandler = new JobHandler(service.getMainLooper());}

mHandler 使用的是主线程,所以要求:

JobService 中onStartJob 和onStopJob 在处理逻辑的时候用其他的thread/handler/AsynTask。

例如,

  • 当job 在start 逻辑处理时很耗时,而此时UI 上进行按键操作,就可能导致长时间无法收到响应而出现ANR。
  • 如果start 逻辑处理耗时,将一直阻塞stop 的接收。(两个过程是互斥的,有lock)

2.2 传输端

之所以有这个划分,主要是为了突出同时在App 和 framework 端传输,当然严格将IJobService也算,放应用端是因为其跟随JobServiceEngine。

2.2.1 JobInfo

frameworks/base/apex/jobscheduler/framework/java/android/app/job/JobInfo.aidl

package android.app.job;parcelable JobInfo;

从定义来看JobInfo 是一个parcelable 类,实现在JobInfo.java 中:

frameworks/base/apex/jobscheduler/framework/java/android/app/job/JobInfo.java

public class JobInfo implements Parcelable {

JobInfo 中包含了传递给JobScheduler 所需的数据,数据中概括了schedule work 所有相关的参数。这些数据使用JobInfo.Builder 构造,这里提供给scheduler关于想要完成工作的high-level semantics。

Android Q 以前的版本,JobInfo 对象中必须要指定至少一个constraint,否则,Builder 会在build 的时候会抛出exception。从Android Q开始,即使不指定constraint 也是有效的。

JobInfo.Builder 中成员简介:

  •  变量mJobId 是该Job 的唯一标识,与应用的uid 相关。如果是App 之间共用一个UID,那么要防止jobId 冲突;
  • setRequiredNetworkType 设置job 需求的网络类型,如果不需要网络连接,则不需要调用,默认为JobInfo.NETWORK_TYPE_NONE;
  • setRequiredNetwork 设置job 需求的网络类型,同上,区别在于参数,这里可以设置多个请求;
  • setRequiresCharging  设置job 请求,若设为true,设备为charging 状态,或者是没有电池时连接电源(TV)时执行,默认为false;
  • setRequiresBatteryNotLow  设置job 请求,若设为true,battery level 不能为low是执行,默认为false;
  • setRequiresDeviceIdle  设置job 请求,如果设为true,设备处于active use 状态不会运行job;如果设为false,即使有人与设备交互,job 也可以运行;
  • setRequiresStorageNotLow  设为job 请求,若设为true,设备的storage 不为low 时执行,默认为false;
  • setPeriodic   设置job的执行间隔,每个周期不超过一次。无法控制在此周期内何时执行,只能保证在此间隔内最多执行一次;

 详细的接口还是看源码吧!

 2.2.2 JobParameters

 frameworks/base/apex/jobscheduler/framework/java/android/app/job/JobParameters.aidl

package android.app.job;parcelable JobParameters;

从定义来看JobParameters 是一个parcelable 类,实现在JobParameters.java 中:

frameworks/base/apex/jobscheduler/framework/java/android/app/job/JobParameters.java

public class JobParameters implements Parcelable {

用来配置或标记job,onStartJob 或onStopJob 中从system传到App,在调用jobFinished 时可以作为参数传递给system。

需要注意两点:

  • getJobId  获取job id,在创建JobInfo 时传入的;
  • getCallback  获取的是callback 给system 的IJobCallback对象,详细看IJobCallback.aidl;

在JobServiceEngine 中说到,system 是通过IJobService 的startJob 和stopJob 调用,并在JobServiceEngine 中进行异步处理,需要注意的是,异步处理中会将App 中onStartJob 或onStopJob 的返回值callback 给system:

        public void handleMessage(Message msg) {final JobParameters params = (JobParameters) msg.obj;switch (msg.what) {case MSG_EXECUTE_JOB:try {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);}

上面是startJob 后的处理流程,会通过ackStartMessage 将App 中onStartJob 的返回值传递回system,而ackStartMessage 就是通过JobParameters 中的getCallback 获取sytem 的回调:

        private void ackStartMessage(JobParameters params, boolean workOngoing) {final IJobCallback callback = params.getCallback();final int jobId = params.getJobId();if (callback != null) {try {callback.acknowledgeStartMessage(jobId, workOngoing);} catch(RemoteException e) {Log.e(TAG, "System unreachable for starting job.");}} else {if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "Attempting to ack a job that has already been processed.");}}}

2.3 system 端

2.3.1 IJobCallback

 frameworks/base/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl

package android.app.job;import android.app.job.JobWorkItem;interface IJobCallback {void acknowledgeStartMessage(int jobId, boolean ongoing);void acknowledgeStopMessage(int jobId, boolean reschedule);JobWorkItem dequeueWork(int jobId);boolean completeWork(int jobId, int workId);void jobFinished(int jobId, boolean reschedule);
}

实现是在system 端,所以将其归为这一类。详细实现见JobServiceContext.java

例如acknowledgeStartMessage 是将onStartJob 的返回值告知system,因为在startJob 之前系统启动了一个timeout,定时为8s,如果在此timeout 内没有acknowledgeStartMessage 返回,就会出现job 回收等操作。

后面会针对特殊情况再详细分析!

2.3.2 JobScheduler

这个是App 向system 添加schedule 或者退出schedule 的入口。

例如,在IorapForwardingService.java 中:

        public IorapdJobService(Context context) {...JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_IORAPD, IORAPD_COMPONENT_NAME);builder.setPeriodic(JOB_INTERVAL_MS);builder.setPrefetch(true);builder.setRequiresCharging(true);builder.setRequiresDeviceIdle(true);builder.setRequiresStorageNotLow(true);IORAPD_JOB_INFO = builder.build();JobScheduler js = context.getSystemService(JobScheduler.class);js.schedule(IORAPD_JOB_INFO);}

最终是通过js.schedule(),将job 送入JobSchedulerService。

而JobScheduler 则是通过context.getSystemService 获取得到:

    public String getSystemServiceName(Class<?> serviceClass) {return SystemServiceRegistry.getSystemServiceName(serviceClass);}

我们知道SystemServcieRegistry 会在最开始构造前,将系统的所有系统应用都创建一个

class - binder 的关系:

frameworks/base/core/java/android/app/SystemServiceRegistry.java

    static {//CHECKSTYLE:OFF IndentationCheckregisterService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,new CachedServiceFetcher<AccessibilityManager>() {@Overridepublic AccessibilityManager createService(ContextImpl ctx) {return AccessibilityManager.getInstance(ctx);}});...registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,new CachedServiceFetcher<ActivityManager>() {@Overridepublic ActivityManager createService(ContextImpl ctx) {return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());}});...try {JobSchedulerFrameworkInitializer.registerServiceWrappers();...

代码的最后发现JobScheduler也在这里:

    public static void registerServiceWrappers() {SystemServiceRegistry.registerStaticService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class,(b) -> new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b)));SystemServiceRegistry.registerContextAwareService(Context.DEVICE_IDLE_CONTROLLER, DeviceIdleManager.class,(context, b) -> new DeviceIdleManager(context, IDeviceIdleController.Stub.asInterface(b)));SystemServiceRegistry.registerContextAwareService(Context.POWER_WHITELIST_MANAGER, PowerWhitelistManager.class,PowerWhitelistManager::new);}

原来通过context.getSystemService 最终获取到的是JobSchedulerImpl 对象:

 frameworks/base/apex/jobscheduler/framework/java/android/app/job/JobSchedulerImpl.java

public class JobSchedulerImpl extends JobScheduler {

JobSchedulerImpl 构造时传入IJobScheduler的proxy,所以,App 与system 交互其实都是通过JobSchedulerImpl 进行:

    public JobSchedulerImpl(IJobScheduler binder) {mBinder = binder;}@Overridepublic int schedule(JobInfo job) {try {return mBinder.schedule(job);} catch (RemoteException e) {return JobScheduler.RESULT_FAILURE;}}

2.3.3 JobSchedulerService

这里是整个机制的核心处理的地方,是JobScheduler server 端实现的地方。App 的请求,或者是通知App 启动Job,或者是timeout处理,各种controller 的集合都在这里。

这里实现了调度和重新调度的逻辑,但是JobSchedulerService 对于active job 的约束条件或状态一无所知。它接收来自不同controller 和已经完成的jobs 的回调,并相应地进行操作。

下面会用单独一节对这个文件进行剖析。

2.3.4 JobServiceContext

用于处理 client binding 和job 的生命周期。Jobs 每一次执行都会建立在一个JobServcieContext 实例上。下面跟随着JobSchedulerService 一同说明。

3. JobSchedulerService

同AMS 等,属于系统核心SystemService,在SystemServer 运行时启动:

framworks/base/services/java/com/android/server/SystemServer.java

            t.traceBegin("StartJobScheduler");mSystemServiceManager.startService(JOB_SCHEDULER_SERVICE_CLASS);t.traceEnd();

详细的startService 接口这里不做过多分析,需要的直接看源码,主要:

  • 通过构造函数创建一个SystemService 实例;
  • 调用onStart 接口;
  • 系统状态变化时,会通过startBootPhase 调用onBootPhase 通知SystemService;

3.1 构造

    public JobSchedulerService(Context context) {super(context); //context 来自SystemServer...mHandler = new JobHandler(context.getMainLooper()); //直接使用SystemServer 的主线程创建HandlermConstants = new Constants();mConstantsObserver = new ConstantsObserver(mHandler);mJobSchedulerStub = new JobSchedulerStub(); //JobScheduler 的实现mConcurrencyManager = new JobConcurrencyManager(this); //并发操作的管理,service 不管的都在这里mStandbyTracker = new StandbyTracker(); //standby 跟踪mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);mQuotaTracker = new CountQuotaTracker(context, QUOTA_CATEGORIZER);mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,mConstants.API_QUOTA_SCHEDULE_COUNT,mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);// Log at most once per minute.mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED, 1, 60_000);mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);mAppStandbyInternal.addListener(mStandbyTracker);// 创建进程内需要使用的通道publishLocalService(JobSchedulerInternal.class, new LocalService());// 创建JobStore 单例mJobs = JobStore.initAndGet(this);// 创建控制器mControllers = new ArrayList<StateController>();final ConnectivityController connectivityController = new ConnectivityController(this);mControllers.add(connectivityController);mControllers.add(new TimeController(this));final IdleController idleController = new IdleController(this);mControllers.add(idleController);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);mQuotaController = new QuotaController(this);mControllers.add(mQuotaController);// 指定限制性的控制器mRestrictiveControllers = new ArrayList<>();mRestrictiveControllers.add(mBatteryController);mRestrictiveControllers.add(connectivityController);mRestrictiveControllers.add(idleController);// Create restrictionsmJobRestrictions = new ArrayList<>();mJobRestrictions.add(new ThermalStatusRestriction(this));mSystemGalleryPackage = Objects.requireNonNull(context.getString(R.string.config_systemGallery));...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));}}

实现大致总结如下:

  • 创建JobHandler 实例,核心处理都在这里,下面会继续分析说明;
  •  创建JobSchedulerStub 实例;
  • 创建各种Controller 实例;
  • 如果RTC 好使,则注册广播监听ACTION_TIME_CHANGED;

3.1.1 StateController

这里是所有Controller 的基类,记录JobScheduler 的service 、stateChangedListener、context、lock、constants,如下:

    StateController(JobSchedulerService service) {mService = service;mStateChangedListener = service;mContext = service.getTestableContext();mLock = service.getLock();mConstants = service.getConstants();}

另外,还有两个重要函数:

    public abstract void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob);public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,boolean forUpdate);

用以从Controller 的 tracking list 中添加、移除Job。

3.1.2 StateChangedListener

public interface StateChangedListener {//控制器通知JobManager 确认下Job状态public void onControllerStateChanged();//控制器通知JobManager 立即执行Job,而无论Job什么状态public void onRunJobNow(JobStatus jobStatus);//device Idle 状态发生改变时通知JobManager 确认Job状态public void onDeviceIdleStateChanged(boolean deviceIdle);void onRestrictedBucketChanged(@NonNull List<JobStatus> jobs);
}

3.1.3 ConnectivityController

主要是通过mNetworkCallback 注册网络变化的监听,并调用updateTrackedJobs,该方法主要是遍历保存在追踪列表中的任务,查看是否有任务的两个参数值相对于之前保存的值有变化,如果有则调用mStateChangedListener监听器的onControllerStateChanged()方法通知JobSchedulerService约束任务的条件状态发生改变,这里的mStateChangedListener就是构造方法传递来的JobSchedulerService的实例。由于控制器实现了ConnectivityManage.OnNetworkActiveListener接口,故当网络可用时会调用该接口中的onNetworkActive方法,在该方法中会调用监听器的onRunJobNow方法通知JobSchedulerService执行任务。

    private void updateTrackedJobs(int filterUid, Network filterNetwork) {synchronized (mLock) {final ArrayMap<Network, NetworkCapabilities> networkToCapabilities = new ArrayMap<>();boolean changed = false;if (filterUid == -1) {for (int i = mTrackedJobs.size() - 1; i >= 0; i--) {changed |= updateTrackedJobsLocked(mTrackedJobs.valueAt(i),filterNetwork, networkToCapabilities);}} else {changed = updateTrackedJobsLocked(mTrackedJobs.get(filterUid),filterNetwork, networkToCapabilities);}if (changed) {mStateChangedListener.onControllerStateChanged();}}}public void onNetworkActive() {synchronized (mLock) {for (int i = mTrackedJobs.size()-1; i >= 0; i--) {final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);for (int j = jobs.size() - 1; j >= 0; j--) {final JobStatus js = jobs.valueAt(j);if (js.isReady()) {if (DEBUG) {Slog.d(TAG, "Running " + js + " due to network activity.");}mStateChangedListener.onRunJobNow(js);}}}}}

3.1.4 TimeController

由于该控制器跟任务执行时间相关,故调用maybeStartTrackingJob方法添加任务时会根据任务执行时间点插入到追踪列表中,并更新下一个要执行任务的执行时间点。

该控制器的大致实现流程:

  • 初始化控制器时,初始化任务的deadline到期和延迟到期时发送广播的操作,动态注册这两个广播,根据接收到不同的广播执行不同的检查机制;
  • 遍历检查任务的延迟时间是否已经到期,如果有任务的延迟时间到期并且所有的约束都得到满足时,调用mStateChangedListener监听器的onControllerStateChanged方法;
  • 或者检查任务追踪列表中是否有deadline过期导致该任务需要执行,如果有则调用mStateChangedListener监听器的onRunJobNow方法。

3.1.5 IdleController

主要是注册一个IdlenessListener 到DeviceIdlenessTracker 中,用以添加亮屏/息屏、进入休眠/退出休眠,以及进入空闲状态的广播。当mIdle 处于true 状态时,会调用reportNewIdleState 通知给IdleController,并进一步通知JobSchedulerService 进行onControllerStateChanged回调。

    // 用以从DeviceIdlenessTracker 中回调public void reportNewIdleState(boolean isIdle) {synchronized (mLock) {for (int i = mTrackedTasks.size()-1; i >= 0; i--) {mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(isIdle);}}mStateChangedListener.onControllerStateChanged();}//IdleController构造时调用,用以startTrackingprivate void initIdleStateTracking(Context ctx) {final boolean isCar = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);if (isCar) {mIdleTracker = new CarIdlenessTracker();} else {mIdleTracker = new DeviceIdlenessTracker();}mIdleTracker.startTracking(ctx, this);}

3.2 schedule

对于添加schedule 的接口有三个:

  • schedule 就是指定个JobInfo,要求安排进schedule;
  • enqueue 除了JobInfo,还指定了JobWorkItem;
  • scheduleAsPackage 除了JobInfo,还指定了packageName;

最终调用的都是scheduleAsPackage,这里不做过多的说明,以schedule() 这个接口为例。

通过构造得知IJobScheduler 的stub 是在JobSchedulerService中实现。来看下schedule 接口:

        public int schedule(JobInfo job) throws RemoteException {...enforceValidJobRequest(uid, job);...validateJobFlags(job, uid);long ident = Binder.clearCallingIdentity();try {return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,null);} finally {Binder.restoreCallingIdentity(ident);}}
  • 函数enforceValidJobRequest 要求JobService 必须有 android.permission.BIND_JOB_SERVICE 权限;
  • 函数validateJobFlags 要求JobInfo 在设置flag 也需要按照要求;

核心的是scheduleAsPackage

    public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,int userId, String tag) {final String servicePkg = job.getService().getPackageName();...synchronized (mLock) {final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());...JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);...// This may throw a SecurityException.jobStatus.prepareLocked();if (toCancel != null) {// Implicitly replaces the existing job record with the new instancecancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");} else {startTrackingJobLocked(jobStatus, null);}...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);addOrderedItem(mPendingJobs, jobStatus, sPendingJobComparator);maybeRunPendingJobsLocked();} else {evaluateControllerStatesLocked(jobStatus);}}return JobScheduler.RESULT_SUCCESS;}

这个函数是结合了schedule()、enqueue()、scheduleAsPackage(),内容比较多。

  • 函数最开始是讨论永久性的Job,这里主要还是针对第三方JobService,或者是动态安排的system 中的JobService。
  • enqueue 进来的work 如果已经创建了Job,则直接直接添加到pendingWork中;
  • 新建JobStatus,如果已经存在此Job,则会进行reschedule,详细看3.2.2节;如果没有添加过Job,则会通过startTrackingJobLocked 添加到各个Controller中,详细看3.2.1节;
  • 如果Job 需要立即执行,会将其添加到pending list 中,等待确认run;

3.2.1  startTrackingJobLocked

    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);}}}
  • 如果mJobs.add 时发现已经有此Job 则会先remove,变量update 则为true;如果没有发现此Job,则update 为false;
  • 变量 mReadyToRock 是在onBootPhase 中指定,标记系统启动完成;
  • 如果有此Job,会先让Controller stopTracking;如果没有此Job 则会让Controller startTracking;

3.2.2 cancelJobImplLocked

    private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason) {...cancelled.unprepareLocked();stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);// Remove from pending queue.if (mPendingJobs.remove(cancelled)) {mJobPackageTracker.noteNonpending(cancelled);}// Cancel if running.stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED, reason);// If this is a replacement, bring in the new version of the jobif (incomingJob != null) {if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());startTrackingJobLocked(incomingJob, cancelled);}reportActiveLocked();}
  • 通过stopTrackingJobLocked 让Controller stopTracking;
  • 如果正在运行则通过stopJobOnServiceContextLocked 退出Job;
  • 如果是Job 替换,stop 后需要调用startTrackingJobLocked;

3.3 运行Jobs

从3.1.3 节ConnectivityController 中得知,当Job可以运行时会调用callback onRunJobNow:

    public void onRunJobNow(JobStatus jobStatus) {mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();}

最终调用maybeRunPendingJobsLocked():

    void maybeRunPendingJobsLocked() {if (DEBUG) {Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");}mConcurrencyManager.assignJobsToContextsLocked();reportActiveLocked();}

这里不详细列出代码,最终调用到的是JobConcurrentcyManager.assignJobsToContextsInternalLocked:

    private void assignJobsToContextsInternalLocked() {...if (!activeServices.get(i).executeRunnableJob(pendingJob)) {Slog.d(TAG, "Error executing " + pendingJob);}

至此,JobScheduler的调用流程其实大概显现出来了,代码的逻辑比较多,由于时间原因,这里暂时不做过多的列举,下面根据实际情况列举JobScheduler 中使用注意点。

4. timeout处理

核心处理函数为scheduleOpTimeOutLocked:

framworks/base/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java

    private void scheduleOpTimeOutLocked() {removeOpTimeOutLocked();final long timeoutMillis;switch (mVerb) {case VERB_EXECUTING:timeoutMillis = EXECUTING_TIMESLICE_MILLIS;break;case VERB_BINDING:timeoutMillis = OP_BIND_TIMEOUT_MILLIS;break;default:timeoutMillis = OP_TIMEOUT_MILLIS;break;}if (DEBUG) {Slog.d(TAG, "Scheduling time out for '" +mRunningJob.getServiceComponent().getShortClassName() + "' jId: " +mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s");}Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback);mCallbackHandler.sendMessageDelayed(m, timeoutMillis);mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;}

根据不同的状态,指定不同的timeout,如果是正常流程,则要求timeout 时间内要尽快取消这个message。

  • 处于startJob 状态下,timeout 为10mins;
  • 处于bind 状态下,timeout 为18s;
  • 其他状态,例如stop、cancel、finish等,timeout 为8s;

如果触发了timeout 后,会调用handleOpTimeoutLocked 进行处理:

    private void handleOpTimeoutLocked() {switch (mVerb) {case VERB_BINDING:...closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding");break;case VERB_STARTING:...closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting");break;case VERB_STOPPING:...closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");break;case VERB_EXECUTING:...mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");sendStopMessageLocked("timeout while executing");break;default:...closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout");}}

状态分别对应情况如下:

  • VERB_BINDING:        JobServiceContext 正处于bind job状态;
  • VERB_STARTING:     JobServiceContext 正处于调用startJob 状态,此时startJob 还没有返回;
  • VERB_STOPPING:    JobServiceContext 正处于stopJob状态;
  • VERB_EXECUTING: JobServiceContext 正处于startJob 返回后callback触发状态,但是App 还没有调用finish;

从上面代码得到timeout 要求

  • bind Job 必须要在18s 内完成并返回,即要求App 端onBind 需要尽快返回;
  • startJob 必须要在8s 内回调callback,即要求App 端onStartJob 需要在8s 内处理完,这也是最开始说的,需要onStartJob 中的逻辑处理放到thread/handler/AsyncTask 中处理;
  • 即使onStartJob在8s 内处理完,并callback 回调给JobServiceContext,但也要求App 端在10min是内处理完Job,并调用jobFinish;
  • stopJob 必须要在8s 内完成,同startJob,需要尽快callback回来;

5. 返回值选择

上面的分析中提到onStartJob 和onStopJob 不同的返回值,JobScheduler 会做出不同的反应。

5.1 onStartJob 返回值

从2.3.1 节中得知onStartJob 是通过接口acknowledgeStartMessage 返回:

framworks/base/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java

        public void acknowledgeStartMessage(int jobId, boolean ongoing) {doAcknowledgeStartMessage(this, jobId, ongoing);}

最终调用doCallbackLocked:

    void doCallbackLocked(boolean reschedule, String reason) {if (DEBUG) {Slog.d(TAG, "doCallback of : " + mRunningJob+ " v:" + VERB_STRINGS[mVerb]);}removeOpTimeOutLocked();if (mVerb == VERB_STARTING) {handleStartedLocked(reschedule);} else if (mVerb == VERB_EXECUTING ||mVerb == VERB_STOPPING) {handleFinishedLocked(reschedule, reason);} else {if (DEBUG) {Slog.d(TAG, "Unrecognised callback: " + mRunningJob);}}}

对于onStartJob,只会是通过handleStartedLocked 处理:

    private void handleStartedLocked(boolean workOngoing) {switch (mVerb) {case VERB_STARTING:mVerb = VERB_EXECUTING;if (!workOngoing) {// Job is finished already so fast-forward to handleFinished.handleFinishedLocked(false, "onStartJob returned false");return;}if (mCancelled) {if (DEBUG) {Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete.");}// Cancelled *while* waiting for acknowledgeStartMessage from client.handleCancelLocked(null);return;}scheduleOpTimeOutLocked();break;default:Slog.e(TAG, "Handling started job but job wasn't starting! Was "+ VERB_STRINGS[mVerb] + ".");return;}}
  • 如果onStartJob 返回值为false,进入handleFinishedLocked 结束job;
  • 如果是因为调用了cancel 接口,进入cancel 流程,这里讨论的是返回值,所以忽略这一条;
  • 如果onStartJob 返回值为true,继续等待Job 处理完成,不过要注意10mins 的timeout;

5.2 onStopJob 的返回值

处理流程基本同onStartJob,这里直接跳到handleFinishedLocked:

    private void handleFinishedLocked(boolean reschedule, String reason) {switch (mVerb) {case VERB_EXECUTING:case VERB_STOPPING:closeAndCleanupJobLocked(reschedule, reason);break;default:Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +"executed. Was " + VERB_STRINGS[mVerb] + ".");}}

可以看到onStopJob 的返回值决定是否进入reschedule:

  • 如果onStopJob 返回值为false,则不会进入reschedule 流程;
  • 如果onStopJob 返回值为true,则进入reschedule 流程;

其他关于JobScheduler:

https://developer.android.com/topic/performance/background-optimization

https://developer.android.com/about/versions/oreo/background

http://www.voidcn.com/article/p-efqwioos-od.html

这篇关于Android JobService和JobScheduler 原理剖析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/677764

相关文章

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

hdu4407容斥原理

题意: 有一个元素为 1~n 的数列{An},有2种操作(1000次): 1、求某段区间 [a,b] 中与 p 互质的数的和。 2、将数列中某个位置元素的值改变。 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.Inpu

hdu4059容斥原理

求1-n中与n互质的数的4次方之和 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.PrintWrit

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

android应用中res目录说明

Android应用的res目录是一个特殊的项目,该项目里存放了Android应用所用的全部资源,包括图片、字符串、颜色、尺寸、样式等,类似于web开发中的public目录,js、css、image、style。。。。 Android按照约定,将不同的资源放在不同的文件夹中,这样可以方便的让AAPT(即Android Asset Packaging Tool , 在SDK的build-tools目