Android開發筆記(一百四十三)任務排程JobScheduler
阿新 • • 發佈:2019-01-03
任務排程
App除了通過螢幕向用戶展示可互動的介面元素之外,還經常需要在後臺做些背地裡做的事情,比如說精密計算、檔案下載、統計分析、資料匯入、狀態監控等等,這些使用者看不到的事一般放在Service中處理。然而有時候我們希望在特定情況下再啟動事務,比如說延遲若干時間之後,或者等手機空閒了再執行,這樣一方面不會在系統資源緊張之時喧賓奪主,另一方面也起到削峰填谷提高系統效率的作用。對於這些額外的條件要求,Service並不能直接支援,往往需要加入其他手段,才能較好地滿足相關的執行條件,比如:
一、對於延遲時間執行,通常考慮利用系統的鬧鐘管理器AlarmManager進行定時管理,有關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
建構函式:指定該任務來源與目的,與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
JobScheduler js = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
獲得任務排程例項後,即可進行任務排程操作,下面是任務排程的相關方法:schedule:把指定的JobInfo物件放入排程佇列,並在條件滿足時觸發該物件中定義的JobService。
cancel:取消指定編號的任務。
cancelAll:取消所有任務。
getAllPendingJobs:獲取所有掛起(即尚未執行)的任務。
JobService
任務服務是一種特殊的Service,它描述了該任務內部的具體業務邏輯,需要開發者重寫的方法如下:onStartJob:在任務開始執行時觸發。返回false表示執行完畢,返回true表示需要開發者自己呼叫jobFinished方法通知系統已執行完成。
onStopJob:在任務停止執行時觸發。
JobService內部另外實現了兩個方法,說明如下
1、onBind方法,原始碼如下所示
public final IBinder onBind(Intent intent) {
return mBinder.asBinder();
}
JobService實現了onBind方法,表示任務排程在工作的時候,JobService是通過繫結方式啟動的。2、jobFinished方法,原始碼如下所示
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進行排程,具體程式碼如下所示: //將任務作業傳送到作業排程中去
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程式碼例子如下所示:
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中補充服務宣告:
<service
android:name=".service.SimpleJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
在Service內部進行排程
如果Activity通過常規的startService方法啟動JobService,那麼就得JobService自己在onStartCommand方法中進行任務排程了。除了對onStartCommand的處理存在區別之外,其它程式碼與上一種方式基本相同,完整的JobService程式碼如下所示: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中補充服務宣告:
<service
android:name=".service.MultiJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
點此檢視Android開發筆記的完整目錄