浅析Android——Android 8.0(O)后台服务的限制和变化

Android 最近几版的特色主要集中在省电和后台管理上,O的发布,对Service和Broadcast又近一步加强了管束。主要可概括为如下两点:

      1、后台应用不被允许创建后台服务,必须通过JobScheduler或者Context.startForegroundService()进行创建

      2、特定的隐式广播不再被允许启动,必须通过JobScheduler调用或者显式注册的方式才能启动

      google之所以限制隐式广播,是因为广播的滥用导致设备在息屏等状态下,依然无法有效的做到省电,往往一个系统广播,会唤醒多个未启动的第三方应用。因此在O中,隐式广播的限制是很严厉的。谷歌给出了允许隐式注册的广播列表https://developer.android.com/preview/features/background-broadcasts.html,为了方便懒得上墙的小伙伴们,贴出整理的广播列表:

 
  1. 开机广播,之所以被允许,因为每次设备启动只会发送一次

  2. 1、ACTION_LOCKED_BOOT_COMPLETED(直接启动模式下才会发出)

  3. 2、ACTION_BOOT_COMPLETED

  4.  
  5. 以下三个广播有权限限制,一般应用无法接受。

  6. 3、ACTION_USER_INITIALIZE

  7. 4、android.intent.action.USER_ADDED

  8. 5、android.intent.action.USER_REMOVED

  9.  
  10. 地区变更广播,这个地区指的是国家一级,一般不会发送

  11. 6、ACTION_LOCALE_CHANGED

  12. 7、

  13. 时区闹钟变更相关的广播,一般时间类APP需要

  14. 7、ACTION_TIMEZONE_CHANGED

  15. 8、ACTION_NEXT_ALARM_CLOCK_CHANGED

  16. 9、android.intent.action.TIME_SET

  17.  
  18. usb事件广播,需要设备触发usb事件才能发送

  19. 10、ACTION_USB_ACCESSORY_ATTACHED

  20. 11、ACTION_USB_ACCESSORY_DETACHED

  21. 12、ACTION_USB_DEVICE_ATTACHED

  22. 13、ACTION_USB_DEVICE_DETACHED

  23.  
  24. 蓝牙事件广播,需要设备触发蓝牙事件才能发送

  25. 14、ACTION_CONNECTION_STATE_CHANGED

  26. 15、ACTION_ACL_CONNECTED

  27. 16、ACTION_ACL_DISCONNECTED

  28.  
  29. OEM相关的广播,一般是OEM通讯类应用会需要

  30. 17、ACTION_CARRIER_CONFIG_CHANGED

  31. 18、TelephoneyIntents.ACTION_*_SUBSCRIPTION_CHANGED

  32. 19、TelephoneyIntents.SECRET_CODE_ACTION

  33.  
  34. 登录用户状态发生改变时,这里指的是google登录,一般我们用不到,同时这个广播也在API26中废弃了。

  35. 20、LOGIN_ACCOUNTS_CHANGED_ACTION

  36.  
  37. 用户通过设置页面清除掉应用数据后才会发出

  38. 21、ACTION_PACKAGE_DATA_CLEARED

  39.  
  40. 来电广播

  41. 22、ACTION_NEW_OUTGOING_CALL

  42.  
  43. 设备隐私状态变更广播

  44. 23、ACTION_DEVICE_OWNER_CHANGED

  45.  
  46. 日历提醒广播,是由日历提供者发送给日历程序的

  47. 24、ACTION_EVENT_REMINDER

  48.  
  49. 程序卸载广播,跟程序卸载相关的广播中只有这个是被允许的。

  50. 25、ACTION_PACKAGE_FULLY_REMOVED

  51.  
  52. 设备媒体相关广播,但是一般会在开机时发出或者是在物理设备交互时发出。

  53. 26、ACTION_MEDIA_MOUNTED

  54. 27、ACTION_MEDIA_UNMOUNTABLE

  55. 28、ACTION_MEDIA_CHECKING

  56. 29、ACTION_MEDIA_UNMOUNTED

  57. 30、ACTION_MEDIA_EJECT

  58. 31、ACTION_MEDIA_REMOVED

  59. 32、ACTION_MEDIA_BAD_REMOVAL

  60.  
  61. 短信接收广播

  62. 33、SMS_RECEIVED_ACTION

  63. 34、WAP_PUSH_RECEIVED_ACTION

    可以概括的说,可以进行隐式注册的广播都是非频繁发送的,或者有着特定的触发场景的。google的目的就是杜绝一个广播唤醒多个后台应用这种现象。如果我们的应用中涉及到了列表以外的广播,就有必要进行O的适配了。

    google针对前台应用的定义,认为如果满足以下任意条件,应用将被视为处于前台:

   1、具有可见 Activity(不管该 Activity 已启动还是已暂停)。

   2、具有前台服务。

   3、另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个内容提供程序)。

  但在开发中,我们会更多的遇到后台运行的需求,当应用处于后台时,监听特定的系统广播或者设置守护Service显然不能很好的适应高版本,所以按照google的剧本走才是正道。以上两各变更点都提到了JobScheduler,JobScheduler作为API21新增类,如它的名字计划任务一样,通过设置约束条件,使系统可以在不影响用户体验的情况下安排这些作业执行。应用作业不会主动挤占系统运行时间,而是在约束范围下执行工作。Android也在不断地增加JobScheduler支持的约束条件,Oreo就新增了一个运行场景:

       1、NETWORK_TYPE_METERED 作业需要一个按流量计费的网络连接,比如大多数移动数据网络数据套餐。

