Android进阶——更节电的后台任务JobScheduler 机制使用详解

文章大纲

  • 引言
  • 一、JobScheduler 机制概述
  • 二、JobSchedulerService 服务
    • 1、触发JobSchedulerService 的启动
    • 2、JobSchedulerService 对象的构造
      • 2.1、使用system_server进程的主线程Looper初始化了JobHandler
      • 2.2、创建了JobSchedulerService 的对应Binder服务端
      • 2.3、创建了持久化相关的JobStore
      • 2.4、创建和注册预置条件的监听器StateController
    • 3、JobSchedulerService的真正启动
  • 三、JobService
    • 1、boolean OnStartJob(JobParameters params)
    • 2、boolean OnStopJob(JobParameters params)
    • 3、void jobFinished (JobParameters params, boolean needsReschedule)
  • 四、JobScheduler
    • 1、JobSchedulerService.JobSchedulerStub#schedule
  • 五、JobInfo & JobInfo.Builder
  • 六、JobScheduler的使用步骤
    • 1、继承JobService 重写onStartJob和onStopJob方法实现定制的JobService
      • 1.1、结合Handler形式处理耗时操作
      • 1.2、结合AsyncTask 处理耗时操作
    • 2、初始化获取JobScheduler对象实例
    • 3、构建作业JobInfo对象预置触发条件绑定JobService
    • 4、通过JobScheduler对象实例调度指定作业

引言

Android 5.0系统以后,Google为了提高使用流畅度以及延长电池续航,引入了在应用后台/锁屏时,系统会回收应用并自动销毁应用拉起的Service的机制。同时为了满足在特定条件下(比如网络、充电状态、电量、时间、周期等)触发执行某些任务的需求,于是乎JobScheduler 机制应运而生。总之,对于一定预定条件而触发的任务,JobScheduler是绝佳选择。

一、JobScheduler 机制概述

JobScheduler 机制中把每个需要后台的业务抽象为一个Job,通过系统管理Job,来提高资源的利用率和减少不必要的唤醒,从而提高性能,节省电源。当系统启动时会通过system_server进程启动**JobSchedulerService**服务,然后当使用该机制时,首先通过JobInfo构造具体的后台任务对象,并通过Jobscheduler 传入到后台任务调度器,当满足配置的条件时系统便会在对应的JobService上执行对应的作业。简而言之,系统提供了一种条件周期性执行的后台任务,无需开发者自己去唤醒,达到配置的条件便会自动执行。

二、JobSchedulerService 服务

The JobSchedulerService knows nothing about constraints, or the state of active jobs. It receives callbacks from the various controllers and completed jobs and operates accordingly.

从通过 (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)的方式获取JobScheduler实例可以得知JobSchedulerService 也是以系统服务形式运行在后台,JobSchedulerService对Job的状态和约束都不了解,完全是通过各种controller的回调去处理各种Job。

1、触发JobSchedulerService 的启动

在com.android.server.SystemServer#startOtherServices方法里

 mSystemServiceManager.startService(JobSchedulerService.class);

SystemServiceManager启动所有系统核心服务的方式都大同小异,基本上都是首先根据传入的class字节码类型newInstance反射构造相关的对象,注册到系统服务列表后再触发其相应的onStart方法启动对应的服务。

com.android.server.SystemServiceManager#startService(java.lang.Class)

