闲言碎语:
最初,我的boss叫我看下JobScheduler API(可能我们项目中要用到),让我写个使用文档出来,然后给公司其他同事讲讲,但是由于公司项目升级迫在眉睫,写文档这事儿就搁置了,现在有大把的空闲时间了,于是乎接着之前写的部分文档就有了下面文章。本文是我结合谷歌官方对JobScheduler的讲解和网络上各路大神的见解来写的,若有理解不到位处,还望谅解,若有错误,也欢迎指出,共同学习,共同进步。
一、JobScheduler 是干什么的?
后台任务是我们应用开发中常见的问题,比如后台下载APP升级包。
最简单的做法就是直接开一个Thread,用Handler通信即可,但是所开的线程和Activity是没有关系的,一旦应用被杀死 ,就和之前所创建的线程失去了联系,就算Activity再次启动,此时启动的线程并不是之前的线程。当然,这个问题可以用Service解决,那么如果用户把Service也kill了呢?你可能会想用AlarmManager,周期性地进行唤醒,可是用户关机了,AlarmManager也就没了。当然这还没到“穷途末路”,用BroadcastReceiver就可以直接解决服务自启的所有问题。
但是让我们换个角度来看,打开管理器查看当前活动的服务就能看到,这些一直在活动的后台任务往往是导致手机耗电飞快的原因。那么我们是否可以当满足某些条件时,才开始执行可以延迟执行的任务呢?比如设备在充电时,或者设备在连接上wifi时,或者设备在空闲时,或者超过了任务的最大延迟时间,才让任务开始执行,是不是会节省很多电量呢?于是乎,就有了JobScheduler。
Android
L开始引入了一个新的API:JobScheduler。正如其名,它允许将任务调度直接交给系统,并且你所需要的许多约束条件,如周期调度,延迟调度,网络连接,电源插入,还有Android L引入的空闲模式,都可以快捷地进行设置。
首先,我们先来看下官方的JobScheduler的API。
This is an API for scheduling various types of jobs against the framework that will be executed in your application's own process.
这是一个在你应用程序进程中执行的一个API调度框架
See
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
schedule(JobInfo)
. When the criteria declared are met, the system will execute this job on your application's
JobService
. You identify which JobService is meant to execute the logic for your job when you create the JobInfo with
JobInfo.Builder(int, android.content.ComponentName)
.
你可以构建并运行一个描述了不同类型的
JobInfo
,你可以构造
JobInfo
,并通过
JobScheduler
的
chedule(JobInfo)
方法来使用。当符合标准声明时,系统将在你的应用的
JobService
服务里面执行调度。当你通过
JobInfo.Builder(int, android.content.ComponentName)
来创建一个
JobInfo
时,你应该已经明确哪个
JobService
应该执行你的调度的逻辑。
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.
JobScheduler
调度框架会尽可能的分批和延迟处理。当你没有明确你的工作的
deadline
时,工作的执行取决于
JobScheduler
内部队列的当前状态,它也可能被延迟到设备充电时执行。
You
do not instantiate this class directly; instead, retrieve it
through
Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)
.
通过
Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)
来获取
JobScheduler
的对象。
二、如何在项目中灵活应用JobScheduler ?
我们来看一下谷歌官方给出的例子,点击这里下载源码
运行后如下图:

