? ? ? ?WorkManager架構(gòu)組件是用來管理后臺工作任務(wù)。這個時候你可能會奇怪了Android不是已經(jīng) 有很多管理后臺任務(wù)的類了么,比如JobScheduler, AlarmManger、在比如AsyncTask, ThreadPool。WorkManager。WorkManager的優(yōu)勢在哪里,我們?yōu)樯兑褂肳orkManager。我們從兩個方面來說明WorkManager的優(yōu)勢
- WorkManager對比JobScheduler, AlarmManger的優(yōu)勢:我們要知道雖然AlarmManager是一直存在但是JobScheduler是Android 5.x之后才有的。WorkManager的底層實(shí)現(xiàn),會根據(jù)你的設(shè)備API的情況,自動選用JobScheduler, 或是AlarmManager來實(shí)現(xiàn)后臺任務(wù)。
- WorkManager對比AsyncTask, ThreadPool的優(yōu)勢:WorkManager里面的任務(wù)在應(yīng)用退出之后還可以繼續(xù)執(zhí)行。AsyncTask, ThreadPool里面的任務(wù)在應(yīng)用退出之后不會執(zhí)行。
? ? ? ?WorkManager適用于那些在應(yīng)用退出之后任務(wù)還需要繼續(xù)執(zhí)行的需求(比如應(yīng)用數(shù)據(jù)上報服務(wù)器的情況),對應(yīng)那些在應(yīng)用退出的之后任務(wù)也需要終止的情況就需要選擇ThreadPool、AsyncTask來實(shí)現(xiàn)。
一、WorkManager相關(guān)類介紹
? ? ? ?想使用WorkManager組件庫,第一步咱們得先了解下WorkManager里面相關(guān)的幾個類。
1.1、Worker
? ? ? ?Worker用于指定需要執(zhí)行的具體任務(wù)。任務(wù)的具體邏輯在Worker里面寫。
Worker是個抽象類。所以我們需要繼承并實(shí)現(xiàn)這個類在定義自己的任務(wù)。
? ? ? ?Worker類里面幾個比較關(guān)鍵的函數(shù):任務(wù)邏輯實(shí)現(xiàn)函數(shù),任務(wù)輸入數(shù)據(jù)的獲取函數(shù),任務(wù)輸出數(shù)據(jù)的設(shè)置函數(shù)。
/**
* 任務(wù)邏輯
* @return 任務(wù)的執(zhí)行情況,成功,失敗,還是需要重新執(zhí)行
*/
@WorkerThread
public abstract @NonNull Worker.Result doWork();
/**
* 任務(wù)的輸入數(shù)據(jù),有的時候可能需要我們傳遞參數(shù)進(jìn)去,比如下載文件我們需要傳遞文件路基進(jìn)去,
* 在doWork()函數(shù)中通過getInputData()獲取到我們傳遞進(jìn)來的參數(shù)
* @return Data參數(shù)
*/
public final @NonNull Data getInputData() {
return mExtras.getInputData();
}
/**
* 設(shè)置我們?nèi)蝿?wù)輸出結(jié)果
* @param outputData 結(jié)果
*/
public final void setOutputData(@NonNull Data outputData) {
mOutputData = outputData;
}
? ? ? ?doWork()函數(shù)的返回值:
- Worker.Result.SUCCESS:任務(wù)執(zhí)行成功。
- Worker.Result.FAILURE:任務(wù)執(zhí)行失敗。
- Worker.Result.RETRY:任務(wù)需要重新執(zhí)行,需要配合WorkRequest.Builder里面的setBackoffCriteria()函數(shù)使用。
1.2、WorkRequest
? ? ? ?WorkRequest代表一個單獨(dú)的任務(wù),是對Worker任務(wù)的包裝,一個WorkRequest對應(yīng)一個Worker類。我們可以通過WorkRequest來給Worker類添加約束細(xì)節(jié),比如指定任務(wù)應(yīng)該運(yùn)行的環(huán)境,任務(wù)的輸入?yún)?shù),任務(wù)只有在有網(wǎng)的情況下執(zhí)行等等。WorkRequest是一個抽象類,組件里面也給兩個相應(yīng)的子類:OneTimeWorkRequest(任務(wù)只執(zhí)行一遍)、PeriodicWorkRequest(任務(wù)周期性的執(zhí)行)。
- WorkRequest.Builder: 創(chuàng)建WorkRequest對象的幫助類。
- Constraints:指定任務(wù)運(yùn)行的限制條件(例如,"僅當(dāng)連接到網(wǎng)絡(luò)時")。使用Constraint.Builder來創(chuàng)建Constraints,并在創(chuàng)建WorkRequest之前把Constraints傳給WorkRequest.Builder的setConstraints()函數(shù)。
WorkRequest里面常用函數(shù)介紹
/**
* 獲取 WorkRequest對應(yīng)的UUID
*/
public @NonNull UUID getId();
/**
* 獲取 WorkRequest對應(yīng)的UUID string
*/
public @NonNull String getStringId();
/**
* 獲取WorkRequest對應(yīng)的WorkSpec(包含了任務(wù)的一些詳細(xì)信息)
*/
public @NonNull WorkSpec getWorkSpec();
/**
* 獲取 WorkRequest對應(yīng)的tag
*/
public @NonNull Set<String> getTags();
public abstract static class Builder<B extends WorkRequest.Builder, W extends WorkRequest> {
...
/**
* 設(shè)置任務(wù)的退避/重試策略。比如我們在Worker類的doWork()函數(shù)返回Result.RETRY,讓該任務(wù)又重新入隊。
*/
public @NonNull B setBackoffCriteria(
@NonNull BackoffPolicy backoffPolicy,
long backoffDelay,
@NonNull TimeUnit timeUnit);
/**
* 設(shè)置任務(wù)的運(yùn)行的限制條件,比如有網(wǎng)的時候執(zhí)行任務(wù),不是低電量的時候執(zhí)行任務(wù)
*/
public @NonNull B setConstraints(@NonNull Constraints constraints);
/**
* 設(shè)置任務(wù)的輸入?yún)?shù)
*/
public @NonNull B setInputData(@NonNull Data inputData);
/**
* 設(shè)置任務(wù)的tag
*/
public @NonNull B addTag(@NonNull String tag);
/**
* 設(shè)置任務(wù)結(jié)果保存時間
*/
public @NonNull B keepResultsForAtLeast(long duration, @NonNull TimeUnit timeUnit);
@RequiresApi(26)
public @NonNull B keepResultsForAtLeast(@NonNull Duration duration);
...
}
? ? ? ?這里要稍微提下Builder的setBackoffCriteria()函數(shù)的使用場景,比較常用,一般當(dāng)我們?nèi)蝿?wù)執(zhí)行失敗的時候任務(wù)需要重試的時候會用到這個函數(shù),在任務(wù)執(zhí)行失敗的時候Worker類的doWork()函數(shù)返回Result.RETRY告訴這個任務(wù)要重試。那重試的策略就是通過setBackoffCriteria()函數(shù)來設(shè)置的。BackoffPolicy有兩個值LINEAR(每次重試的時間線性增加,比如第一次10分鐘,第二次就是20分鐘)、EXPONENTIAL(每次重試時間指數(shù)增加)。
1.3、WorkManager
? ? ? ?管理任務(wù)請求和任務(wù)隊列,我們需要把WorkRequest對象傳給WorkManager以便將任務(wù)編入隊列。通過WorkManager來調(diào)度任務(wù),以分散系統(tǒng)資源的負(fù)載。
WorkManager常用函數(shù)介紹
/**
* 任務(wù)入隊
*/
public final void enqueue(@NonNull WorkRequest... workRequests);
public abstract void enqueue(@NonNull List<? extends WorkRequest> workRequests);
/**
* 鏈?zhǔn)浇Y(jié)構(gòu)的時候使用,從哪些任務(wù)開始。
* 比如我們有A,B,C三個任務(wù),我們需要順序執(zhí)行。那我們就可以WorkManager.getInstance().beginWith(A).then(B).then(C).enqueue();
*/
public final @NonNull WorkContinuation beginWith(@NonNull OneTimeWorkRequest...work);
public abstract @NonNull WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work);
/**
* 創(chuàng)建一個唯一的工作隊列,唯一工作隊列里面的任務(wù)不能重復(fù)添加
*/
public final @NonNull WorkContinuation beginUniqueWork(
@NonNull String uniqueWorkName,
@NonNull ExistingWorkPolicy existingWorkPolicy,
@NonNull OneTimeWorkRequest... work);
public abstract @NonNull WorkContinuation beginUniqueWork(
@NonNull String uniqueWorkName,
@NonNull ExistingWorkPolicy existingWorkPolicy,
@NonNull List<OneTimeWorkRequest> work);
/**
* 允許將一個PeriodicWorkRequest任務(wù)放到唯一的工作序列里面去,但是當(dāng)隊列里面有這個任務(wù)的時候你的提供替換的策略。
*/
public abstract void enqueueUniquePeriodicWork(
@NonNull String uniqueWorkName,
@NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
@NonNull PeriodicWorkRequest periodicWork);
/**
* 通過UUID取消任務(wù)
*/
public abstract void cancelWorkById(@NonNull UUID id);
/**
* 通過tag取消任務(wù)
*/
public abstract void cancelAllWorkByTag(@NonNull String tag);
/**
* 取消唯一隊列里面所有的任務(wù)(beginUniqueWork)
*/
public abstract void cancelUniqueWork(@NonNull String uniqueWorkName);
/**
* 取消所有的任務(wù)
*/
public abstract void cancelAllWork();
/**
* 獲取任務(wù)的WorkStatus。一般會通過WorkStatus來獲取返回值,LiveData是可以感知WorkStatus數(shù)據(jù)變化的
*/
public abstract @NonNull LiveData<WorkStatus> getStatusById(@NonNull UUID id);
public abstract @NonNull LiveData<List<WorkStatus>> getStatusesByTag(@NonNull String tag);
/**
* 獲取唯一隊列里面所有的任務(wù)(beginUniqueWork)的WorkStatus
*/
public abstract @NonNull LiveData<List<WorkStatus>> getStatusesForUniqueWork(@NonNull String uniqueWorkName);
? ? ? ?beginWith(),beginUniqueWork()兩個函數(shù)開啟的隊列的唯一區(qū)別在于,隊列里面的任務(wù)能不能重復(fù)。beginWith()開始的隊列里面的任務(wù)是可以重復(fù)的,beginUniqueWork()開始的隊列里面的任務(wù)是不能重復(fù)的。
1.4、WorkStatus
? ? ? ?包含任務(wù)的信息。WorkManager為每個WorkRequest對象提供一個LiveData(WorkManager通過getStatusById、getStatusesByTag、getStatusesForUniqueWork函數(shù)來獲取)。LiveData持有一個WorkStatus對象。LiveData是可以感知數(shù)據(jù)變化的。通過觀察這個LiveData,我們可以確定任務(wù)的當(dāng)前狀態(tài),并在任務(wù)完成后獲得返回值。WorkStatus里面就包含的東西不多就任務(wù)的id、tag、狀態(tài)、返回值。
通過如下方式來監(jiān)聽任務(wù)的狀態(tài)
// 獲取到LiveData然后監(jiān)聽數(shù)據(jù)變化
WorkManager.getInstance().getStatusById(request.getId()).observe(this, new Observer<WorkStatus>() {
@Override
public void onChanged(@Nullable WorkStatus workStatus) {
if (workStatus == null) {
return;
}
if (workStatus.getState() == State.ENQUEUED) {
mTextOut.setText("任務(wù)入隊");
}
if (workStatus.getState() == State.RUNNING) {
mTextOut.setText("任務(wù)正在執(zhí)行");
}
if (workStatus.getState().isFinished()) {
Data data = workStatus.getOutputData();
mTextOut.setText("任務(wù)完成" + "-結(jié)果:" + data.getString("key_name", "null"));
}
}
});
1.5、Data
? ? ? ?Data是用于來給Worker設(shè)置輸入?yún)?shù)和輸出參數(shù)的。舉個例子,比如我們需要去網(wǎng)絡(luò)上下載圖,那么需要給Worker傳入下載地址(輸入?yún)?shù)),在Worker執(zhí)行成功之后我們又需要獲取到圖片在本地的保持路徑(輸出參數(shù))。這這個傳入傳出都是通過Data來實(shí)現(xiàn)的。Data是一個輕量級的容器(不能超過10KB),Data通過key-value的形式來保存信息。
二、WorkManager使用
? ? ? ?前面講了WorkManager里面常用的一些類,接下來就是WorkManager的使用了。
? ? ? ?我們把WorkManager的使用分為幾個步驟:
- 繼承Worker,處理任務(wù)的具體邏輯。
- OneTimeWorkRequest或者PeriodicWorkRequest包裝Worker,設(shè)置Worker的一些約束添加,或者Worker的輸入?yún)?shù)。
- 任務(wù)入隊執(zhí)行(如果是多個任務(wù)可以形成任務(wù)鏈在入隊執(zhí)行)。
- 監(jiān)聽任務(wù)的輸出(LiveData的使用)。
2.1、任務(wù)的輸入輸出
? ? ? ?有些時候,一個任務(wù)的執(zhí)行,我們可能需要從外部傳入?yún)?shù),在任務(wù)結(jié)束的時候需要把任務(wù)的結(jié)果告訴外部。比如我們?nèi)ゾW(wǎng)絡(luò)上下載一個圖片,那咱們需要url地址(輸入),在圖片下載成功之后獲取圖片在本地的保存路徑(輸出)。
- 輸入?yún)?shù):想要給任務(wù)傳遞輸入?yún)?shù)需要在WorkRequest包裝Worker的通過WorkRequest.Builder的setInputData()函數(shù)設(shè)置輸入?yún)?shù)。之后任務(wù)在執(zhí)行過程中可以通過getInputData()獲取到傳入的參數(shù)。
- 輸出參數(shù):任務(wù)執(zhí)行過程中如果想要結(jié)果傳遞給外界,需要在Worker中通過setOutputData()設(shè)置輸出參數(shù)。之后如果想獲取任務(wù)結(jié)果需要通過WorkManager.getInstance().getStatusById()或者WorkManager.getInstance().getStatusesByTag()先獲取到LiveData。然后通過LiveData來感知任務(wù)數(shù)據(jù)的變化。
? ? ? ?我們通過一個簡單的實(shí)力來看下任務(wù)有輸入輸出的情況應(yīng)該怎么處理。
定義一個任務(wù)
public class InputOutputWorker extends Worker {
@NonNull
@Override
public Result doWork() {
try {
//模擬耗時任務(wù)
Thread.sleep(3000);
Data inputData = getInputData();
//獲取到輸入的參數(shù),我們又把輸入的參數(shù)給outputData
Data outputData = new Data.Builder().putString("key_name", inputData.getString("key_name", "no data")).build();
setOutputData(outputData);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Result.SUCCESS;
}
}
OneTimeWorkRequest設(shè)置任務(wù)的輸入,執(zhí)行任務(wù),任務(wù)的執(zhí)行過程中設(shè)置輸出,LiveData感知任務(wù)結(jié)果
private void startWorker() {
// 定義一個OneTimeWorkRequest,并且關(guān)聯(lián)InputOutputWorker。設(shè)置輸入?yún)?shù)
Data inputData = new Data.Builder().putString("key_name", "江西高安").build();
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(InputOutputWorker.class)
.setInputData(inputData)
.build();
// 任務(wù)入隊,WorkManager調(diào)度執(zhí)行
WorkManager.getInstance().enqueue(request);
// 獲取到LiveData然后監(jiān)聽數(shù)據(jù)變化
WorkManager.getInstance().getStatusById(request.getId()).observe(this, new Observer<WorkStatus>() {
@Override
public void onChanged(@Nullable WorkStatus workStatus) {
if (workStatus == null) {
return;
}
if (workStatus.getState() == State.ENQUEUED) {
mTextOut.setText("任務(wù)入隊");
}
if (workStatus.getState() == State.RUNNING) {
mTextOut.setText("任務(wù)正在執(zhí)行");
}
if (workStatus.getState().isFinished()) {
Data data = workStatus.getOutputData();
mTextOut.setText("任務(wù)完成" + "-結(jié)果:" + data.getString("key_name", "null"));
}
}
});
}
2.2、周期任務(wù)
? ? ? ?WorkManager組件庫里面提供了一個專門做周期性任務(wù)的類PeriodicWorkRequest。但是PeriodicWorkRequest類有一個限制條件最小的周期時間是15分鐘。
private void startWorker() {
// 定義一個PeriodicWorkRequest,并且關(guān)聯(lián)PeriodicWorker。任務(wù)15m循環(huán)(源碼里面已經(jīng)規(guī)定了最小時間間隔15分鐘)
PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(PeriodicWorker.class, 15, TimeUnit.MINUTES).build();
// 任務(wù)入隊,WorkManager調(diào)度執(zhí)行
WorkManager.getInstance().enqueue(request);
}
2.3、任務(wù)添加約束
? ? ? ?有些情況下,有些任務(wù)可能需要添加額外的約束,比如只能在聯(lián)網(wǎng)的情況下才能執(zhí)行,只有在設(shè)備空閑的情況下才能執(zhí)行等等。WorkManager里面所有的約束條件都是通過Constraints來實(shí)現(xiàn)的,Constraints也是通過Constraints.Builder()來實(shí)現(xiàn)的。
? ? ? ?關(guān)于任務(wù)的約束要注意,可能我們?nèi)蝿?wù)加入的那個時刻候沒有滿足約束的條件,任務(wù)沒有執(zhí)行。但是過后一旦約束條件滿足之后任務(wù)會自動執(zhí)行的。
Constraints常用函數(shù)-可以添加的限制如下
/**
* 是否在充電狀態(tài)下執(zhí)行任務(wù)
*/
public @NonNull Constraints.Builder setRequiresCharging(boolean requiresCharging);
/**
* 是否在設(shè)備空閑的時候執(zhí)行
*/
@RequiresApi(23)
public @NonNull Constraints.Builder setRequiresDeviceIdle(boolean requiresDeviceIdle);
/**
* 指定網(wǎng)絡(luò)狀態(tài)執(zhí)行任務(wù)
* NetworkType.NOT_REQUIRED:對網(wǎng)絡(luò)沒有要求
* NetworkType.CONNECTED:網(wǎng)絡(luò)連接的時候執(zhí)行
* NetworkType.UNMETERED:不計費(fèi)的網(wǎng)絡(luò)比如WIFI下執(zhí)行
* NetworkType.NOT_ROAMING:非漫游網(wǎng)絡(luò)狀態(tài)
* NetworkType.METERED:計費(fèi)網(wǎng)絡(luò)比如3G,4G下執(zhí)行。
*/
public @NonNull Constraints.Builder setRequiredNetworkType(@NonNull NetworkType networkType);
/**
* 在電量不足的是否是否可以執(zhí)行任務(wù)
*/
public @NonNull Constraints.Builder setRequiresBatteryNotLow(boolean requiresBatteryNotLow);
/**
* 在存儲容量不足時是否可以執(zhí)行
*/
public @NonNull Constraints.Builder setRequiresStorageNotLow(boolean requiresStorageNotLow);
/**
* 當(dāng)Uri有更新的時候是否執(zhí)行任務(wù)
*/
@RequiresApi(24)
public @NonNull Constraints.Builder addContentUriTrigger(Uri uri, boolean triggerForDescendants);
? ? ? ?我們舉一個簡單的例子,比如我們限制任務(wù)只有在wifi的狀態(tài)下才能執(zhí)行。
/**
* 啟動約束任務(wù)
*/
private void startWorker() {
// 設(shè)置只有在wifi狀態(tài)下才能執(zhí)行
Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED).build();
// 設(shè)置約束條件
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(ConstraintsWorker.class).setConstraints(constraints).build();
// 任務(wù)入隊,WorkManager調(diào)度執(zhí)行
WorkManager.getInstance().enqueue(request);
}
2.3、任務(wù)取消
? ? ? ?每個任務(wù)都有自己獨(dú)特的UUID,我們可以通過任務(wù)的UUID找到任務(wù),然后取消他。除了UUID的方式,我們還可以給任務(wù)添加tag,然后通過tag來取消任務(wù)(可以給多個任務(wù)添加同一個tag,同時取消)。
? ? ? ?我們簡單的實(shí)現(xiàn)一個通過tag取消任務(wù)的例子
/**
* 給任務(wù)設(shè)置tag
*/
private void startWorker() {
// 給任務(wù)設(shè)置tag->cancel
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(ConstraintsWorker.class).addTag("cancel").build();
// 任務(wù)入隊,WorkManager調(diào)度執(zhí)行
WorkManager.getInstance().enqueue(request);
}
/**
* 通過tag取消任務(wù)
*/
private void cancelWorker() {
WorkManager.getInstance().cancelAllWorkByTag("cancel");
}
2.4、鏈?zhǔn)饺蝿?wù)
? ? ? ?有時候我們想讓某些按照特定的順序行來執(zhí)行。WorkManager允許我們創(chuàng)建和排隊多個任務(wù)的工作序列,以及它們應(yīng)該以什么順序運(yùn)行。這個就是鏈?zhǔn)饺蝿?wù)了。
? ? ? ?任務(wù)鏈里面的任何一個任務(wù)返回WorkerResult.FAILURE,則整個任務(wù)鏈終止。
? ? ? ?鏈?zhǔn)饺蝿?wù)的關(guān)鍵在WorkContinuation,通過WorkContinuation來整理好隊列(是順序執(zhí)行,還是組合執(zhí)行)然后入隊執(zhí)行。
WorkContinuation里面常用函數(shù)介紹
/**
* 順序執(zhí)行任務(wù),
* 如果then的參數(shù)指定了多個任務(wù)那么這些任務(wù)的執(zhí)行順序是沒有規(guī)律的,但是一定要等這個then函數(shù)參數(shù)里面所有任務(wù)都執(zhí)行完了才會去執(zhí)行下一個then的任務(wù)
* 任務(wù)任務(wù)返回Worker.WorkerResult.FAILURE整個則整個任務(wù)鏈結(jié)束
*/
public final @NonNull WorkContinuation then(@NonNull OneTimeWorkRequest... work);
public abstract @NonNull WorkContinuation then(@NonNull List<OneTimeWorkRequest> work);
/**
* 這些都是static函數(shù)哦,用于組合任務(wù)
*/
public static @NonNull WorkContinuation combine(@NonNull WorkContinuation... continuations);
public static @NonNull WorkContinuation combine(@NonNull List<WorkContinuation> continuations);
public static @NonNull WorkContinuation combine(@NonNull OneTimeWorkRequest work,
@NonNull WorkContinuation... continuations);
public static @NonNull WorkContinuation combine(@NonNull OneTimeWorkRequest work,
@NonNull List<WorkContinuation> continuations);
/**
* 獲取任務(wù)鏈中所有任務(wù)的LiveData,用于監(jiān)聽任務(wù)鏈里面任務(wù)的結(jié)果
*/
public abstract @NonNull LiveData<List<WorkStatus>> getStatuses();
/**
* 任務(wù)鏈中的任務(wù)入隊,開始執(zhí)行。
*/
public abstract void enqueue();
2.4.1、任務(wù)順序執(zhí)行
? ? ? ?任務(wù)順序執(zhí)行,WorkContinuation的then()函數(shù)的使用。假設(shè)我們有A,B,C三個任務(wù)需要按順序執(zhí)行。
/**
* A,B,C三個任務(wù)順序執(zhí)行
*/
private void startWorker() {
// A
OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(OrderWorkerA.class).build();
// B
OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(OrderWorkerB.class).build();
// C
OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(OrderWorkerC.class).build();
// 任務(wù)入隊,WorkManager調(diào)度執(zhí)行
WorkManager.getInstance().beginWith(requestA).then(requestB).then(requestC).enqueue();
}
? ? ? ?為了把順序任務(wù)說的更加徹底,我們在來一個例子。
WorkManager.getInstance()
// First, run all the A tasks (in parallel):
.beginWith(workA1, workA2, workA3)
// ...when all A tasks are finished, run the single B task:
.then(workB1, workB2)
// ...then run the C tasks (in any order):
.then(workC1, workC2)
.enqueue();
? ? ? ?上訴代碼中beginWith函數(shù)里面的workA1, workA2, workA3三個任務(wù)是平行(同時)執(zhí)行的,而且要等workA1, workA2, workA3都執(zhí)行完才能做下一步the里的任務(wù)。then(workB1, workB2)里面的workB1,workB2的執(zhí)行順序是沒有規(guī)律的,但是一定要等到workB1,workB2都執(zhí)行玩才能執(zhí)行下一步的then里面的任務(wù)。workC1, workC2的執(zhí)行順序也是沒有規(guī)律的。
2.4.2、組合任務(wù)
? ? ? ?想要組合任務(wù),就需要用到WorkContinuation的combine()函數(shù)了。我們用一個非常見到的任務(wù)來說明組合任務(wù)的執(zhí)行。比如我們想要實(shí)現(xiàn)如下圖所示的鏈試效果。
上圖對應(yīng)代碼如下
/**
* 組合任務(wù)
*/
private void startWorker() {
OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(ConbineWorkerA.class).build();
OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(ConbineWorkerB.class).build();
OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(ConbineWorkerC.class).build();
OneTimeWorkRequest requestD = new OneTimeWorkRequest.Builder(ConbineWorkerD.class).build();
OneTimeWorkRequest requestE = new OneTimeWorkRequest.Builder(ConbineWorkerE.class).build();
//A,B任務(wù)鏈
WorkContinuation continuationAB = WorkManager.getInstance().beginWith(requestA).then(requestB);
//C,D任務(wù)鏈
WorkContinuation continuationCD = WorkManager.getInstance().beginWith(requestC).then(requestD);
//合并上面兩個任務(wù)鏈,在接入requestE任務(wù),入隊執(zhí)行
WorkContinuation.combine(continuationAB, continuationCD).then(requestE).enqueue();
}
2.4.3、任務(wù)鏈中任務(wù)數(shù)據(jù)流(每個任務(wù)的輸入輸出)
? ? ? ?在任務(wù)鏈中,我們可能會有這樣的需求,任務(wù)之間的數(shù)據(jù)是相互依賴的,下一個任務(wù)需要上一個任務(wù)的輸出數(shù)據(jù)。這種情況我們就稱之為任務(wù)鏈中任務(wù)的數(shù)據(jù)流。其實(shí)強(qiáng)大的WorkManager已經(jīng)幫我們設(shè)計好了。WorkManager會把上一個任務(wù)的輸出自動作為下一個人任務(wù)的輸入。
2.4.3.1、順序任務(wù)的數(shù)據(jù)流
? ? ? ?因?yàn)閃orkManager設(shè)計的時候已經(jīng)幫我們設(shè)計好了上一任務(wù)的輸出會自動作為下一個任務(wù)的輸入。所以順序任務(wù)的數(shù)據(jù)流是非常好處理的。上一個任務(wù)調(diào)用setOutputData()返回其結(jié)果,下一個任務(wù)調(diào)用getInputData()來獲取上一個任務(wù)的結(jié)果。我們用一個簡單的實(shí)例來說明。A,B,C三個順序任務(wù)。A任務(wù)輸出10,B任務(wù)得到A任務(wù)的值再乘以10,最后把結(jié)果給到C任務(wù)。我們來看下這種情況下的代碼應(yīng)該怎么寫。
A任務(wù)
/**
* A任務(wù)輸出10
*/
public class StreamThenWorkerA extends Worker {
@NonNull
@Override
public Result doWork() {
Data data = new Data.Builder().putInt("a_out", 10).build();
setOutputData(data);
return Result.SUCCESS;
}
}
B任務(wù)
/**
* 得到A任務(wù)的輸出在乘以10,做為輸出
*/
public class StreamThenWorkerB extends Worker {
@NonNull
@Override
public Result doWork() {
//先得到A任務(wù)的輸出值
Data inputData = getInputData();
int a_out = inputData.getInt("a_out", 0);
//把A任務(wù)的輸出×10在給到C任務(wù)
Data data = new Data.Builder().putInt("b_out", a_out * 10).build();
setOutputData(data);
return Result.SUCCESS;
}
}
C任務(wù)
/**
* 只是做一個簡單的打印
*/
public class StreamThenWorkerC extends Worker{
@NonNull
@Override
public Result doWork() {
Data inputData = getInputData();
int b_out = inputData.getInt("b_out", 0);
//獲取到B任務(wù)的輸出,我們只是做一個簡單的輸出。
Log.d("tuacy", "value = " + b_out);
return Result.SUCCESS;
}
}
執(zhí)行任務(wù)
/**
* 順序任務(wù)的數(shù)據(jù)流
* A,B,C三個任務(wù)。A,輸出10,B任務(wù)得到A任務(wù)的值×10,最后給到C任務(wù)。
*/
private void startThenWorker() {
OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(StreamThenWorkerA.class).build();
OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(StreamThenWorkerB.class).build();
OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(StreamThenWorkerC.class).build();
WorkManager.getInstance().beginWith(requestA).then(requestB).then(requestC).enqueue();
}
2.4.3.2、組合任務(wù)的數(shù)據(jù)流
? ? ? ?組合任務(wù)的數(shù)據(jù)流稍稍復(fù)雜一點(diǎn),因?yàn)樯婕暗蕉鄠€任務(wù)的輸出同時作為一個任務(wù)的輸入,這個時候多個任務(wù)的輸出需要合并一個輸入。這個時候就會有合并規(guī)則一說了(不同的任務(wù)中有相同的key應(yīng)該怎么處理),WorkManager通過OneTimeWorkRequest.Builder類的setInputMerger()函數(shù)來指定多個任務(wù)輸入流的合并規(guī)則。參數(shù)是繼承自InputMerger的類。InputMerger是一個抽象類,WorkManager也給我們提供了兩種合并規(guī)則:ArrayCreatingInputMerger、OverwritingInputMerger。
- ArrayCreatingInputMerger:所有key對應(yīng)的value都會放到數(shù)組里面,有相同的key的話,數(shù)組慢慢擴(kuò)大。比如有A、B兩個任務(wù)的輸出需要組合到一起。A任務(wù)輸出里面有一個key:a_key->100。B任務(wù)里面有兩個key(有個key和A任務(wù)是相同的):b_key->100、a_key->200。最后通過ArrayCreatingInputMerger規(guī)則組合的結(jié)果是:a_key對應(yīng)一個數(shù)組,數(shù)組里面有兩個元素100和200、b_key也對應(yīng)一個數(shù)組,里面只有一個元素100。這個時候在下一個任務(wù)中想要獲取合并之后的輸入必須使用getIntArray(),因?yàn)楝F(xiàn)在key對應(yīng)的value是一個數(shù)組了。
- OverwritingInputMerger:如果有相同的key,直接覆蓋。我通過測試發(fā)現(xiàn)OverwritingInputMerger沒效果,表現(xiàn)形式和ArrayCreatingInputMerger一樣
? ? ? ?我們還是用一個簡單的例子來說明組合任務(wù)的數(shù)據(jù)流,我們有A,B,C三個任務(wù)。A,B任務(wù)合并再執(zhí)行C任務(wù)。在C任務(wù)中獲取A,B兩個任務(wù)的輸出。
A任務(wù)的輸出中只有一個key: a_key -> 100
/**
* A任務(wù)的輸出中只有一個key: a_key -> 100
*/
public class StreamCombineWorkerA extends Worker {
@NonNull
@Override
public Result doWork() {
Data data = new Data.Builder().putInt("a_key", 100).build();
setOutputData(data);
return Result.SUCCESS;
}
}
B任務(wù)的輸出中有兩個key:b_key -> 100、a_key -> 200,有個key在A任務(wù)中也出現(xiàn)了
/**
* B任務(wù)的輸出中有兩個key:b_key -> 100、a_key -> 200
* 有個key在A任務(wù)中也出現(xiàn)了
*/
public class StreamCombineWorkerB extends Worker {
@NonNull
@Override
public Result doWork() {
Data data = new Data.Builder().putInt("b_key", 100).putInt("a_key", 200).build();
setOutputData(data);
return Result.SUCCESS;
}
}
C任務(wù)只是簡單的獲取A,B任務(wù)的輸出
/**
* 在C任務(wù)中獲取到A,B任務(wù)的輸出。
*
*/
public class StreamCombineWorkerC extends Worker {
@NonNull
@Override
public Result doWork() {
Data data = getInputData();
// 注意;這里我用的是getIntArray
int[] aKeyValueList = data.getIntArray("a_key");
int[] bKeyValueList = data.getIntArray("b_key");
Log.d("tuacy", "a_key = " + aKeyValueList[0]);
Log.d("tuacy", "b_key = " + bKeyValueList[0]);
return Result.SUCCESS;
}
}
啟動組合任務(wù),調(diào)用setInputMerger(
OverwritingInputMerger.class)來設(shè)置合并規(guī)則
private void startCombineWorker() {
OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(StreamCombineWorkerA.class).build();
OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(StreamCombineWorkerB.class).build();
// 設(shè)置合并規(guī)則OverwritingInputMerger
OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(StreamCombineWorkerC.class).setInputMerger(
OverwritingInputMerger.class).build();
//A任務(wù)鏈
WorkContinuation continuationA = WorkManager.getInstance().beginWith(requestA);
//B任務(wù)鏈
WorkContinuation continuationB = WorkManager.getInstance().beginWith(requestB);
//合并上面兩個任務(wù)鏈,在接入requestE任務(wù),入隊執(zhí)行
WorkContinuation continuation = WorkContinuation.combine(continuationA, continuationB).then(requestC);
continuation.enqueue();
}
2.4.4、唯一工作隊列
? ? ? ?我們上面例子中所有的鏈試任務(wù)的隊列都是通過WorkManager.getInstance().beginWith()來創(chuàng)建的。這種方式創(chuàng)建的鏈試任務(wù)沒啥限制條件,任務(wù)隨便怎么入隊。要是某些場景我們需要同一個任務(wù)不能重復(fù)入隊怎么辦。這個時候就需要唯一工作隊列了。
? ? ? ?WorkManager允許我們創(chuàng)建一個唯一的工作隊列。唯一工作隊列指的是這個隊列中任務(wù)不能重復(fù)入隊。WorkManager中通過beginUniqueWork()來建一個唯一隊列。每個唯一工作隊列創(chuàng)建的時候都必須指定一個隊列名字,同時還得指定ExistingWorkPolicy當(dāng)WorkManager里面已經(jīng)有一個相同的唯一隊列時候的處理方式。ExistingWorkPolicy有三個值:REPLACE(取消現(xiàn)有的序列并將其替換為新序列)、KEEP(保持現(xiàn)有順序并忽略新請求)、APPEND(將新序列附加到現(xiàn)有序列,在現(xiàn)有序列的最后一個任務(wù)完成后運(yùn)行新序列的第一個任務(wù))。
? ? ? ?如果在唯一工作隊列中多次加入同一個任務(wù),程序會異常退出。
/**
* A,B,C三個任務(wù)加入到唯一工作隊列中去
*/
private void startWorker() {
// A
OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(OrderWorkerA.class).build();
// B
OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(OrderWorkerB.class).build();
// C
OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(OrderWorkerC.class).build();
// 任務(wù)入隊,WorkManager調(diào)度執(zhí)行
WorkManager.getInstance().beginUniqueWork("unique", ExistingWorkPolicy.KEEP, requestA)
.then(requestB)
.then(requestC)
.enqueue();
}
? ? ? ?關(guān)于WorkManager的任務(wù)就講這么寫,如果大家在使用過程中有什么疑問,歡迎留言。最后給出本文涉及到的實(shí)例下載地址https://github.com/tuacy/WorkManagerDev。