public <T extends SystemService> T startService(Class<T> serviceClass) {try {final String name = serviceClass.getName(); final T service;Constructor<T> constructor = serviceClass.getConstructor(Context.class);service = constructor.newInstance(mContext);//传入SystemServiceManager的mContext 反射构造JobSchedulerService 对象...// {@ link ArrayList<SystemService> mServices}Register it.mServices.add(service);// Start it. 启动JobSchedulerService service.onStart();return service;}}

2、JobSchedulerService 对象的构造

 public final class JobSchedulerService extends com.android.server.SystemServiceimplements StateChangedListener, JobCompletedListener{...public JobSchedulerService(Context context) {super(context);mHandler = new JobHandler(context.getMainLooper());//使用system_server进程中主线程的Looper初始化JobHandlermConstants = new Constants(mHandler);mJobSchedulerStub = new JobSchedulerStub();//创建对应Binder服务端mJobs = JobStore.initAndGet(this);// Create the controllers.mControllers = new ArrayList<StateController>();mControllers.add(ConnectivityController.get(this));//注册监听网络连接状态的广播mControllers.add(TimeController.get(this));//注册监听Job时间到期的广播mControllers.add(IdleController.get(this));//注册监听屏幕亮/灭,dream进入/退出,状态改变的广播mBatteryController = BatteryController.get(this);//注册监听电池是否充电,电量状态的广播mControllers.add(mBatteryController);mStorageController = StorageController.get(this);mControllers.add(mStorageController);mControllers.add(AppIdleController.get(this));//监听app是否空闲mControllers.add(ContentObserverController.get(this));//监听ContentObserver事件广播mControllers.add(DeviceIdleJobsController.get(this));//监听设备空闲广播}@Overridepublic void onControllerStateChanged() {mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();}@Overridepublic void onRunJobNow(JobStatus jobStatus) {mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();}@Overridepublic void onStart() {publishLocalService(JobSchedulerInternal.class, new LocalService());publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);}

JobSchedulerService 继承自SystemService类并实现了StateChangedListener、JobCompletedListener接口,构造方法执行时主要完成四件事。

2.1、使用system_server进程的主线程Looper初始化了JobHandler

该过程运行在主线程,因此不能做耗时操作。

//com.android.server.job.JobSchedulerService.JobHandler 
final private class JobHandler extends Handler {public JobHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message message) {synchronized (mLock) {if (!mReadyToRock) {//phase == PHASE_THIRD_PARTY_APPS_CAN_START 时mReadyToRock为true运行运行第三方Appreturn;}switch (message.what) {case MSG_JOB_EXPIRED: {...} break;case MSG_CHECK_JOB:if (mReportedActive) {// if jobs are currently being run, queue all ready jobs for execution.queueReadyJobsForExecutionLocked();} else {// Check the list of jobs and run some of them if we feel inclined.maybeQueueReadyJobsForExecutionLocked();}break;case MSG_CHECK_JOB_GREEDY:queueReadyJobsForExecutionLocked();break;case MSG_STOP_JOB:cancelJobImplLocked((JobStatus) message.obj, null,"app no longer allowed to run");break;}maybeRunPendingJobsLocked();// Don't remove JOB_EXPIRED in case one came along while processing the queue.removeMessages(MSG_CHECK_JOB);}}}

2.2、创建了JobSchedulerService 的对应Binder服务端

    /*** Binder stub trampoline implementation*/final class JobSchedulerStub extends IJobScheduler.Stub {private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();// IJobScheduler implementation@Overridepublic int schedule(JobInfo job) throws RemoteException {。。。try {return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, -1, null);} finally {Binder.restoreCallingIdentity(ident);}}// IJobScheduler implementation@Overridepublic int enqueue(JobInfo job, JobWorkItem work) throws RemoteException {...long ident = Binder.clearCallingIdentity();try {return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, -1, null);} finally {Binder.restoreCallingIdentity(ident);}}@Overridepublic int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)throws RemoteException {...try {return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid,packageName, userId, tag);} finally {Binder.restoreCallingIdentity(ident);}}@Overridepublic List<JobInfo> getAllPendingJobs() throws RemoteException {try {return JobSchedulerService.this.getPendingJobs(uid);} finally {Binder.restoreCallingIdentity(ident);}}@Overridepublic JobInfo getPendingJob(int jobId) throws RemoteException {final int uid = Binder.getCallingUid();long ident = Binder.clearCallingIdentity();try {return JobSchedulerService.this.getPendingJob(uid, jobId);} finally {Binder.restoreCallingIdentity(ident);}}@Overridepublic void cancelAll() throws RemoteException {...try {JobSchedulerService.this.cancelJobsForUid(uid, "cancelAll() called by app");} finally {Binder.restoreCallingIdentity(ident);}}@Overridepublic void cancel(int jobId) throws RemoteException {...try {JobSchedulerService.this.cancelJob(uid, jobId);} finally {Binder.restoreCallingIdentity(ident);}}};

2.3、创建了持久化相关的JobStore

JobStore对象构造时会在创建/data/system/job/jobs.xml文件,同时可能之前已经存储过,还会解析XML文件创建JobInfo和,并转化为对应的JobStatus,最后把所有的JobStatus并保存到JobSet集合中,也是为什么JobScheduler可以持久化的原因。

JobStatus对象记录着任务的jobId, ComponentName, uid以及标签和失败次数信息。

  private JobStore(Context context, Object lock, File dataDir) {File systemDir = new File(dataDir, "system");File jobDir = new File(systemDir, "job");jobDir.mkdirs();mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));mJobSet = new JobSet();readJobMapFromDisk(mJobSet);}// frameworks/base/services/core/java/com/android/server/job/JobStore.java
/** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
static JobStore initAndGet(JobSchedulerService jobManagerService) {synchronized (sSingletonLock) {if (sSingleton == null) {sSingleton = new JobStore(jobManagerService.getContext(),jobManagerService.getLock(), 									Environment.getDataDirectory());}return sSingleton;}
}

2.4、创建和注册预置条件的监听器StateController

创建和注册预置条件的监听器,StateControler内部依然是通过广播实现的,监听到相应广播然后通知到监听者,当满足条件后,就会通过Handler 发送相应的消息触发任务执行。

StateControler类型说明
ConnectivityController注册监听网络连接状态的广播
TimeController注册监听job时间到期的广播
IdleController注册监听屏幕亮/灭,dream进入/退出,状态改变的广播
BatteryController注册监听电池是否充电,电量状态的广播
AppIdleController监听app是否空闲
ContentObserverController通过ContentObserver监测content URIs的变化
DeviceIdleJobsController根据doze状态为app设置约束。
public class ConnectivityController extends StateController implementsConnectivityManager.OnNetworkActiveListener {private final ConnectivityManager mConnManager;/** Singleton. */private static ConnectivityController mSingleton;private ConnectivityController(StateChangedListener stateChangedListener, Context context,Object lock) {super(stateChangedListener, context, lock);mConnManager = mContext.getSystemService(ConnectivityManager.class);mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);final IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);mContext.registerReceiverAsUser(mConnectivityReceiver, UserHandle.SYSTEM, intentFilter, null, null);mNetPolicyManager.registerListener(mNetPolicyListener);//主动监听网络相关广播}.../*** Update all jobs tracked by this controller.* @param uid only update jobs belonging to this UID, or {@code -1} to update all tracked jobs.*/private void updateTrackedJobs(int uid) {synchronized (mLock) {boolean changed = false;for (int i = 0; i < mTrackedJobs.size(); i++) {final JobStatus js = mTrackedJobs.get(i);if (uid == -1 || uid == js.getSourceUid()) {changed |= updateConstraintsSatisfied(js);}}if (changed) {mStateChangedListener.onControllerStateChanged();}}}/*** We know the network has just come up. We want to run any jobs that are ready.*/@Overridepublic synchronized void onNetworkActive() {synchronized (mLock) {for (int i = 0; i < mTrackedJobs.size(); i++) {final JobStatus js = mTrackedJobs.get(i);if (js.isReady()) {mStateChangedListener.onRunJobNow(js);}}}}private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {updateTrackedJobs(-1);}};
}

Android O以后禁止了一些广播的发送后,都是由这些Controller进行动态注册广播,由这些Controller触发相应的预置回调接口,从而转交给JobScheduler进行处理

/*** 是否正在充电*/
public static boolean isCharging(Context context){//注册个包含充电状态的广播,并且是一个持续的广播IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);Intent intent = context.registerReceiver(null,filter);//获取充电状态int isPlugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);boolean acPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_AC;boolean usbPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_USB;boolean wifiPlugged = isPlugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;return acPlugged || usbPlugged || wifiPlugged;
}

3、JobSchedulerService的真正启动

与其他系统服务一样,执行发布,这样其他应用就可以直接通过Binder使用这个服务的能力了。

    @Overridepublic void onStart() {publishLocalService(JobSchedulerInternal.class, new LocalService());publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);}

最后由SystemServiceRegistry的静态代码块中完成注册工作,可以看到当客户端请求获取JOB_SCHEDULER_SERVICE服务, 返回的是继承自JobScheduler 的JobSchedulerImpl实例。

        registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class,new StaticServiceFetcher<JobScheduler>() {@Overridepublic JobScheduler createService() {IBinder b = ServiceManager.getService(Context.JOB_SCHEDULER_SERVICE);return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));}});