新增了两个约束条件:

      1、JobInfo.isRequireStorageNotLow() 如果设备的可用存储空间非常低,作业将不会运行。

      2、JobInfo.isRequireBatteryNotLow() 如果电池电量等于或低于临界阈值,作业将不会运行;临界阈值是指设备显示 Low battery warning系统对话框的电量。

  当你需要持续的后台监听功能时,用守护Service或者监听特定的高频系统广播显然已经不合时宜了,google希望你通过JobSchedular来实现类似的功能。JobScheduler的运行实质是JobService,google对JobService的定义有这样一句话:This service executes each incoming job on a {@link android.os.Handler} running on your application's main thread.表明JobService是在主线程处理任务,也就是说当一个JobService处于onStartJob()时,应用进程实际是处于激活状态的,满足上文前台定义中的第二条:具有前台服务。而任务处理又是在系统许可范围内,可谓是皆大欢喜。同时JobService是继承自Service的,它的生命周期是:

onCreated()-->onStartJob-->onStopJob()-->jobFinished()-->onDestroy()

onCreated()继承自Service,google对onCreated的定义是这样的:Called by the system when the service is first created.  Do not call this method directly.即当前服务被创建时会调用一次,被创建意味着这个服务之前没有存在过或者被onDestroy了。

onStartJob()-->onStopJob()这个工作阶段取决于配置的约束条件,onStartJob()不会无限制的重复执行。如果我们不设定约束条件,那么JobService在创建成功后只会调用一次onStartJob。onDestroy()同样继承自Service的,google对onDestroy的定义有这样一句话:Called by the system to notify a Service that it is no longer used and is being removed.即当系统想要回收当前服务时,便会触发onDestroy,至于间隔多久会触发取决于系统当前操作。我们可以在onDestroy()中重新执行一次创建操作,即重新通知系统安排一次计划任务。保证我们的JobScheduler始终存在于系统任务调度队列中了,这样任务就会持续的运行下去,具体操作如下:

    (1)创建一个JobService命名为DetectionService,在onCreated()中显式注册一个BroadcastReceiver,用来接收应用自身的广播或者其他系统广播,作为唤醒JobService的辅助方式。(毕竟还有安全软件这种东西的存在)

 
  1. @Override

  2. public void onCreate() {

  3. super.onCreate();

  4. receiver = new AuxiliaryReceiver();

  5. IntentFilter intentFilter = new IntentFilter();

  6. intentFilter.addAction("your app's broadcasts");

  7. intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED);

  8. registerReceiver(receiver, intentFilter);

  9. }

(2)在onDestroy()中执行重新创建当前JobService操作,不设定约束条件。

 
  1. @Override

  2. public void onDestroy() {

  3. super.onDestroy();

  4. unregisterReceiver(receiver);

  5. JobScheduler scheduler = null;

  6. if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){

  7. scheduler = this.getSystemService(JobScheduler.class);

  8. }else{

  9. scheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);

  10. }

  11. final JobInfo.Builder builder = new JobInfo.Builder(1024,new ComponentName(this, DetectionService.class));

  12. builder.setPersisted(true);

  13. builder.setOverrideDeadline(1000);

  14. scheduler.schedule(builder.build());

  15. }

