任務排程
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
- 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
- public final IBinder onBind(Intent intent) {
- return mBinder.asBinder();
- }
JobService實作了onBind方法,表示任務排程在工作的時候,JobService是通過綁定方式啟動的。
2、jobFinished方法,源碼如下所示
[java] view plain copy
- public final void jobFinished(JobParameters params, boolean needsReschedule) {
- ensureHandler();
- Message m = Message.obtain(mHandler, MSG_JOB_FINISHED, params);
- m.arg2 = needsReschedule ? 1 : 0;
- m.sendToTarget();
- }
因為JobService由系統觸發,不是在App的主線程中,是以這裡通過Message機制與主線程進行通信。
啟動方式
由于JobService繼承自Service,是以既可以把它當作專門的排程服務來啟動,也可以把它當作普通的服務來啟動。
在Service外部進行排程
在Activity代碼中增加任務排程,需要聲明JobInfo對象,并通過JobScheduler進行排程,具體代碼如下所示:
[java] view plain copy
- //将任務作業發送到作業排程中去
- public void scheduleJob() {
- Log.d(TAG, "scheduleJob");
- JobInfo.Builder builder = new JobInfo.Builder(0,
- new ComponentName(this, SimpleJobService.class));
- //設定需要的網絡條件,預設為JobInfo.NETWORK_TYPE_NONE即無網絡時執行
- //NETWORK_TYPE_NONE
- builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
- //builder.setPersisted(true); //重新開機後是否還要繼續執行
- builder.setRequiresCharging(false); //是否在充電時執行
- builder.setRequiresDeviceIdle(false); //是否在空閑時執行
- //builder.setPeriodic(1000); //設定時間間隔,機關毫秒
- //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”
- builder.setMinimumLatency(500); //設定至少延遲多久後執行,機關毫秒
- builder.setOverrideDeadline(3000); //設定最多延遲多久後執行,機關毫秒
- JobInfo ji = builder.build();
- JobScheduler js = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
- js.schedule(ji);
- }
該方式用到的JobService代碼例子如下所示:
[java] view plain copy
- public class SimpleJobService extends JobService {
- private final static String TAG = "SimpleJobService";
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- JobParameters param = (JobParameters) msg.obj;
- jobFinished(param, true);
- Log.d(TAG, "jobFinished");
- }
- };
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.d(TAG, "onStartCommand");
- return START_NOT_STICKY;
- }
- @Override
- public boolean onStartJob(JobParameters params) {
- Log.d(TAG, "onStartJob");
- Message message = Message.obtain();
- message.obj = params;
- mHandler.sendMessage(message);
- //傳回false表示執行完畢,傳回true表示需要開發者自己調用jobFinished方法通知系統已執行完成
- return true;
- }
- @Override
- public boolean onStopJob(JobParameters params) {
- //停止,不是結束。jobFinished不會直接觸發onStopJob
- //必須在“onStartJob之後,jobFinished之前”取消任務,才會在jobFinished之後觸發onStopJob
- Log.d(TAG, "onStopJob");
- mHandler.removeMessages(0);
- return true;
- }
以上代碼需要注意的是:
1、因為系統服務是通過綁定方式啟動JobService,是以此時onStartCommand方法永遠不會執行;
2、onStopJob顧名思義是在任務停止時觸發,但是直接調用jobFinished方法并不能觸發onStopJob。原因是onStopJob的觸發是有條件的,首先這裡的停止指的是取消任務而不是完成任務;其次必須在“onStartJob之後,jobFinished之前”取消任務,才會在jobFinished之後觸發onStopJob。
另外注意在AndroidManifest.xml中補充服務聲明:
[html] view plain copy
- <service
- android:name=".service.SimpleJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
在Service内部進行排程
如果Activity通過正常的startService方法啟動JobService,那麼就得JobService自己在onStartCommand方法中進行任務排程了。除了對onStartCommand的處理存在差別之外,其它代碼與上一種方式基本相同,完整的JobService代碼如下所示:
[java] view plain copy
- public class MultiJobService extends JobService {
- private final static String TAG = "MultiJobService";
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- JobParameters param = (JobParameters) msg.obj;
- jobFinished(param, true);
- Intent intent = new Intent(getApplicationContext(), MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- }
- };
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.d(TAG, "onStartCommand");
- scheduleJob();
- return START_NOT_STICKY;
- }
- @Override
- public boolean onStartJob(JobParameters params) {
- Log.d(TAG, "onStartJob");
- Message message = Message.obtain();
- message.obj = params;
- mHandler.sendMessage(message);
- //傳回false表示執行完畢,傳回true表示需要開發者自己調用jobFinished方法通知系統已執行完成
- return true;
- }
- @Override
- public boolean onStopJob(JobParameters params) {
- Log.d(TAG, "onStopJob");
- mHandler.removeMessages(0);
- return true;
- }
- //将任務作業發送到作業排程中去
- public void scheduleJob() {
- Log.d(TAG, "scheduleJob");
- JobInfo.Builder builder = new JobInfo.Builder(0,
- new ComponentName(this, MultiJobService.class));
- //設定需要的網絡條件,預設為JobInfo.NETWORK_TYPE_NONE即無網絡時執行
- builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
- //重新開機後是否還要繼續執行,此時需要聲明權限RECEIVE_BOOT_COMPLETED
- //否則會報錯“java.lang.IllegalArgumentException: Error: requested job be persisted without holding RECEIVE_BOOT_COMPLETED permission.”
- //而且RECEIVE_BOOT_COMPLETED需要在安裝的時候就要聲明,如果一開始沒聲明,在更新時才聲明,那麼依然會報權限不足的錯誤
- builder.setPersisted(true);
- builder.setRequiresCharging(false); //是否在充電時執行
- builder.setRequiresDeviceIdle(false); //是否在空閑時執行
- builder.setPeriodic(1000); //設定時間間隔,機關毫秒
- JobInfo ji = builder.build();
- JobScheduler js = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
- js.schedule(ji);
- }
- }
另外注意在AndroidManifest.xml中補充服務聲明:
[html] view plain copy
- <service
- android:name=".service.MultiJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
原文連結:https://blog.csdn.net/aqi00/article/details/71638721