至此JobSchedulerService 服务启动完成。

三、JobService

Entry point for the callback from the {@link android.app.job.JobScheduler}

抽象类JobService 继承自Service类,在JobScheduler监测到系统状态达到对应启动条件时,会启动JobService执行任务。所以我们需要继承JobService创建一个继承自JobService的Service,并必须实现两个方法:onStartJob(JobParameters params)onStopJob(JobParameters params)

public abstract class JobService extends Service {final JobHandler mHandler;final JobSchedulerStub mJobSchedulerStub;IJobService mBinder = new IJobService.Stub() {public void startJob(JobParameters jobParams) {ensureHandler();//向主线程的Handler发送MSG_EXECUTE_JOB消息Message m = Message.obtain(mHandler, MSG_EXECUTE_JOB, jobParams);m.sendToTarget();}public void stopJob(JobParameters jobParams) {ensureHandler();//向主线程的Handler发送MSG_STOP_JOB消息Message m = Message.obtain(mHandler, MSG_STOP_JOB, jobParams);m.sendToTarget();}};void ensureHandler() {synchronized (mHandlerLock) {if (mHandler == null) {mHandler = new JobHandler(getMainLooper());}}}public final IBinder onBind(Intent intent) {return mBinder.asBinder();}...
}

当JobService运行在App进程时,则mHandler即App进程的主线程关联的Handler。当分别向主线程发送消息启动和停止任务时,就会分别回调onStartJob和onStopJob方法。

1、boolean OnStartJob(JobParameters params)

当作业开始执行时会触发onStartJob(JobParameters params)方法(系统用来触发已经被执行的任务)并返回一个boolean值。若值为false,系统会认为在它返回时,任务已经执行完毕;而返回true,则系统任务这个任务正要被执行,因此当任务执行完毕时你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来通知系统。