(3)在onStartJob()中,你可以根据自己的需求安排具体操作。

  这种设计方式,可以实现类似Service常驻后台的效果,同时符合Android O中对后台运行的要求,但是会对省电模式有影响,如果你的APP没有这方面的顾虑话可以采取这种方式,如果有省电相关的需求,那就老老实实的只用JobScheduler启动一次。google通过JobScheduler规范后台应用启动的目的,就是为了保证设备的正常休眠和应用功耗的可控,我们在开发过程中更多的去遵守google标准,才能让用户有更好的产品体验。

JobScheduler应用场景

当你需要在Android设备满足某种场合才需要去执行处理数据,例如 
* 应用具有您可以推迟的非面向用户的工作(定期数据库数据更新) 
* 应用具有当插入设备时您希望优先执行的工作(充电时才希望执行的工作备份数据) 
* 需要访问网络或 Wi-Fi 连接的任务(如向服务器拉取内置数据) 
* 希望作为一个批次定期运行的许多任务

而使用JobScheduler可以很优雅的完成这些情况。

所以相比于其他方式,JobScheduler的好处是显而易见的。 
* 避免频繁的唤醒硬件模块,造成不必要的电量消耗。 
* 避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量;

JobScheduler和Android 6.0出现的Doze都一样,总结来说就是限制应用频繁唤醒硬件,从而达到省电的效果。

You do not instantiate this class directly; instead, retrieve it throughContext.getSystemService(Context.JOB_SCHEDULER_SERVICE).

Instances of this class must be obtained using Context.getSystemService(Class) with the argument JobScheduler.class or Context.getSystemService(String) with the argument Context.JOB_SCHEDULER_SERVICE.

官方demo git地址:

https://github.com/googlesamples/android-JobScheduler

任务调度机制由三个工具组成,首先是JobInfo,它指定了一个任务的概要信息,比如何时启动,启动时需要满足什么条件等等;其次是JobScheduler,它是系统提供的任务调度服务,它的实例从系统服务Context.JOB_SCHEDULER_SERVICE中获得;最后是JobService,它描述了该任务内部的具体业务逻辑,它的运行时刻由JobScheduler根据JobInfo指定的条件而计算决定。下面分别说明这三个工具的编码过程:

JobInfo

任务信息的运行条件由JobInfo.Builder来构造,下面是Builder的函数说明:
构造函数:指定该任务来源与目的,与Intent类似,第二个参数指定了开发者自定义的JobService。
setRequiredNetworkType:设置需要的网络条件,有三个取值:JobInfo.NETWORK_TYPE_NONE(无网络时执行,默认)、JobInfo.NETWORK_TYPE_ANY(有网络时执行)、JobInfo.NETWORK_TYPE_UNMETERED(网络无需付费时执行)
setPersisted:重启后是否还要继续执行,此时需要声明权限RECEIVE_BOOT_COMPLETED,否则会报错“java.lang.IllegalArgumentException: Error: requested job be persisted without holding RECEIVE_BOOT_COMPLETED permission.”而且RECEIVE_BOOT_COMPLETED需要在安装的时候就要声明,如果一开始没声明,而在升级时才声明,那么依然会报权限不足的错误。
setRequiresCharging:是否在充电时执行
setRequiresDeviceIdle:是否在空闲时执行
setPeriodic:设置时间间隔,单位毫秒。该方法不能和setMinimumLatency、setOverrideDeadline这两个同时调用,否则会报错“java.lang.IllegalArgumentException: Can't call setMinimumLatency() on a periodic job”,或者报错“java.lang.IllegalArgumentException: Can't call setOverrideDeadline() on a periodic job”。
setMinimumLatency:设置至少延迟多久后执行,单位毫秒。
setOverrideDeadline:设置最多延迟多久后执行,单位毫秒。
build:完成条件设置,返回构建好的JobInfo对象。

 

JobScheduler

任务调度的实例从系统服务Context.JOB_SCHEDULER_SERVICE中获得,代码举例如下:

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

获得任务调度实例后,即可进行任务调度操作,下面是任务调度的相关方法:
schedule:把指定的JobInfo对象放入调度队列,并在条件满足时触发该对象中定义的JobService。
cancel:取消指定编号的任务。
cancelAll:取消所有任务。
getAllPendingJobs:获取所有挂起(即尚未执行)的任务。

 

JobService

