Android架構(gòu)組件WorkManager詳解

? ? ? ?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)如下圖所示的鏈試效果。

combine.png

上圖對應(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內(nèi)容