  • 如果返回值是false,该系统假定任何任务运行不需要很长时间并且到方法返回时已经完成。
  • **如果返回值是true,那么系统假设该任务是需要一些时间并且是需要在我们自己应用执行的。**当给定的任务完成时,需要通过调用jobFinished(JobParameters params, boolean needsRescheduled)方法来停止该任务告知系统该任务已经处理完成。

换言之,onStartJob方法在系统判定达到约束条件时被调用,我们可以在此处执行我们的业务逻辑

2、boolean OnStopJob(JobParameters params)

当系统接收到一个取消请求时,系统会触发onStopJob(JobParameters params)方法取消正在等待执行的任务也同样返回一个boolean值很重要的一点是如果onStartJob(JobParameters params)返回false,那么系统假定在接收到一个取消请求时已经没有正在运行的任务。换句话说,onStopJob(JobParameters params)在这种情况下不会被调用。当收到取消请求时,onStopJob(JobParameters params)是系统用来取消挂起的任务的。如果onStartJob(JobParameters params)返回 false,当取消请求被接收时,该系统假定没有目前运行的工作,它根本就不调用onStopJob(JobParameters params)。因此就需要我们手动调用jobFinished (JobParameters params, boolean needsReschedule)方法了。

要注意的是,onStartJob和onStopJob方法是运行在主线程中的,我们不可以在其中做耗时操作,否则可能导致ANR,可以使用另一个线程处理程序或运行时间更长的任务异步任务处理。因此通常在上面自定义的JobService类中创建一个Handler或者AsyncTask来处理需要进行的Job。

3、void jobFinished (JobParameters params, boolean needsReschedule)

回调通知已完成执行的JobManager,这可以从任何线程调用,因为它最终将在应用程序的主线程上运行。当系统收到该消息时,它将释放正在保存的唤醒。