任务服务是一种特殊的Service,它描述了该任务内部的具体业务逻辑,需要开发者重写的方法如下:
onStartJob:在任务开始执行时触发。返回false表示执行完毕,返回true表示需要开发者自己调用jobFinished方法通知系统已执行完成。
onStopJob:在任务停止执行时触发。

JobService内部另外实现了两个方法,说明如下

1、onBind方法,源码如下所示

 
  1. public final IBinder onBind(Intent intent) {

  2. return mBinder.asBinder();

  3. }

JobService实现了onBind方法,表示任务调度在工作的时候,JobService是通过绑定方式启动的。

2、jobFinished方法,源码如下所示

 
  1. public final void jobFinished(JobParameters params, boolean needsReschedule) {

  2. ensureHandler();

  3. Message m = Message.obtain(mHandler, MSG_JOB_FINISHED, params);

  4. m.arg2 = needsReschedule ? 1 : 0;

  5. m.sendToTarget();

  6. }

因为JobService由系统触发,不是在App的主线程中,所以这里通过Message机制与主线程进行通信。

通俗的来讲,Jobscheduler也是通过Jobservice服务和JobInfo搭配来完成我们特定情况下需要完成的各种工作的一个新组件。和传统的service相比较,(从上边也可以了解到)它具有更优秀的表现,不仅对系统环境的友好,同时在特定的预置条件下进行我们期望的工作也节省了手机的电量,节约了系统资源。下边先了解下Jobservice和JobInfo。之后在完成搭配使用的用例以及应用衍生。

Jobscheduler的使用流程大概分为以下四个部分:

  • 派生JobService 子类,定义需要执行的任务(UI线程)
  • 从Context 中获取JobScheduler 实例(相当于管理器)
  • 构建JobInfo 实例,指定 JobService任务实现类及其执行条件
  • 通过JobScheduler 实例加入到任务队列

· 1 自定义Jobservice

java.lang.Object ↳ android.content.Context ↳ android.content.ContextWrapper ↳ android.app.Service ↳ android.app.job.JobService

JobService是从Service中扩展出来的一个新类,继承Service。但是有两个重要的新接口。

下边使用用例代码来讲解:

public class JobSchedulerService extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        // 返回true,表示该工作耗时,同时工作处理完成后需要调用onStopJob销毁(jobFinished)
        // 返回false,任务运行不需要很长时间,到return时已完成任务处理
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // 有且仅有onStartJob返回值为true时,才会调用onStopJob来销毁job
        // 返回false来销毁这个工作
        return false;
    }
}  

1、OnStartJob(JobParameters params)

当开始一个任务时,onstartjob(jobparameters params) 是必须使用的方法,因为它是系统用来触发已经安排的工作(job)的。
从上边的用例代码可以看到,该方法返回一个布尔值。不同的返回值对应了不同的处理方式。

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

2、JobFinished

void jobFinished (JobParameters params, boolean needsReschedule)

Callback to inform the JobManager you've finished executing. This can be called from any thread, as it will ultimately be run on your application's main thread. When the system receives this message it will release the wakelock being held.
回调通知已完成执行的JobManager。这可以从任何线程调用,因为它最终将在应用程序的主线程上运行。当系统收到该消息时,它将释放正在保存的唤醒。

  • JobParameters params
    -- onStartJob(JobParameters).
    传入的param需要和onStartJob中的param一致

  • boolean needsReschedule
    -- True if this job should be rescheduled according to the back-off criteria specified at schedule-time. False otherwise.
    如果这项工作应按照计划时间指定的停止条件进行重新安排,则传入true。 否则的话传入false。
    说人话就是让系统知道这个任务是否应该在最初的条件下被重复执行(稍后会介绍这个布尔值的用处)

3、OnStopJob(JobParameters params)

当收到取消请求时,onStopJob(JobParameters params)是系统用来取消挂起的任务的。
重要的是要注意到,如果onStartJob(JobParameters params)返回 false,当取消请求被接收时,该系统假定没有目前运行的工作。换句话说,它根本就不调用onStopJob(JobParameters params)。那此时就需要我们手动调用

jobFinished (JobParameters params, boolean needsReschedule)方法了。

要注意的是,工作服务需要在应用程序的主线程上运行。这意味着,须使用另一个线程处理程序,或运行时间更长的任务异步任务用以不阻塞主线程。

简单的来讲,我们可以在上面JobSchedulerService类中创建一个Handler或者AsyncTask来处理需要进行的Job。

