天天看點

Android 任務排程JobScheduler任務排程啟動方式

任務排程

App除了通過螢幕向使用者展示可互動的界面元素之外,還經常需要在背景做些背地裡做的事情,比如說精密計算、檔案下載下傳、統計分析、資料導入、狀态監控等等,這些使用者看不到的事一般放在Service中處理。

然而有時候我們希望在特定情況下再啟動事務,比如說延遲若幹時間之後,或者等手機空閑了再運作,這樣一方面不會在系統資源緊張之時喧賓奪主,另一方面也起到削峰填谷提高系統效率的作用。對于這些額外的條件要求,Service并不能直接支援,往往需要加入其他手段,才能較好地滿足相關的運作條件,比如:

一、對于延遲時間執行,通常考慮利用系統的鬧鐘管理器AlarmManager進行定時管理,有關AlarmManager的說明參見《 Android開發筆記(五十)定時器AlarmManager》。

二、對于是否聯網、是否充電、是否空閑,一般要監聽系統的相應廣播,常見的系統廣播說明如下:

1、網絡狀态變化需要監聽系統廣播android.net.conn.CONNECTIVITY_CHANGE;

2、裝置是否充電需要監聽系統廣播Intent.ACTION_POWER_CONNECTED也就是android.intent.action.ACTION_POWER_CONNECTED;

3、裝置是否空閑需要監聽系統廣播Intent.ACTION_SCREEN_OFF也就是android.intent.action.SCREEN_OFF;

可是要想給Service補充以上條件,勢必加大了程式邏輯的複雜度,一會兒注冊這個事件,一會兒注冊那個事件,工程代碼将變得不易維護。有鑒于此,Android從5.0開始,增加支援一種特殊的機制,即任務排程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中獲得,代碼舉例如下:

[java] view plain copy

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

獲得任務排程執行個體後,即可進行任務排程操作,下面是任務排程的相關方法:

schedule:把指定的JobInfo對象放入排程隊列,并在條件滿足時觸發該對象中定義的JobService。

cancel:取消指定編号的任務。

cancelAll:取消所有任務。

getAllPendingJobs:擷取所有挂起(即尚未執行)的任務。

JobService

任務服務是一種特殊的Service,它描述了該任務内部的具體業務邏輯,需要開發者重寫的方法如下:

onStartJob:在任務開始執行時觸發。傳回false表示執行完畢,傳回true表示需要開發者自己調用jobFinished方法通知系統已執行完成。

onStopJob:在任務停止執行時觸發。

JobService内部另外實作了兩個方法,說明如下

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

[java] view plain copy

  1. public final IBinder onBind(Intent intent) {  
  2.     return mBinder.asBinder();  
  3. }  

JobService實作了onBind方法,表示任務排程在工作的時候,JobService是通過綁定方式啟動的。

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

[java] view plain copy

  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機制與主線程進行通信。

啟動方式

由于JobService繼承自Service,是以既可以把它當作專門的排程服務來啟動,也可以把它當作普通的服務來啟動。

在Service外部進行排程

在Activity代碼中增加任務排程,需要聲明JobInfo對象,并通過JobScheduler進行排程,具體代碼如下所示:

[java] view plain copy

  1. //将任務作業發送到作業排程中去  
  2. public void scheduleJob() {  
  3.     Log.d(TAG, "scheduleJob");  
  4.     JobInfo.Builder builder = new JobInfo.Builder(0,   
  5.             new ComponentName(this, SimpleJobService.class));  
  6.     //設定需要的網絡條件,預設為JobInfo.NETWORK_TYPE_NONE即無網絡時執行  
  7.     //NETWORK_TYPE_NONE  
  8.     builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);  
  9.     //builder.setPersisted(true); //重新開機後是否還要繼續執行  
  10.     builder.setRequiresCharging(false); //是否在充電時執行  
  11.     builder.setRequiresDeviceIdle(false); //是否在空閑時執行  
  12.     //builder.setPeriodic(1000); //設定時間間隔,機關毫秒  
  13.     //setPeriodic不能和setMinimumLatency、setOverrideDeadline這兩個同時調用  
  14.     //否則會報錯“java.lang.IllegalArgumentException: Can't call setMinimumLatency() on a periodic job”  
  15.     //“java.lang.IllegalArgumentException: Can't call setOverrideDeadline() on a periodic job”  
  16.     builder.setMinimumLatency(500); //設定至少延遲多久後執行,機關毫秒  
  17.     builder.setOverrideDeadline(3000); //設定最多延遲多久後執行,機關毫秒  
  18.     JobInfo ji = builder.build();  
  19.     JobScheduler js = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);  
  20.     js.schedule(ji);  
  21. }  

該方式用到的JobService代碼例子如下所示:

[java] view plain copy

  1. public class SimpleJobService extends JobService {  
  2. private final static String TAG = "SimpleJobService";  
  3. private Handler mHandler = new Handler() {  
  4.     @Override  
  5.     public void handleMessage(Message msg) {  
  6.         try {  
  7.             Thread.sleep(5000);  
  8.         } catch (InterruptedException e) {  
  9.             e.printStackTrace();  
  10.         }  
  11.         JobParameters param = (JobParameters) msg.obj;  
  12.         jobFinished(param, true);  
  13.         Log.d(TAG, "jobFinished");  
  14.     }  
  15. };  
  16. @Override  
  17. public int onStartCommand(Intent intent, int flags, int startId) {  
  18.     Log.d(TAG, "onStartCommand");  
  19.     return START_NOT_STICKY;  
  20. }  
  21. @Override  
  22. public boolean onStartJob(JobParameters params) {  
  23.     Log.d(TAG, "onStartJob");  
  24.     Message message = Message.obtain();  
  25.     message.obj = params;  
  26.     mHandler.sendMessage(message);  
  27.     //傳回false表示執行完畢,傳回true表示需要開發者自己調用jobFinished方法通知系統已執行完成  
  28.     return true;  
  29. }  
  30. @Override  
  31. public boolean onStopJob(JobParameters params) {  
  32.     //停止,不是結束。jobFinished不會直接觸發onStopJob  
  33.     //必須在“onStartJob之後,jobFinished之前”取消任務,才會在jobFinished之後觸發onStopJob  
  34.     Log.d(TAG, "onStopJob");  
  35.     mHandler.removeMessages(0);  
  36.     return true;  
  37. }  

以上代碼需要注意的是:

1、因為系統服務是通過綁定方式啟動JobService,是以此時onStartCommand方法永遠不會執行;

2、onStopJob顧名思義是在任務停止時觸發,但是直接調用jobFinished方法并不能觸發onStopJob。原因是onStopJob的觸發是有條件的,首先這裡的停止指的是取消任務而不是完成任務;其次必須在“onStartJob之後,jobFinished之前”取消任務,才會在jobFinished之後觸發onStopJob。

另外注意在AndroidManifest.xml中補充服務聲明:

[html] view plain copy

  1. <service  
  2.     android:name=".service.SimpleJobService"  
  3.     android:permission="android.permission.BIND_JOB_SERVICE" />  

在Service内部進行排程

如果Activity通過正常的startService方法啟動JobService,那麼就得JobService自己在onStartCommand方法中進行任務排程了。除了對onStartCommand的處理存在差別之外,其它代碼與上一種方式基本相同,完整的JobService代碼如下所示:

[java] view plain copy

  1. public class MultiJobService extends JobService {  
  2.     private final static String TAG = "MultiJobService";  
  3.     private Handler mHandler = new Handler() {  
  4.         @Override  
  5.         public void handleMessage(Message msg) {  
  6.             JobParameters param = (JobParameters) msg.obj;  
  7.             jobFinished(param, true);  
  8.             Intent intent = new Intent(getApplicationContext(), MainActivity.class);  
  9.             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  10.             startActivity(intent);  
  11.         }  
  12.     };  
  13.     @Override  
  14.     public int onStartCommand(Intent intent, int flags, int startId) {  
  15.         Log.d(TAG, "onStartCommand");  
  16.         scheduleJob();  
  17.         return START_NOT_STICKY;  
  18.     }  
  19.     @Override  
  20.     public boolean onStartJob(JobParameters params) {  
  21.         Log.d(TAG, "onStartJob");  
  22.         Message message = Message.obtain();  
  23.         message.obj = params;  
  24.         mHandler.sendMessage(message);  
  25.         //傳回false表示執行完畢,傳回true表示需要開發者自己調用jobFinished方法通知系統已執行完成  
  26.         return true;  
  27.     }  
  28.     @Override  
  29.     public boolean onStopJob(JobParameters params) {  
  30.         Log.d(TAG, "onStopJob");  
  31.         mHandler.removeMessages(0);  
  32.         return true;  
  33.     }  
  34.     //将任務作業發送到作業排程中去  
  35.     public void scheduleJob() {  
  36.         Log.d(TAG, "scheduleJob");  
  37.         JobInfo.Builder builder = new JobInfo.Builder(0,   
  38.                 new ComponentName(this, MultiJobService.class));  
  39.       //設定需要的網絡條件,預設為JobInfo.NETWORK_TYPE_NONE即無網絡時執行  
  40.         builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);  
  41.         //重新開機後是否還要繼續執行,此時需要聲明權限RECEIVE_BOOT_COMPLETED  
  42.         //否則會報錯“java.lang.IllegalArgumentException: Error: requested job be persisted without holding RECEIVE_BOOT_COMPLETED permission.”  
  43.         //而且RECEIVE_BOOT_COMPLETED需要在安裝的時候就要聲明,如果一開始沒聲明,在更新時才聲明,那麼依然會報權限不足的錯誤  
  44.         builder.setPersisted(true);  
  45.         builder.setRequiresCharging(false); //是否在充電時執行  
  46.         builder.setRequiresDeviceIdle(false); //是否在空閑時執行  
  47.         builder.setPeriodic(1000); //設定時間間隔,機關毫秒  
  48.         JobInfo ji = builder.build();  
  49.         JobScheduler js = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);  
  50.         js.schedule(ji);  
  51.     }  
  52. }  

另外注意在AndroidManifest.xml中補充服務聲明:

[html] view plain copy

  1. <service  
  2.     android:name=".service.MultiJobService"  
  3.     android:permission="android.permission.BIND_JOB_SERVICE" />  

原文連結:https://blog.csdn.net/aqi00/article/details/71638721