  • params——传入的param需要和onStartJob中的param一致
  • needsReschedule——如果这项工作应按照计划时间指定的停止条件进行重新安排,则传入true。 否则的话传入false,让系统知道这个任务是否应该在最初的条件下被重复执行**

当任务完成时,需要调用jobFinished(JobParameters params, boolean needsRescheduled)让系统知道完成了哪项任务,它可以开始排队接下来的操作。如果不这样做,工作将只运行一次,应用程序将不被允许执行额外的工作

四、JobScheduler

This is an API for scheduling various types of jobs against the framework that will be executed in your application’s own process.

JobScheduler 从代码角度上来看是给我们开发者提供了一系列管理调度JobInfo的API,同时从另一个角度它还是一个Android的系统服务——JobSchedulerService相关的Binder对象。从上面得知JobScheduler 的实现类是JobSchedulerImpl,主要就是通过Binder 调用com.android.server.job.JobSchedulerService.JobSchedulerStub里的Binder远程接口方法。

方法参数说明
int schedule(JobInfo job)对应的任务信息把要执行的任务添加到调度集合中,如果传入的参数里携带的jobId已经存在则会覆盖旧ID的作业,而如果传入的jobId的作业当前正在运行,则会将其停止。如果schedule方法失败了,它会返回一个小于0的错误码。否则它会返回我们在JobInfo.Builder中定义的标识id。
void cancel(int jobId)jobId取消jobId对应的作业,如果任务当前正在执行,它会立即停止且它的Job Service#onStopjob (Job Parameters)方法的返回值将被忽略。
void cancelAll()\取消所有当前应用配置的Job,cancalAll()小心有坑,因为该方法的功能是取消该uid下的所有jobs,即当存在多个app通过shareUid的方式,那么在其中任意一个app执行cancalAll(),则会把所有同一uid下的app中的jobs都cancel掉
List< JobInfo > getAllPendingJobs()\获取所有当前应用配置的Job
JobInfo getPendingJob(int jobId)jobId获取jobId对应的Job
public class JobSchedulerImpl extends JobScheduler {IJobScheduler mBinder;/* package */ JobSchedulerImpl(IJobScheduler binder) {mBinder = binder;}@Overridepublic int schedule(JobInfo job) {try {//{@link com.android.server.job.JobSchedulerService.JobSchedulerStub#schedule}return mBinder.schedule(job);} catch (RemoteException e) {return JobScheduler.RESULT_FAILURE;}}@Overridepublic int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag) {return mBinder.scheduleAsPackage(job, packageName, userId, tag);}@Overridepublic void cancel(int jobId) {mBinder.cancel(jobId);}@Overridepublic void cancelAll() {mBinder.cancelAll();}@Overridepublic List<JobInfo> getAllPendingJobs() {return mBinder.getAllPendingJobs();}@Overridepublic JobInfo getPendingJob(int jobId) {return mBinder.getPendingJob(jobId);}
}

1、JobSchedulerService.JobSchedulerStub#schedule

JobSchedulerImpl里通过mBinder调用schedule方法,然后传递到JobSchedulerService 服务端Binder对象JobSchedulerStub中

// IJobScheduler implementation
@Override
public int schedule(JobInfo job) throws RemoteException {return JobSchedulerService.this.schedule(job, uid);
}

在JobSchedulerStub里调用JobSchedulerService.this.schedule方法,

//@link com.android.server.job.JobSchedulerService.JobSchedulerStub#schedulepublic int schedule(JobInfo job, int uId) {return scheduleAsPackage(job, uId, null, -1, null);}public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,String tag) {//创建JobStatusJobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);JobStatus toCancel;synchronized (mLock) {//先取消该uid下的任务toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());if (toCancel != null) {cancelJobImpl(toCancel, jobStatus);}//开始追踪该任务startTrackingJob(jobStatus, toCancel);}//向system_server进程的主线程发送messagemHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();return JobScheduler.RESULT_SUCCESS;}

然后通过JobHandler处理Message消息,其他过程大同小异。

五、JobInfo & JobInfo.Builder

JobInfo是对任务主体信息的封装,比如说任务的执行条件、绑定的JobService类名、策略、重试策略、任务执行时间、是否持久化等等。通过构造者模式 JobInfo.Builder的构造JobInfo时需要传入一个jobId和绑定的JobService类名,其中jobId是Job的唯一标志,后续通过该jobId来取消Job

Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the parameters required to schedule work against the calling application.

JobInfo.Builder成员方法参数说明备注
addTriggerContentUri(JobInfo.TriggerContentUri uri)添加一个TriggerContentUri(原理是利用ContentObserver来监控一个Content Uri),当且仅当其发生变化时将触发任务的执行。为了持续监控Content的变化,需要在最近的任务触发后再调度一个新的任务(触发器URI不能与setPeriodic(long)setPersisted(boolean)组合使用。要持续监控内容更改,需要在完成JobService处理最近的更改之前,调度新的JobInfo监控相同的URI。因为设置此属性与定期或持久化Job不兼容,这样做会在调用build()时抛出IllegalArgumentException异常。)
setBackoffCriteria(long mills, int policy)mills表示第一次尝试重试的时间间隔,policy表示重试策略设置回退/重试的策略,类似网络原理中的冲突退避,当一个任务的调度失败时需要重试,所采取的策略预设的时间间隔有:DEFAULT_INITIAL_BACKOFF_MILLIS 30000MAX_BACKOFF_DELAY_MILLIS 18000000
而预设的策略有:BACKOFF_POLICY_EXPONENTIAL 二进制退避,等待间隔呈指数增长和BACKOFF_POLICY_LINEAR
setExtras(PersistableBundle extras)Job中附带的数据设置附带的额外数据,类似于Bundle的作用,是持久的,所以只允许原始类型。可以通过pendingJob.getExtras()获取
setMinimumLatency(long minLatencyMillis)设置任务的延迟执行时间(毫秒),相当于post delay。
setOverrideDeadline(long maxDeadlineMillis)设置任务最晚的延迟时间 。如果到了规定的时间时其他条件还未满足,任务也会被启动。
setPeriodic(long ms)设置任务运行的周期,即每X毫秒,运行一次
setPeriodic(long intervalMillis, long flexMillis)设置在Job周期末的一个flex长度的窗口,任务都有可能被执行。Andoid API 24及以上
setPersisted(boolean isPersisted)设置是否支持持久化,当设备重启之后系统根据值决定相应任务是否还要继续执行。
setRequiredNetworkType(int networkType)配置任务执行的网络条件NETWORK_TYPE_NONE——默认选择,不管是否有网络这个任务都会被执行
NETWORK_TYPE_ANY——需要任意一种网络才使得任务可以执行。
NETWORK_TYPE_UNMETERED——不是蜂窝网络( 比如在WIFI连接时 )时任务才会被执行
setRequiresCharging(boolean requiresCharging)设置设备在充电时这个任务才会被执行,这个也并非只是插入充电器,而且还要在电池处于健康状态的情况下才会触发,一般来说是手机电量>15%
setRequiresDeviceIdle(boolean isDeviceIdle)指定Job在空闲状态才能运行,设备处于屏幕关闭或dreaming状态(类似window的休眠动画状态)71分钟后,执行工作。
setTransientExtras(Bundle extras)设置作业可以携带的额外数据,类似Intent携带Bundle作用。Android API 26 及以上
setTriggerContentMaxDelay(long durationMs)设置从第一次检测到内容更改到Job之前允许的最大总延迟(以毫秒为单位),设置从Content变化到任务被执行,中间的最大延迟。Android API 24及以上
setTriggerContentMaxDelay(long durationMs)设置从第一次检测到内容更改到Job之前允许的最大总延迟(以毫秒为单位),设置从Content变化到任务被执行,中间的最大延迟。Android API 24及以上
setExtras(PersistableBundle extra)设置作业可以携带的额外数据,类似Intent携带Bundle作用。

六、JobScheduler的使用步骤

1、继承JobService 重写onStartJob和onStopJob方法实现定制的JobService

作业被启动后会调用onStartJob方法,而因为JobService运行在主线程,所以要使用子线程、Handler或者一个异步任务来运行耗时的操作以防止阻塞主线程。如果要执行耗时的操作,就需要创建一个线程去做;如果onStartJob执行的是不耗时的任务,就可以返回false,表示任务执行结束。如果onStartJob起了一个线程执行耗时任务,就要返回true,表示任务还在执行,需要等任务真正结束后手动调用JobFinished()方法告诉系统任务已经结束。

1.1、结合Handler形式处理耗时操作

public class JobServiceWithHandler  extends JobService {private Messenger mActivityMessenger;@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {mActivityMessenger = intent.getParcelableExtra(MESSENGER_INTENT_KEY);return START_NOT_STICKY;}private final Handler mJobHandler = new Handler( new Handler.Callback() {@Overridepublic boolean handleMessage( Message msg ) {//TODO 业务操作//...jobFinished( (JobParameters) msg.obj, false );//任务执行完毕后通知return true;}} );@Overridepublic boolean onStartJob(JobParameters params) {mJobHandler.sendMessage( Message.obtain( mJobHandler, 1, params ) );/* GOOGLE 官网demo 模拟的是通过Messenger 跨进程通信sendMessage(MSG_COLOR_START, params.getJobId());long duration = params.getExtras().getLong(WORK_DURATION_KEY);// Uses a handler to delay the execution of jobFinished().Handler handler = new Handler();handler.postDelayed(new Runnable() {@Overridepublic void run() {sendMessage(MSG_COLOR_STOP, params.getJobId());jobFinished(params, false);}}, duration);*//*      new Handler().postDelayed(new Runnable() {@Overridepublic void run() {//Do 。。。。jobFinished(params, false);}}, duration);*///onStartJob返回true的时候,意味着耗时操作花费的事件比onStartJob执行的事件更长,并且意味着必须在合适时机手动调用jobFinished方法,否则该应用中的其他Job将不会被执行return true;}@Overridepublic boolean onStopJob(JobParameters params) {mJobHandler.removeMessages( 1 );//sendMessage(MSG_COLOR_STOP, params.getJobId());// 当系统收到一个cancel job的请求时,并且这个Job仍然在执行(onStartJob返回true),系统就会调用onStopJob方法。 但不管是否调用onStopJob,系统只要收到取消请求,都会取消该Job// true 需要重试  false 不再重试 丢弃jobreturn false;}private void sendMessage(int messageID, @Nullable Object params) {if (mActivityMessenger == null) {return;}Message m = Message.obtain();m.what = messageID;m.obj = params;mActivityMessenger.send(m);} 
}

需要到AndroidManifest.xml中添加一个service节点让你的应用拥有绑定和使用这个JobService的权限。

<service android:name=".JobServiceWithHandler"android:permission="android.permission.BIND_JOB_SERVICE" />

1.2、结合AsyncTask 处理耗时操作

public class JobServiceWithAsyncTask extends JobService {private JobParameters mJobParameters;private final AsyncTask<Void, Void, Void> mTask = new AsyncTask<Void, Void, Void>() {@Overrideprotected Void doInBackground(Void... params) {// TODO 耗时操作return null;}@Overrideprotected void onPostExecute(Void result) {// TODO 耗时操作执行完毕后,告知系统jobFinished(mJobParameters, true);super.onPostExecute(result);}};@Overridepublic boolean onStartJob(JobParameters params) {// 返回true,表示该工作耗时,同时工作处理完成后需要调用jobFinished销毁mJobParameters = params;mTask.execute();return true;}@Overridepublic boolean onStopJob(JobParameters params) {return false;}
}

同样需要在清单文件中注册

<service android:name=".JobServiceWithAsyncTask"android:permission="android.permission.BIND_JOB_SERVICE" />

2、初始化获取JobScheduler对象实例

JobScheduler mJobScheduler = (JobScheduler) getSystemService( Context.JOB_SCHEDULER_SERVICE );

3、构建作业JobInfo对象预置触发条件绑定JobService

使用JobInfo.Builder来构建一个JobInfo对象并绑定定制的JobService。

// jobId :0
PersistableBundle extras = new PersistableBundle();
extras.putString("DATA","xxxx");
//创建一个job
JobInfo jobInfo = newJobInfo.Builder(0,new ComponentName(context, JobServiceWithHandler.class))//bInfo.Builder(0,new ComponentName(context, JobServiceWithAsyncTask.class))//只在充电的时候.setRequiresCharging(true)//不是蜂窝网络.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED).setExtras(extras).build();

4、通过JobScheduler对象实例调度指定作业

  //提交任务
if( mJobScheduler.schedule( jobInfo) <= 0 ) {//If something goes wrong
}

提交了之后,就静待条件满足系统自动执行了,完。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/189669.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue中使用echarts实现省市地图绘制,根据数据在地图上显示柱状图信息,增加涟漪特效动画效果

一、实现效果 使用echarts实现省市地图绘制根据数据在地图显示柱状图根据数据显示数据&#xff0c;涟漪效果 二、实现方法 1、安装echarts插件 npm install echarts --save2、获取省市json数据 https://datav.aliyun.com/portal/school/atlas/area_selector 通过 阿里旗下…

openbabel 安装 生成指纹方法

今日踩坑小结&#xff1a; openbabel 安装&#xff1a; 可以装&#xff0c;但是得在 Linux 环境下&#xff0c;win 环境装会报错&#xff08;安装不会报错&#xff0c;但是生成指纹的时候会&#xff09; 指纹&#xff1a; 在下面这个链接里&#xff0c;官方给出了命令行调用 o…

MinGW是什么,怎么安装

MinGW&#xff08;Minimalist GNU for Windows&#xff09;是一个用于Windows平台的GCC&#xff08;GNU Compiler Collection&#xff09;编译器的移植版本。它可以让开发者在Windows环境下使用GCC来编译和链接C、C等语言的程序。 MinGW提供了一套开源的编译工具&#xff0c;包…

Doris 数据导入一:Broker Load 方式

1.Doris导入数据的方式总结 导入(Load)功能就是将用户的原始数据导入到 Doris 中。导入成功后,用户即可通过 Mysql 客户端查询数据。为适配不同的数据导入需求,Doris 系统提供了6种不同的导入方式。每种导入方式支持不同的数据源,存在不同的使用方式(异步,同步)。 所有…

如何在 Ubuntu 22.04中安装 Docker Compose

1 安装 pip # 下载get-pip.py脚本 wget https://bootstrap.pypa.io/pip/3.10/get-pip.py 或者 # 下载最新版本 curl https://bootstrap.pypa.io/get-pip.py --output get-pip.py# 为 Python 3 安装 pip sudo python3 get-pip.py2 安装 Pip 后&#xff0c;运行以下命令安装 Doc…

数据结构基础(带头节点的双向循环链表)

完整代码 DLinkList.hDLinkList.ctest.c DLinkList.h #pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h>typedef int ElemType;// SList - 单链表 // DList - 双链表 // 带头节点的双向循环链表 - 最优链表结构&#xff0c;任意位置…

Spinnaker 基于 jenkins 触发部署

jenkins job 触发部署 将 Jenkins 设置为 Spinnaker 中的持续集成 (CI) 系统可让您使用 Jenkins 触发管道、向管道添加 Jenkins 阶段或向管道添加脚本阶段。 前置要求&#xff1a; 已在kubernetes中部署spinnaker已准备可用的jenkins实例 启用 jenkins触发器 官方文档&…

TZOJ 1379 C语言合法标识符

答案&#xff1a; #include <stdio.h> #include <string.h> int main() {char arr[60];int n 0, i 0, num 0, flag;scanf("%d", &n);getchar(); //读取回车键while (n--) //循环N次{gets(arr);num strlen(arr); //num为字符串长度flag 1; …

利用ElementUI配置商品的规格参数

商品有不同的规格组合&#xff0c;自动生成对应规格的所有组合&#xff0c;并设置该规格的图片、价格、库存数据。 <template><div class"sku-list"><template v-if"!disabled"><div class"sku-list-head"><el-but…

开启新零售时代,引领消费革命

开启新零售时代&#xff0c;引领消费革命 新零售的魅力在于它将线上线下融合&#xff0c;打破了传统零售的界限。以往&#xff0c;消费者需要亲自前往实体店面购物&#xff0c;但如今他们可以通过电子商务平台随时随地进行购物。这种便捷的消费方式不仅节省了时间和精力&#x…

【带头学C++】----- 九、类和对象 ---- 9.2 构造函数

目录 9.2 构造函数 9.2.1 构造函数的概述 9.2.2 构造函数定义方法&#xff08;初始化构造函数&#xff09; 9.2.3 提供构造函数的影响 9.2 构造函数 以下是一些C引入构造函数的原因&#xff1a; 初始化对象&#xff1a;构造函数允许在创建对象时立即初始化该对象的成员变量…

如何快速了解一家公司?

在炒股过程中&#xff0c;我们想要了解一家公司是否具有投资价值&#xff0c;需要查看和阅读很多公司的相关资料。股民们自行去查询往往会花费很多的时间精力&#xff0c;所以专业的炒股软件一般都会给股民提供这些现成的资料。 在金斗云智投APP内&#xff0c;进入到个股详情页…

【Node.js】笔记梳理 7 - mongoose

写在最前&#xff1a;跟着视频学习只是为了在新手期快速入门。想要学习全面、进阶的知识&#xff0c;需要格外注重实战和官方技术文档&#xff0c;文档建议作为手册使用 系列文章 【Node.js】笔记整理 1 - 基础知识【Node.js】笔记整理 2 - 常用模块【Node.js】笔记整理 3 - n…

mac修改默认shell为bash

1. 打开系统偏好设置 2. 点击用户群组 3. 按住ctrl&#xff0c;点击用户名 4. 点击高级选项&#xff0c;修改登录shell 参考&#xff1a;在 Mac 上将 zsh 用作默认 Shell - 官方 Apple 支持 (中国)

Node-red

Node-Red 什么是Node-redNode-red的特点 Node-red的Windows安装安装Node.js安装包下载安装包安装安装检查 安装Node-red安装Note-red运行Note-red 什么是Node-red Node-RED 是一种编程工具&#xff0c;用于以新颖有趣的方式将硬件设备、API 和在线服务连接在一起。 Node-RED 是…

elementui el-table用span-method方法对相同的列名或行名进行合并

看到的一篇文章 同理 如果对第二列进行合并的话copy一下第一个方法&#xff0c;让值赋给第二个数组就可以 // 合并方法mergeCells({ row, column , rowIndex, columnIndex }) {debugger;if (columnIndex 1) {const _row this.spanArr[rowIndex];const _col _row > 0 ? …

抽奖送平板是骗局!!!

在街上被派传单&#xff0c;然后扫了码抽奖中了平板&#xff0c;被领到卖电器门店兑奖。他们给我在宜嘉商城上充值4980&#xff0c;我现场给他们付了4980元&#xff0c;签了他们的业务办理单&#xff0c;上面有违约者赔款30%违约金字样。我领走了荣耀畅玩40plus手机一台。第二天…

手敲单链表,简单了解其运行逻辑

1. 链表 1.1 结构组成 链表是一种物理存储结构上非连续存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。 链表的结构如下图所示&#xff0c;是由很多个节点相互通过引用来连接而成的&#xff1b;每一个节点由两部分组成&#xff0c;分别数据域&…

CSS 垂直水平居中总结(全)

目录 1&#xff0c;不需要知道元素的宽高1.1&#xff0c;flex&#xff08;2种&#xff09;1.2&#xff0c;grid&#xff08;2种&#xff09;1.3&#xff0c;verticle-align:middle1.4&#xff0c;绝对定位1.5&#xff0c;table-cell 2&#xff0c;需要知道元素的宽高2.1&#x…

MySQL 索引,优化,回表,执行计划等相关总结学习

一、MySQL 执行流程 innoDB表引擎&#xff1a;默认的事务型引擎&#xff0c;最重要最广泛的存储引擎&#xff0c;性能非常优秀,数据村粗在共享表空间&#xff0c;可以通过配置分开,主键查询性能高于其他引擎 myISM表引擎&#xff1a;5.1版本前这个是默认的存储引擎&#xff0c…