public class JobSchedulerService extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        // 返回true,表示该工作耗时,同时工作处理完成后需要调用jobFinished销毁
        mJobHandler.sendMessage(Message.obtain(mJobHandler, 1, params));
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        mJobHandler.removeMessages(1);
        return false;
    }
    
    // 创建一个handler来处理对应的job
    private Handler mJobHandler = new Handler(new Handler.Callback() {
        // 在Handler中,需要实现handleMessage(Message msg)方法来处理任务逻辑。
        @Override
        public boolean handleMessage(Message msg) {
            Toast.makeText(getApplicationContext(), "JobService task running", Toast.LENGTH_SHORT).show();
            // 调用jobFinished
            jobFinished((JobParameters) msg.obj, false);
            return true;
        }
    });
} 

当然一个异步任务也是可行的:

public class JobSchedulerService extends JobService {

    private JobParameters mJobParameters

    @Override
    public boolean onStartJob(JobParameters params) {
        // 返回true,表示该工作耗时,同时工作处理完成后需要调用jobFinished销毁
        mJobParameters = params;
        mTask.execute();
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }

    private AsyncTask<Void, Void, Void> mTask = new AsyncTask<Void, Void, Void>() {

        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            // TODO Auto-generated method stub
            Toast.makeText(wenfengService.this, "finish job", 1000).show();
            jobFinished(mJobParameters, true);
            super.onPostExecute(result);
        }
    } 
}

当任务完成时,需要调用

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

代码片段中,因为handler中的操作可能比onStartJob(JobParameters params)方法它可能需要更长的时间来完成。通过设置true返回值,让程序了解将手动调用

jobFinished(JobParameters params, boolean needsRescheduled)方法标记完成任务。

同时从上边的用例也可发现

jobFinished(JobParameters params, boolean needsRescheduled)布尔值是false,它让系统知道是否需要根据工作的最初要求重新编排工作(重复执行)。同时这个布尔值是非常有用,可以帮助我们解决如何处理由于其他问题(如一个失败的网络电话)而导致任务无法完成的情况。设置为true我们就可以重复(?参见下面的描述)的进行这个任务。

任务失败的情况有很多,例如下载失败了,例如下载过程wifi断掉了。
例如如果下载过程中,wifi断掉了,JobService会回调onStopJob函数,这是只需要把函数的返回值设置为true就可以了。当wifi重新连接后,JobService会重新回调onStartJob函数。
而如果下载失败了,例如上面的例子中的mJobHandler执行失败,怎么办呢?我们只需要在Handler的handleMessage中执行jobFinished(mJobParameters, true),这里的true代表任务要在wifi条件重新满足情况下重新调度。

4、 绑定服务:

在简单的完成以上发送toast的Java代码之后,同Service一样,需要在AndroidManifest.xml中添加一个Service节点让应用拥有绑定和使用这个JobService的权限。

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

· 2 创建JobScheduler对象

在完成JobSchedulerService的构建以及绑定Service节点之后,接下来进行的是如何与JobScheduler API交互。

1、创建一个JobScheduler

在Activity中我们通过getSystemService(Context.JOB_SCHEDULER_SERVICE)实例化一个mJobScheduler的JobScheduler对象。

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

创建定时任务时,可以使用JobInfo.Builder来构建一个JobInfo对象,然后传递给JobService。

JobInfo.Builder builder = new JobInfo.Builder(jobId, 
new ComponentName(getPackageName(), JobSchedulerService.class.getName()));

JobInfo

Constants

  • BACKOFF_POLICY_EXPONENTIAL 、BACKOFF_POLICY_LINEAR
    与setBackoffCriteria (long initialBackoffMillis, int backoffPolicy)中backoffPolicy对应

  • DEFAULT_INITIAL_BACKOFF_MILLIS
    默认的执行延迟时间

  • MAX_BACKOFF_DELAY_MILLIS
    最大的执行延迟时间

  • NETWORK_TYPE_ANY
    网络状态联网就行setRequiredNetworkType

  • NETWORK_TYPE_NONE
    默认的网络连接状态setRequiredNetworkType

  • NETWORK_TYPE_NOT_ROAMING
    移动网络连接情况setRequiredNetworkType

  • NETWORK_TYPE_UNMETERED
    WIFI连接情况setRequiredNetworkType

▼▼▼▼重点来了!!!▼▼▼▼

JobInfo.Builder

