JobSchedule类是从Android 5开始的,已经是快三年前的东西了,最开始提出这个东西,Google的目的是为了节省电量,关于节电部分的讨论,可以移步知乎的一个话题帖 link。
总体上说,这个提出的意义就是将一些不合适的设备唤醒行为进行统一管理,达到省电的目的。
应该并没有显示广播隐式广播的官方说法,使用 显示Intent 发送的就是显示广播,使用 隐式Intent 发送的就是隐式广播。
在Manifest文件中声明的就是静态注册,代码中通知Context的API进行注册的就是动态注册。静态注册的广播接收器可以在APP进程处于非活状态下,接受广播并激活程序(启动进程)。
鉴于这些特性,有些APP中利用静态注册的形式,监听了一系列系统广播(这些都是通过隐式Intent发出的)。但是,这是比较费电的,举个例子:监听系统新消息广播,提取短信验证码。
这是一个比较常见的需求,一般来说,这个“监听”的生命期应该从调用webservice发出短信开始到相关的Activity结束,但是不排除部分程序员将其做成静态注册的广播接收器。类似的还有网络状态变更的监听(这也是一个很典型的例子,而且更容易设计成静态注册的广播接收器)。
这一系列行为使得我们的APP在不需要监听的情况下也进行了监听,甚至是APP进程没有起的情况下,一旦收到广播通知,APP进程就会启动,可能一些依赖网络的信息同步任务、ping包测试、日志统计模块会运行,无形中增加了耗电,但这些是没有意义的。
所以、JobSchedule设计的目的,有一方面是为了减少不合理的静态注册的系统广播接收器。
我们知道Android O已经正式发布了,但国内使用Android N的还只有三成,L、M、N形成了三分天下,而且我们知道在去年发布的Android N已经取消了针对三项系统广播的接收器的静态注册的支持。分别是:
Android O彻底移除了静态注册系统广播的接收器。
其实这是一个很好的改变,意味着我们有更好的方案来优雅的实习需求,毕竟广播是一个重量级的家伙。而且,在此之前,可能你有使用一些方案实现了需求,但在Android O中将不再受到支持,所以是时候把JobSchedule拿出来再细看看了。
先摘录一段Java doc
/**
* This is an API for scheduling various types of jobs against the framework that will be executed
* in your application's own process.
*
* See {@link android.app.job.JobInfo} for more description of the types of jobs that can be run
* and how to construct them. You will construct these JobInfo objects and pass them to the
* JobScheduler with {@link #schedule(JobInfo)}. When the criteria declared are met, the
* system will execute this job on your application's {@link android.app.job.JobService}.
* You identify which JobService is meant to execute the logic for your job when you create the
* JobInfo with
* {@link android.app.job.JobInfo.Builder#JobInfo.Builder(int,android.content.ComponentName)}.
*
*
* The framework will be intelligent about when you receive your callbacks, and attempt to batch
* and defer them as much as possible. Typically if you don't specify a deadline on your job, it
* can be run at any moment depending on the current state of the JobScheduler's internal queue,
* however it might be deferred as long as until the next time the device is connected to a power
* source.
*
* You do not
* instantiate this class directly; instead, retrieve it through
* {@link android.content.Context#getSystemService
* Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)}.
*/
大体说来,包含以下几点信息:
public abstract class JobScheduler {
/**
* 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;
/**
* @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 An int representing ({@link #RESULT_SUCCESS} or {@link #RESULT_FAILURE}).
*/
public abstract int schedule(JobInfo job);
/**
*
* @param job The job to be scheduled.
* @param packageName The package on behalf of which the job is to be scheduled. This will be
* used to track battery usage and appIdleState.
* @param userId User on behalf of whom this job is to be scheduled.
* @param tag Debugging tag for dumps associated with this job (instead of the service class)
* @return {@link #RESULT_SUCCESS} or {@link #RESULT_FAILURE}
* @hide
*/
@SystemApi
public abstract int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag);
/**
* Cancel a job that is pending in the JobScheduler.
* @param jobId unique identifier for this job. Obtain this value from the jobs returned by
* {@link #getAllPendingJobs()}.
*/
public abstract void cancel(int jobId);
/**
* Cancel all jobs that have been registered with the JobScheduler by this package.
*/
public abstract void cancelAll();
/**
* Retrieve all jobs for this package that are pending in the JobScheduler.
*
* @return a list of all the jobs registered by this package that have not
* yet been executed.
*/
public abstract @NonNull List getAllPendingJobs();
/**
* Retrieve a specific job for this package that is pending in the
* JobScheduler.
*
* @return job registered by this package that has not yet been executed.
*/
public abstract @Nullable JobInfo getPendingJob(int jobId);
}
可供使用的API很简单明了,而且都是需要实现类实现的。
刚才我们在API-doc中看到了两个类:JobService和JobInfo。接下来我们看一看有什么门道。
顾名思义,这是描述一个任务的类。
粗略看一下源码,量还是不少的,其实现了序列化(Parcelable接口)、提供了builder(说明创建它还是比较麻烦的)。
从源码中截取了部分,构造时需要初始化的常量:
private final int jobId;
private final PersistableBundle extras;
private final ComponentName service;
private final boolean requireCharging;
private final boolean requireDeviceIdle;
private final TriggerContentUri[] triggerContentUris;
private final long triggerContentUpdateDelay;
private final long triggerContentMaxDelay;
private final boolean hasEarlyConstraint;
private final boolean hasLateConstraint;
private final int networkType;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
private final boolean isPeriodic;
private final boolean isPersisted;
private final long intervalMillis;
private final long flexMillis;
private final long initialBackoffMillis;
private final int backoffPolicy;
private final int priority;
private final int flags;
能够描述的任务,在这里可见一斑,以上类成员的具体含义可以参见getter的doc、或者Builder的相关内容,builder中更为详细。
接下来我们就看看Builder中的主要内容:
/**
* Initialize a new Builder to construct a {@link JobInfo}.
*
* @param jobId Application-provided id for this job. Subsequent calls to cancel, or
* jobs created with the same jobId, will update the pre-existing job with
* the same id. This ID must be unique across all clients of the same uid
* (not just the same package). You will want to make sure this is a stable
* id across app updates, so probably not based on a resource ID.
* @param jobService The endpoint that you implement that will receive the callback from the
* JobScheduler.
*/
public Builder(int jobId, ComponentName jobService) {
mJobService = jobService;
mJobId = jobId;
}
/** @hide */
public Builder setPriority(int priority) {
mPriority = priority;
return this;
}
/** @hide */
public Builder setFlags(int flags) {
mFlags = flags;
return this;
}
/**
* Set optional extras. This is persisted, so we only allow primitive types.
* @param extras Bundle containing extras you want the scheduler to hold on to for you.
*/
public Builder setExtras(PersistableBundle extras) {
mExtras = extras;
return this;
}
/**
* Set some description of the kind of network type your job needs to have.
* Not calling this function means the network is not necessary, as the default is
* {@link #NETWORK_TYPE_NONE}.
* Bear in mind that calling this function defines network as a strict requirement for your
* job. If the network requested is not available your job will never run. See
* {@link #setOverrideDeadline(long)} to change this behaviour.
*/
public Builder setRequiredNetworkType(int networkType) {
mNetworkType = networkType;
return this;
}
/**
* Specify that to run this job, the device needs to be plugged in. This defaults to
* false.
* @param requiresCharging Whether or not the device is plugged in.
*/
public Builder setRequiresCharging(boolean requiresCharging) {
mRequiresCharging = requiresCharging;
return this;
}
/**
* Specify that to run, the job needs the device to be in idle mode. This defaults to
* false.
* Idle mode is a loose definition provided by the system, which means that the device
* is not in use, and has not been in use for some time. As such, it is a good time to
* perform resource heavy jobs. Bear in mind that battery usage will still be attributed
* to your application, and surfaced to the user in battery stats.
* @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
* window.
*/
public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
mRequiresDeviceIdle = requiresDeviceIdle;
return this;
}
/**
* Add a new content: URI that will be monitored with a
* {@link android.database.ContentObserver}, and will cause the job to execute if changed.
* If you have any trigger content URIs associated with a job, it will not execute until
* there has been a change report for one or more of them.
* Note that trigger URIs can not be used in combination with
* {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}. To continually monitor
* for content changes, you need to schedule a new JobInfo observing the same URIs
* before you finish execution of the JobService handling the most recent changes.
* Because because setting this property is not compatible with periodic or
* persisted jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called.
*
* The following example shows how this feature can be used to monitor for changes
* in the photos on a device.
*
* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/PhotosContentJob.java
* job}
*
* @param uri The content: URI to monitor.
*/
public Builder addTriggerContentUri(@NonNull TriggerContentUri uri) {
if (mTriggerContentUris == null) {
mTriggerContentUris = new ArrayList<>();
}
mTriggerContentUris.add(uri);
return this;
}
/**
* Set the delay (in milliseconds) from when a content change is detected until
* the job is scheduled. If there are more changes during that time, the delay
* will be reset to start at the time of the most recent change.
* @param durationMs Delay after most recent content change, in milliseconds.
*/
public Builder setTriggerContentUpdateDelay(long durationMs) {
mTriggerContentUpdateDelay = durationMs;
return this;
}
/**
* Set the maximum total delay (in milliseconds) that is allowed from the first
* time a content change is detected until the job is scheduled.
* @param durationMs Delay after initial content change, in milliseconds.
*/
public Builder setTriggerContentMaxDelay(long durationMs) {
mTriggerContentMaxDelay = durationMs;
return this;
}
/**
* Specify that this job should recur with the provided interval, not more than once per
* period. You have no control over when within this interval this job will be executed,
* only the guarantee that it will be executed at most once within this interval.
* Setting this function on the builder with {@link #setMinimumLatency(long)} or
* {@link #setOverrideDeadline(long)} will result in an error.
* @param intervalMillis Millisecond interval for which this job will repeat.
*/
public Builder setPeriodic(long intervalMillis) {
return setPeriodic(intervalMillis, intervalMillis);
}
/**
* Specify that this job should recur with the provided interval and flex. The job can
* execute at any time in a window of flex length at the end of the period.
* @param intervalMillis Millisecond interval for which this job will repeat. A minimum
* value of {@link #getMinPeriodMillis()} is enforced.
* @param flexMillis Millisecond flex for this job. Flex is clamped to be at least
* {@link #getMinFlexMillis()} or 5 percent of the period, whichever is
* higher.
*/
public Builder setPeriodic(long intervalMillis, long flexMillis) {
mIsPeriodic = true;
mIntervalMillis = intervalMillis;
mFlexMillis = flexMillis;
mHasEarlyConstraint = mHasLateConstraint = true;
return this;
}
/**
* Specify that this job should be delayed by the provided amount of time.
* Because it doesn't make sense setting this property on a periodic job, doing so will
* throw an {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called.
* @param minLatencyMillis Milliseconds before which this job will not be considered for
* execution.
*/
public Builder setMinimumLatency(long minLatencyMillis) {
mMinLatencyMillis = minLatencyMillis;
mHasEarlyConstraint = true;
return this;
}
/**
* Set deadline which is the maximum scheduling latency. The job will be run by this
* deadline even if other requirements are not met. Because it doesn't make sense setting
* this property on a periodic job, doing so will throw an
* {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called.
*/
public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
mMaxExecutionDelayMillis = maxExecutionDelayMillis;
mHasLateConstraint = true;
return this;
}
/**
* Set up the back-off/retry policy.
* This defaults to some respectable values: {30 seconds, Exponential}. We cap back-off at
* 5hrs.
* Note that trying to set a backoff criteria for a job with
* {@link #setRequiresDeviceIdle(boolean)} will throw an exception when you call build().
* This is because back-off typically does not make sense for these types of jobs. See
* {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
* for more description of the return value for the case of a job executing while in idle
* mode.
* @param initialBackoffMillis Millisecond time interval to wait initially when job has
* failed.
* @param backoffPolicy is one of {@link #BACKOFF_POLICY_LINEAR} or
* {@link #BACKOFF_POLICY_EXPONENTIAL}
*/
public Builder setBackoffCriteria(long initialBackoffMillis, int backoffPolicy) {
mBackoffPolicySet = true;
mInitialBackoffMillis = initialBackoffMillis;
mBackoffPolicy = backoffPolicy;
return this;
}
/**
* Set whether or not to persist this job across device reboots. This will only have an
* effect if your application holds the permission
* {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED}. Otherwise an exception will
* be thrown.
* @param isPersisted True to indicate that the job will be written to disk and loaded at
* boot.
*/
public Builder setPersisted(boolean isPersisted) {
mIsPersisted = isPersisted;
return this;
}
需要一个唯一的jobId和用于接受任务回调的Service类路径。注意这个jobID会用于取消任务、同id覆盖,所以粒度应该到实例的级别。
只接受基础数据类型
任务触发的网络状态条件,均为JobInfo中的常量
是否需要处于充电,默认:否
触发条件需要处于idle状态,默认:否。idle状态:不处于充电+息屏 逐渐进入的一种设备休眠的状态、会限制到网络访问等,有利于省电。
添加一个内容变动监视,注意:不可以和setPeriodic(long)
或setPersisted(boolean)
同时使用。连续的监视需要持续创建新任务。
注意:有一些设置是互斥的
隶属于package android.app.job
,继承自Service
。
/**
* Entry point for the callback from the {@link android.app.job.JobScheduler}.
* This is the base class that handles asynchronous requests that were previously scheduled. You
* are responsible for overriding {@link JobService#onStartJob(JobParameters)}, which is where
* you will implement your job logic.
* This service executes each incoming job on a {@link android.os.Handler} running on your
* application's main thread. This means that you must offload your execution logic to
* another thread/handler/{@link android.os.AsyncTask} of your choosing. Not doing so will result
* in blocking any future callbacks from the JobManager - specifically
* {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the
* scheduling requirements are no longer being met.
*/
android.permission.BIND_JOB_SERVICE
权限。大体内容就到这里,可以再写一写实验性的demo。本篇暂不提供demo