运行后如下图:
- 创建JobService
我们创建一个继承自JobService且名字为MyJobService的类,这个类必须实现两个方法,分别是onStartJob(JobParameters params)和 onStopJob(JobParameters params);
/**
* Service to handle callbacks from the JobScheduler. Requests scheduled with the JobScheduler
* ultimately land on this service's "onStartJob" method. It runs jobs for a specific amount of time
* and finishes them. It keeps the activity updated with changes via a Messenger.
*/
public class MyJobService extends JobService {
private static final String TAG = MyJobService . class . getSimpleName () ;
private Messenger mActivityMessenger ;
@Override
public void onCreate () {
super . onCreate () ;
Log . i ( TAG , "Service created" ) ;
}
@Override
public void onDestroy () {
super . onDestroy () ;
Log . i ( TAG , "Service destroyed" ) ;
}
/**
* When the app's MainActivity is created, it starts this service. This is so that the
* activity and this service can communicate back and forth. See "setUiCallback()"
*/
@Override
public int onStartCommand ( Intent intent , int flags , int startId ) {
mActivityMessenger = intent . getParcelableExtra ( MESSENGER_INTENT_KEY ) ;
return START_NOT_STICKY ;
}
@Override
public boolean onStartJob ( final JobParameters params ) {
// The work that this service "does" is simply wait for a certain duration and finish
// the job (on another thread).
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 () {
@Override
public void run () {
sendMessage ( MSG_COLOR_STOP , params . getJobId ()) ;
jobFinished ( params , false ) ;
}
} , duration ) ;
Log . i ( TAG , "on start job: " + params . getJobId ()) ;
// Return true as there's more work to be done with this job.
return true ;
}
@Override
public boolean onStopJob ( JobParameters params ) {
// Stop tracking these job parameters, as we've 'finished' executing.
sendMessage ( MSG_COLOR_STOP , params . getJobId ()) ;
Log . i ( TAG , "on stop job: " + params . getJobId ()) ;
// Return false to drop the job.
return false ;
}
private void sendMessage ( int messageID , @Nullable Object params ) {
// If this service is launched by the JobScheduler, there's no callback Messenger. It
// only exists when the MainActivity calls startService() with the callback in the Intent.
if ( mActivityMessenger == null ) {
Log . d ( TAG , "Service is bound, not started. There's no callback to send a message to." ) ;
return ;
}
Message m = Message . obtain () ;
m . what = messageID ;
m . obj = params ;
try {
mActivityMessenger . send ( m ) ;
} catch ( RemoteException e ) {
Log . e ( TAG , "Error passing service object back to activity." ) ;
}
}
}
* Service to handle callbacks from the JobScheduler. Requests scheduled with the JobScheduler
* ultimately land on this service's "onStartJob" method. It runs jobs for a specific amount of time
* and finishes them. It keeps the activity updated with changes via a Messenger.
*/
public class MyJobService extends JobService {
private static final String TAG = MyJobService . class . getSimpleName () ;
private Messenger mActivityMessenger ;
@Override
public void onCreate () {
super . onCreate () ;
Log . i ( TAG , "Service created" ) ;
}
@Override
public void onDestroy () {
super . onDestroy () ;
Log . i ( TAG , "Service destroyed" ) ;
}
/**
* When the app's MainActivity is created, it starts this service. This is so that the
* activity and this service can communicate back and forth. See "setUiCallback()"
*/
@Override
public int onStartCommand ( Intent intent , int flags , int startId ) {
mActivityMessenger = intent . getParcelableExtra ( MESSENGER_INTENT_KEY ) ;
return START_NOT_STICKY ;
}
@Override
public boolean onStartJob ( final JobParameters params ) {
// The work that this service "does" is simply wait for a certain duration and finish
// the job (on another thread).
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 () {
@Override
public void run () {
sendMessage ( MSG_COLOR_STOP , params . getJobId ()) ;
jobFinished ( params , false ) ;
}
} , duration ) ;
Log . i ( TAG , "on start job: " + params . getJobId ()) ;
// Return true as there's more work to be done with this job.
return true ;
}
@Override
public boolean onStopJob ( JobParameters params ) {
// Stop tracking these job parameters, as we've 'finished' executing.
sendMessage ( MSG_COLOR_STOP , params . getJobId ()) ;
Log . i ( TAG , "on stop job: " + params . getJobId ()) ;
// Return false to drop the job.
return false ;
}
private void sendMessage ( int messageID , @Nullable Object params ) {
// If this service is launched by the JobScheduler, there's no callback Messenger. It
// only exists when the MainActivity calls startService() with the callback in the Intent.
if ( mActivityMessenger == null ) {
Log . d ( TAG , "Service is bound, not started. There's no callback to send a message to." ) ;
return ;
}
Message m = Message . obtain () ;
m . what = messageID ;
m . obj = params ;
try {
mActivityMessenger . send ( m ) ;
} catch ( RemoteException e ) {
Log . e ( TAG , "Error passing service object back to activity." ) ;
}
}
}
当任务开始时会执行onStartJob(JobParameters params)方法,因为这是系统用来触发已经被执行的任务。正如你所看到的,这个方法返回一个boolean值。如果返回值是false,系统假设这个方法返回时任务已经执行完毕。如果返回值是true,那么系统假定这个任务正要被执行。当任务执行完毕时你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来通知系统。当系统接收到一个取消请求时,系统会调用onStopJob(JobParameters params)方法取消正在等待执行的任务。很重要的一点是如果onStartJob(JobParameters params)返回false,那么系统假定在接收到一个取消请求时已经没有正在运行的任务。换句话说,onStopJob(JobParameters params)在这种情况下不会被调用。
需要注意的是这个JobService运行在你的主线程,这意味着你需要使用子线程,handler, 或者一个异步任务来运行耗时的操作以防止阻塞主线程。
在谷歌的sample中自定义了一个Handler来处理耗时的操作:
/**
* A { @link Handler } allows you to send messages associated with a thread. A { @link Messenger }
* uses this handler to communicate from { @link MyJobService }. It's also used to make
* the start and stop views blink for a short period of time.
*/
private static class IncomingMessageHandler extends Handler {
// Prevent possible leaks with a weak reference.
private WeakReference < MainActivity > mActivity ;
IncomingMessageHandler ( MainActivity activity ) {
super ( /* default looper */ ) ;
this . mActivity = new WeakReference <> ( activity ) ;
}
@Override
public void handleMessage ( Message msg ) {
MainActivity mainActivity = mActivity . get () ;
if ( mainActivity == null ) {
// Activity is no longer available, exit.
return ;
}
View showStartView = mainActivity . findViewById ( R.id . onstart_textview ) ;
View showStopView = mainActivity . findViewById ( R.id . onstop_textview ) ;
Message m ;
switch ( msg . what ) {
/*
* Receives callback from the service when a job has landed
* on the app. Turns on indicator and sends a message to turn it off after
* a second.
*/
case MSG_COLOR_START :
// Start received, turn on the indicator and show text.
showStartView . setBackgroundColor ( getColor ( R.color . start_received )) ;
updateParamsTextView ( msg . obj , "started" ) ;
// Send message to turn it off after a second.
m = Message . obtain ( this , MSG_UNCOLOR_START ) ;
sendMessageDelayed ( m , 1000L ) ;
break ;
/*
* Receives callback from the service when a job that previously landed on the
* app must stop executing. Turns on indicator and sends a message to turn it
* off after two seconds.
*/
case MSG_COLOR_STOP :
// Stop received, turn on the indicator and show text.
showStopView . setBackgroundColor ( getColor ( R.color . stop_received )) ;
updateParamsTextView ( msg . obj , "stopped" ) ;
// Send message to turn it off after a second.
m = obtainMessage ( MSG_UNCOLOR_STOP ) ;
sendMessageDelayed ( m , 2000L ) ;
break ;
case MSG_UNCOLOR_START :
showStartView . setBackgroundColor ( getColor ( R.color . none_received )) ;
updateParamsTextView ( null , "" ) ;
break ;
case MSG_UNCOLOR_STOP :
showStopView . setBackgroundColor ( getColor ( R.color . none_received )) ;
updateParamsTextView ( null , "" ) ;
break ;
}
}
private void updateParamsTextView ( @Nullable Object jobId , String action ) {
TextView paramsTextView = ( TextView ) mActivity . get () . findViewById ( R.id . task_params ) ;
if ( jobId == null ) {
paramsTextView . setText ( "" ) ;
return ;
}
String jobIdText = String . valueOf ( jobId ) ;
paramsTextView . setText ( String . format ( "Job ID %s %s" , jobIdText , action )) ;
}
private int getColor ( @ColorRes int color ) {
return mActivity . get () . getResources () . getColor ( color ) ;
}
}
* A { @link Handler } allows you to send messages associated with a thread. A { @link Messenger }
* uses this handler to communicate from { @link MyJobService }. It's also used to make
* the start and stop views blink for a short period of time.
*/
private static class IncomingMessageHandler extends Handler {
// Prevent possible leaks with a weak reference.
private WeakReference < MainActivity > mActivity ;
IncomingMessageHandler ( MainActivity activity ) {
super ( /* default looper */ ) ;
this . mActivity = new WeakReference <> ( activity ) ;
}
@Override
public void handleMessage ( Message msg ) {
MainActivity mainActivity = mActivity . get () ;
if ( mainActivity == null ) {
// Activity is no longer available, exit.
return ;
}
View showStartView = mainActivity . findViewById ( R.id . onstart_textview ) ;
View showStopView = mainActivity . findViewById ( R.id . onstop_textview ) ;
Message m ;
switch ( msg . what ) {
/*
* Receives callback from the service when a job has landed
* on the app. Turns on indicator and sends a message to turn it off after
* a second.
*/
case MSG_COLOR_START :
// Start received, turn on the indicator and show text.
showStartView . setBackgroundColor ( getColor ( R.color . start_received )) ;
updateParamsTextView ( msg . obj , "started" ) ;
// Send message to turn it off after a second.
m = Message . obtain ( this , MSG_UNCOLOR_START ) ;
sendMessageDelayed ( m , 1000L ) ;
break ;
/*
* Receives callback from the service when a job that previously landed on the
* app must stop executing. Turns on indicator and sends a message to turn it
* off after two seconds.
*/
case MSG_COLOR_STOP :
// Stop received, turn on the indicator and show text.
showStopView . setBackgroundColor ( getColor ( R.color . stop_received )) ;
updateParamsTextView ( msg . obj , "stopped" ) ;
// Send message to turn it off after a second.
m = obtainMessage ( MSG_UNCOLOR_STOP ) ;
sendMessageDelayed ( m , 2000L ) ;
break ;
case MSG_UNCOLOR_START :
showStartView . setBackgroundColor ( getColor ( R.color . none_received )) ;
updateParamsTextView ( null , "" ) ;
break ;
case MSG_UNCOLOR_STOP :
showStopView . setBackgroundColor ( getColor ( R.color . none_received )) ;
updateParamsTextView ( null , "" ) ;
break ;
}
}
private void updateParamsTextView ( @Nullable Object jobId , String action ) {
TextView paramsTextView = ( TextView ) mActivity . get () . findViewById ( R.id . task_params ) ;
if ( jobId == null ) {
paramsTextView . setText ( "" ) ;
return ;
}
String jobIdText = String . valueOf ( jobId ) ;
paramsTextView . setText ( String . format ( "Job ID %s %s" , jobIdText , action )) ;
}
private int getColor ( @ColorRes int color ) {
return mActivity . get () . getResources () . getColor ( color ) ;
}
}
在Handler中,你需要实现handleMessage(Message msg)方法来处理你的任务逻辑。此sample里面用两个TextView的背景颜色改变的时长来模拟耗时的任务,代码理解比较简单。
当任务执行完毕之后,你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来让系统知道这个任务已经结束,系统可以将下一个任务添加到队列中。如果你没有调用jobFinished(JobParameters params, boolean needsRescheduled),你的任务只会执行一次,而应用中的其他任务就不会被执行。
jobFinished(JobParameters params, boolean needsRescheduled)的两个参数中的params参数是从JobService的onStartJob(JobParameters params)的params传递过来的,needsRescheduled参数是让系统知道这个任务是否应该在最处的条件下被重复执行。这个boolean值很有用,因为它指明了你如何处理由于其他原因导致任务执行失败的情况,例如一个失败的网络请求调用。
创建了Handler实例之后,你就可以实现onStartJob(JobParameters params) 和onStopJob(JobParameters params)方法来控制你的任务了
你还需要到AndroidManifest.xml中添加一个service节点让你的应用拥有绑定和使用这个JobService的权限。
<
service
android :name= ".service.MyJobService"
android :exported= "true"
android :name= ".service.MyJobService"
android :exported= "true"
android:permission="android.permission.BIND_JOB_SERVICE"/>
- 创建JobScheduler对象
//
创建
JobScheduler
对象
JobScheduler
tm
=
(
JobScheduler
)
getSystemService
(
Context
.
JOB_SCHEDULER_SERVICE
)
;
- 创建JobInfo对象并设置不同的选项来控制任务的进行
//
创建
JobInfo
对象
JobInfo.Builder builde = new JobInfo . Builder ( mJobId ++ , mServiceComponent ) ;
String delay = mDelayEditText . getText () . toString () ;
if ( ! TextUtils . isEmpty ( delay )) {
// 设置最小延迟时间的 ms 值
builder . setMinimumLatency ( Long . valueOf ( delay ) * 1000 ) ;
}
String deadline = mDeadlineEditText . getText () . toString () ;
if ( ! TextUtils . isEmpty ( deadline )) {
// 设置最大的延迟时间的 ms 值
builder . setOverrideDeadline ( Long . valueOf ( deadline ) * 1000 ) ;
}
boolean requiresUnmetered = mWiFiConnectivityRadioButton . isChecked () ;
boolean requiresAnyConnectivity = mAnyConnectivityRadioButton . isChecked () ;
if ( requiresUnmetered ) {
// 设备不是蜂窝网络 ( 比如在 WIFI 连接时 ) 时任务才会被执行
builder . setRequiredNetworkType ( JobInfo . NETWORK_TYPE_UNMETERED ) ;
} else if ( requiresAnyConnectivity ) {
// 需要任意一种网络才使得任务可以执行
builder . setRequiredNetworkType ( JobInfo . NETWORK_TYPE_ANY ) ;
}
// 设置是否要求设备在空闲状态下才开始执行任务
builder . setRequiresDeviceIdle ( mRequiresIdleCheckbox . isChecked ()) ;
// 设置是否要求设备在充电的状态下才开始执行任务
builder . setRequiresCharging ( mRequiresChargingCheckBox . isChecked ()) ;
// Extras, work duration.
PersistableBundle extras = new PersistableBundle () ;
String workDuration = mDurationTimeEditText . getText () . toString () ;
if ( TextUtils . isEmpty ( workDuration )) {
workDuration = "1" ;
}
extras . putLong ( WORK_DURATION_KEY , Long . valueOf ( workDuration ) * 1000 ) ;
builder . setExtras ( extras ) ;
JobInfo.Builder builde = new JobInfo . Builder ( mJobId ++ , mServiceComponent ) ;
String delay = mDelayEditText . getText () . toString () ;
if ( ! TextUtils . isEmpty ( delay )) {
// 设置最小延迟时间的 ms 值
builder . setMinimumLatency ( Long . valueOf ( delay ) * 1000 ) ;
}
String deadline = mDeadlineEditText . getText () . toString () ;
if ( ! TextUtils . isEmpty ( deadline )) {
// 设置最大的延迟时间的 ms 值
builder . setOverrideDeadline ( Long . valueOf ( deadline ) * 1000 ) ;
}
boolean requiresUnmetered = mWiFiConnectivityRadioButton . isChecked () ;
boolean requiresAnyConnectivity = mAnyConnectivityRadioButton . isChecked () ;
if ( requiresUnmetered ) {
// 设备不是蜂窝网络 ( 比如在 WIFI 连接时 ) 时任务才会被执行
builder . setRequiredNetworkType ( JobInfo . NETWORK_TYPE_UNMETERED ) ;
} else if ( requiresAnyConnectivity ) {
// 需要任意一种网络才使得任务可以执行
builder . setRequiredNetworkType ( JobInfo . NETWORK_TYPE_ANY ) ;
}
// 设置是否要求设备在空闲状态下才开始执行任务
builder . setRequiresDeviceIdle ( mRequiresIdleCheckbox . isChecked ()) ;
// 设置是否要求设备在充电的状态下才开始执行任务
builder . setRequiresCharging ( mRequiresChargingCheckBox . isChecked ()) ;
// Extras, work duration.
PersistableBundle extras = new PersistableBundle () ;
String workDuration = mDurationTimeEditText . getText () . toString () ;
if ( TextUtils . isEmpty ( workDuration )) {
workDuration = "1" ;
}
extras . putLong ( WORK_DURATION_KEY , Long . valueOf ( workDuration ) * 1000 ) ;
builder . setExtras ( extras ) ;
- setPeriodic(longtime):设置任务可以每隔time秒运行一次
- setMinimumLatency(long minLatencyMillis):设置任务的延迟执行时间(单位是毫秒),与setPeriodic(longtime)方法不兼容,如果这两个方法同时调用了就会引起异常;
-
setOverrideDeadline(long maxExecutionDelayMillis):
设置任务最大的延迟时间。如果到了规定的时间时其他条件还未满足,任务也会被启动。与setMinimumLatency(long minLatencyMillis)一样,这个方法也会与setPeriodic( long time ),同时调用这两个方法会引发异常。 -
setPersisted(boolean isPersisted):
设置设备重启之后你的任务是否还要继续执行。 - setRequiredNetworkType(int networkType):
设置满足指定的网络条件时才会被执行。默认条件是JobInfo.NETWORK_TYPE_NONE,这意味着不管是否有网络这个任务都会被执行。另外两个可选类型,一种是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一种网络才使得任务可以执行。另一种是JobInfo.NETWORK_TYPE_UNMETERED,它表示设备不是蜂窝网络( 比如在WIFI连接时 )时任务才会被执行。 -
setRequiresCharging(boolean requiresCharging):
设置只有当设备在充电时这个任务才会被执行。 -
setRequiresDeviceIdle(boolean requiresDeviceIdle):
设置只有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务。
需要注意的是setRequiredNetworkType(int networkType), setRequiresCharging(boolean requireCharging) and setRequiresDeviceIdle(boolean requireIdle)者几个方法可能会使得你的任务无法执行,除非调用setOverrideDeadline(long time)设置了最大延迟时间,使得你的任务在未满足条件的情况下也会被执行(达到最大延迟时间)。
- 调用JobScheduler对象的schedule()方法
JobInfo对象创建好并设置了欲置的条件后,你就可以调用schedule()方法将JobInfo对象发送到JobScheduler中
tm
.
schedule
(
builder
.
build
())
;
值得注意的是,这个schedule方法会返回一个整型。如果schedule方法失败了,它会返回一个小于0的错误码。否则它会我们在JobInfo.Builder中定义的标识id。
if
(
tm
.
schedule
(
builder
.
build
() )
<=
0
)
{
//If something goes wrong
//If something goes wrong
}
三、总结
至此,你已学会怎么实现一个使用Handler对象来运行后台任务的JobService子类,你也学会了如何使用JobInfo.Builder来设置各种执行后台任务需要的条件。因此,你的APP可以节省用户更多的电量了,既你的APP的性能提升了。