JobInfo.Builder(int jobId, ComponentName jobService)
Initialize a new Builder to construct a JobInfo.

JobInfo.Builder接收两个参数

  • jobId : 要运行的任务的标识符
  • jobService : Service组件的类名。

下面简要的介绍部分builder中的方法:(截止Android API 25)

  • addTriggerContentUri(JobInfo.TriggerContentUri uri):添加一个TriggerContentUri,该Uri将利用ContentObserver来监控一个Content Uri,当且仅当其发生变化时将触发任务的执行。为了持续监控content的变化,你需要在最近的任务触发后再调度一个新的任务(需要注意的是触发器URI不能与setPeriodic(long)setPersisted(boolean)组合使用。要持续监控内容更改,需要在完成JobService处理最近的更改之前,调度新的JobInfo,观察相同的URI。因为设置此属性与定期或持久化Job不兼容,这样做会在调用build()时抛出IllegalArgumentException异常。)

  • setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)特殊:设置回退/重试的策略,详细的可以参阅Google API。
    类似网络原理中的冲突退避,当一个任务的调度失败时需要重试,所采取的策略。第一个参数时第一次尝试重试的等待间隔,单位为毫秒,预设的参数有:DEFAULT_INITIAL_BACKOFF_MILLIS 30000MAX_BACKOFF_DELAY_MILLIS 18000000。第二个参数是对应的退避策略,预设的参数有:BACKOFF_POLICY_EXPONENTIAL 二进制退避。等待间隔呈指数增长 BACKOFF_POLICY_LINEAR
  • setExtras(PersistableBundle extras):设置可选附件。这是持久的,所以只允许原始类型。

  • setMinimumLatency(long minLatencyMillis): 这个函数能用以设置任务的延迟执行时间(毫秒),相当于post delay。

  • setOverrideDeadline(long maxExecutionDelayMillis): 这个方法让用以设置任务最晚的延迟时间
    。如果到了规定的时间时其他条件还未满足,任务也会被启动。

  • setPeriodic(long time):设置任务运行的周期(每X毫秒,运行一次)。衍生在运行一些权限检查的时候如果可以使用job scheduler的话,可以这样来循环检查权限的开启,但是目前的触发条件没有权限部分的触发

  • setPeriodic(long intervalMillis, long flexMillis):设置在Job周期末的一个flex长度的窗口,任务都有可能被执行 require API LEVEL 24

  • setPersisted(boolean isPersisted): 这个方法告诉系统当设备重启之后任务是否还要继续执行

  • setRequiredNetworkType(int networkType): 这个方法让这个任务只有在满足指定的网络条件时才会被执行。默认条件是JobInfo.NETWORK_TYPE_NONE,这意味着不管是否有网络这个任务都会被执行。另外两个可选类型,一种是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一种网络才使得任务可以执行。另一种是JobInfo.NETWORK_TYPE_UNMETERED,它表示设备不是蜂窝网络( 比如在WIFI连接时 )时任务才会被执行。

  • setRequiresCharging(boolean requiresCharging): 只有当设备在充电时这个任务才会被执行。这个也并非只是插入充电器,而且还要在电池处于健康状态的情况下才会触发,一般来说是手机电量>15%

  • setRequiresDeviceIdle(boolean requiresDeviceIdle):指定Job在空闲状态才能运行。设备处于屏幕关闭或dreaming状态(类似window的休眠动画状态)71分钟后,执行工作

  • setTransientExtras(Bundle extras):设置可选的临时附加功能。(Android O Developer Preview)这里指定了需要Android O 系统,可以看出,Google还是在继续完善job scheduler的,期待能够代替传统的service组件成为主流。

  • setTriggerContentMaxDelay(long durationMs):设置从第一次检测到内容更改到Job之前允许的最大总延迟(以毫秒为单位)。说人话就是设置从content变化到任务被执行,中间的最大延迟同样的require API 24

  • setTriggerContentUpdateDelay(long durationMs):设置从content变化到任务被执行中间的延迟。如果在延迟期间content发生了变化,延迟会重新计算

  • setExtras(PersistableBundle extra):Bundle

要注意的是:
1、
设置延迟时间 setMinimumLatency(long minLatencyMillis)设置最终期限时间 setOverrideDeadline(long maxExecutionDelayMillis)的两个方法不能同时与setPeriodic(long time)同时设置,也就是说,在设置延迟和最终期限时间时是不能设置重复周期时间的。还有在具体开发过程中需要注意各个方法的API兼容情况。

2、setRequiredNetworkType(int networkType), setRequiresCharging(boolean requireCharging)setRequiresDeviceIdle(boolean requireIdle)这几个方法可能会使得任务无法执行,除非调用setOverrideDeadline(long time)设置了最大延迟时间,使得任务在为满足条件的情况下也会被执行。

构建一个JobInfo对象设置预置的条件,然后通过如下所示的代码将它发送到的JobScheduler中。

开启一个JobScheduler任务:

mJobScheduler.schedule(JobInfo job)

在schedule时,会返回一个int类型的值来标记这次任务是否执行成功,如果返回小于0的错误码,这表示该次任务执行失败,反之则成功(成功会返回该任务的id,这里可以使用这个id来判断哪些任务成功了)。所以在返回值小于0的时候就需要我们手动去处理一些事情了。

if(mJobScheduler.schedule(JobInfo job) < 0){
    // do something when schedule goes wrong
}

最后如果需要停止一个任务,就通过JobScheduler中,cancel(int jobId)来实现(所以之前在Builer中的指定id又有了重要作用);如果想取消所有的任务,可以调用JobScheduler对象的cancelAll()来实现。

推荐运行官方的Demo,简单易懂

具体运行推荐下载Github上的Demo运行,跑跑看一目了然。

如果偷偷懒可以看看下面的逻辑层代码,已经写上注释能够更好理解。

Activity代码

import android.app.Activity;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.PersistableBundle;
import android.support.annotation.ColorRes;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;

import com.example.android.jobscheduler.service.MyJobService;

import java.lang.ref.WeakReference;
import java.util.List;


/**
 * Schedules and configures jobs to be executed by a {@link JobScheduler}.
 * <p>
 * {@link MyJobService} can send messages to this via a {@link Messenger}
 * that is sent in the Intent that starts the Service.
 *
 *
 * 计划和配置要由{@link JobScheduler}执行的作业。
 *
 * {@link MyJobService}可以通过{@link Messenger}向其发送消息
 * 在启动服务的Intent中发送。
 */
public class MainActivity extends Activity {

    private static final String TAG = MainActivity.class.getSimpleName();
    //消息
    public static final int MSG_UNCOLOR_START = 0;
    public static final int MSG_UNCOLOR_STOP = 1;
    public static final int MSG_COLOR_START = 2;
    public static final int MSG_COLOR_STOP = 3;

    public static final String MESSENGER_INTENT_KEY
            = BuildConfig.APPLICATION_ID + ".MESSENGER_INTENT_KEY";
    public static final String WORK_DURATION_KEY =
            BuildConfig.APPLICATION_ID + ".WORK_DURATION_KEY";

    private EditText mDelayEditText;
    private EditText mDeadlineEditText;
    private EditText mDurationTimeEditText;
    private RadioButton mWiFiConnectivityRadioButton;
    private RadioButton mAnyConnectivityRadioButton;
    private CheckBox mRequiresChargingCheckBox;
    private CheckBox mRequiresIdleCheckbox;

    private ComponentName mServiceComponent;

    private int mJobId = 0;

    // Handler for incoming messages from the service.
    // 用于来自服务的传入消息的处理程序。
    private IncomingMessageHandler mHandler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample_main);

        // Set up UI.
        // 设置UI
        mDelayEditText = (EditText) findViewById(R.id.delay_time);
        mDurationTimeEditText = (EditText) findViewById(R.id.duration_time);
        mDeadlineEditText = (EditText) findViewById(R.id.deadline_time);
        mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered);
        mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any);
        mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging);
        mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle);
        mServiceComponent = new ComponentName(this, MyJobService.class);

        mHandler = new IncomingMessageHandler(this);
    }

    @Override
    protected void onStop() {
        // A service can be "started" and/or "bound". In this case, it's "started" by this Activity
        // and "bound" to the JobScheduler (also called "Scheduled" by the JobScheduler). This call
        // to stopService() won't prevent scheduled jobs to be processed. However, failing
        // to call stopService() would keep it alive indefinitely.

        // 服务可以是“开始”和/或“绑定”。 在这种情况下,它由此Activity“启动”
        // 和“绑定”到JobScheduler(也被JobScheduler称为“Scheduled”)。
        // 对stopService()的调用不会阻止处理预定作业。
        // 然而,调用stopService()失败将使它一直存活。
        stopService(new Intent(this, MyJobService.class));
        super.onStop();
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Start service and provide it a way to communicate with this class.
        // 启动服务并提供一种与此类通信的方法。
        Intent startServiceIntent = new Intent(this, MyJobService.class);
        Messenger messengerIncoming = new Messenger(mHandler);
        startServiceIntent.putExtra(MESSENGER_INTENT_KEY, messengerIncoming);
        startService(startServiceIntent);
    }

    /**
     * Executed when user clicks on SCHEDULE JOB.
     *
     * 当用户单击SCHEDULE JOB时执行。
     */
    public void scheduleJob(View v) {
        //开始配置JobInfo
        JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);

        //设置任务的延迟执行时间(单位是毫秒)
        String delay = mDelayEditText.getText().toString();
        if (!TextUtils.isEmpty(delay)) {
            builder.setMinimumLatency(Long.valueOf(delay) * 1000);
        }
        //设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,你的任务也会被启动。
        String deadline = mDeadlineEditText.getText().toString();
        if (!TextUtils.isEmpty(deadline)) {
            builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
        }
        boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
        boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();

        //让你这个任务只有在满足指定的网络条件时才会被执行
        if (requiresUnmetered) {
            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);

        // Schedule job
        Log.d(TAG, "Scheduling job");
        JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        tm.schedule(builder.build());

        //tm.schedule(builder.build())会返回一个int类型的数据
        //如果schedule方法失败了,它会返回一个小于0的错误码。否则它会返回我们在JobInfo.Builder中定义的标识id。
    }

    /**
     * Executed when user clicks on CANCEL ALL.
     *
     * 当用户点击取消所有时执行
     */
    public void cancelAllJobs(View v) {
        JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        tm.cancelAll();
        Toast.makeText(MainActivity.this, R.string.all_jobs_cancelled, Toast.LENGTH_SHORT).show();
    }

    /**
     * Executed when user clicks on FINISH LAST TASK.
     *
     * 当用户点击取消上次任务时执行
     */
    public void finishJob(View v) {
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        List<JobInfo> allPendingJobs = jobScheduler.getAllPendingJobs();
        if (allPendingJobs.size() > 0) {
            // Finish the last one
            int jobId = allPendingJobs.get(0).getId();
            jobScheduler.cancel(jobId);
            Toast.makeText(
                    MainActivity.this, String.format(getString(R.string.cancelled_job), jobId),
                    Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(
                    MainActivity.this, getString(R.string.no_jobs_to_cancel),
                    Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 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.
     *
     *
     * {@link Handler}允许您发送与线程相关联的消息。
     * {@link Messenger}使用此处理程序从{@link MyJobService}进行通信。
     * 它也用于使开始和停止视图在短时间内闪烁。
     */
    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.
                 *
                 * 当作业登录到应用程序时,从服务接收回调。 打开指示灯(上方View闪烁)并发送一条消息,在一秒钟后将其关闭。
                 */
                case MSG_COLOR_START:
                    // Start received, turn on the indicator and show text.
                    // 开始接收,打开指示灯(上方View闪烁)并显示文字。
                    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;
            }
        }

        /**
         * 更新UI显示
         * @param jobId jobId
         * @param action 消息
         */
        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);
        }
    }
}

JobService代码

import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;


import static com.example.android.jobscheduler.MainActivity.MESSENGER_INTENT_KEY;
import static com.example.android.jobscheduler.MainActivity.MSG_COLOR_START;
import static com.example.android.jobscheduler.MainActivity.MSG_COLOR_STOP;
import static com.example.android.jobscheduler.MainActivity.WORK_DURATION_KEY;


/**
 * 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.
 *
 *
 * 服务处理来自JobScheduler的回调。 使用JobScheduler计划的请求最终将回调在此服务的“onStartJob”方法上。
 * 它运行作业特定的时间并完成它们。 它通过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()"
     *
     * 当应用程序的MainActivity被创建时,它启动这个服务。
     * 这是为了使活动和此服务可以来回通信。 请参见“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处理程序来延迟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.
        // 返回true,很多工作都会执行这个地方
        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.
        // 返回false来销毁这个工作
        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.

        // 如果此服务由JobScheduler启动,则没有回调Messenger。
        // 它仅在MainActivity在Intent中使用回调函数调用startService()时存在。
        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.");
        }
    }
}

猜你喜欢

转载自blog.csdn.net/kdsde/article/details/82144801
今日推荐