WorkManager 後臺任務管理 Jetpack

Worker: 後臺執行的具體任務

WorkRequest:相當於裝飾模式,把Worker包一層,在不影響Worker的情況下,給它添加約束細節。

WorkManager:後臺任務管理,把WorkRequest放入WorkManager

別人的觀點,可以參考一下:

後臺作業
在 Android 應用程序不在前臺執行邏輯時,你可以有很多種方式來實現後臺運行,這也是 Android 動態化的用例之一。如果你不引入 doze , SyncAdapter , CGMNetworkManager , FirebaseJobDispatcher , JobScheuler 和最近的 WorkManager,可以使用常規的啓動服務(非綁定服務)來實現。這些都是 Google 提供的 API , 我可以說出所有的使用方式。當然,還有一些第三方庫可以使用, 例如:Android-Job。

不過,Google 最近宣佈,他們將圍繞 WorkManager 來統一後臺任務調度[3]。這聽起來非常棒,我再也不用學習那麼多後臺調度的知識了,只是,不知道爲什麼,我好像以前在哪兒聽到過這句話……

我們不管將來是否會統一使用 WorkManager,WorkManager 都還存在一些問題,比如可靠性。我不想在本文中解釋爲什麼,但是請你一定要記住,如果你想在應用程序中使用 WorkManager,實現後臺作業,一定要去讀一下 dontkillmyapp.com 上的所有內容,並且要關注一下與 WorkManager 相關的 Google 問題列表
 


https://www.jianshu.com/p/de19752f159c

service一直被用來做後臺運行的操作,包括一些保活,上傳數據之類的,這個後臺運行的弊端很多,比如耗電,比如設計用戶隱私之類的,谷歌對這些後臺行爲進行了一些處理,從Android Oreo(API 26) 開始,如果一個應用的目標版本爲Android 8.0,當它在某些不被允許創建後臺服務的場景下,調用了Service的startService()方法,該方法會拋出IllegalStateException。並且出臺了一些新政策:

1、2018年8月: 所有新開發應用的target API level必須是26(Android 8.0)甚至更高。
2、2018年11月: 所有已發佈應用的target API level必須更新至26甚至更高。
3、2019年起: 在每一次發佈新版本的Android系統之後,所有新開發以及待更新的應用都必須在一年內將target API level調整至對應的系統版本甚至更高。

    如果想繼續使用service,必須調用Context.startForegroundService(),在前臺啓動新服務,系統創建服務,應用有五秒的時間來調用該服務的 startForeground() 方法以顯示新服務的用戶可見通知。 如果應用在此時間限制內未調用 startForeground(),則系統將停止服務並聲明此應用爲 ANR。所以,在不久的將來,service的使用範圍會越來越小,取而代之的,是谷歌推出的新的技術:WorkManager。
    WorkManager 在工作的觸發器 滿足時, 運行可推遲的後臺工作。WorkManager會根據設備API的情況,自動選用JobScheduler, 或是AlarmManager來實現後臺任務,WorkManager裏面的任務在應用退出之後還可以繼續執行,這個技術適用於在應用退出之後任務還需要繼續執行的需求,對於在應用退出的之後任務也需要終止的需求,可以選擇ThreadPool、AsyncTask。

WorkManager相關類

Worker
任務的執行者,是一個抽象類,用於指定需要執行的具體任務,需要實現doWork() 這一個方法,它是執行在一個單獨的後臺線程裏的。所有需要在後臺執行的任務都在這個方法裏完成。
doWork()函數的返回值:

  • Worker.Result.SUCCESS:任務執行成功。
  • Worker.Result.FAILURE:任務執行失敗。
  • Worker.Result.RETRY:任務需要重新執行,如果出現這個返回結果,就需要與WorkRequest.Builder中的setBackoffCriteria()函數一起使用。

WorkRequest
代表一個單獨的任務,對Worker任務進行包裝,一個WorkRequest對應一個Worker類。可以通過WorkRequest來給Worker類添加約束細節,比如設備是否空閒,設備電池是否不應低於臨界閾值,指定設備在充電時是否啓動任務等等。WorkRequest是一個抽象類,具體要使用兩個子類:OneTimeWorkRequest(任務只執行一遍)、PeriodicWorkRequest(任務週期性的執行)。

WorkManager
主要管理任務請求和任務隊列,將WorkRequest加入任務隊列。 通過WorkManager來調度任務,以分散系統資源的負載。

WorkStatus
當 WorkManager 把任務加入隊列後,會爲每個WorkRequest對象提供一個 LiveData, LiveData 持有 WorkStatus,包含有任務的狀態和任務的信息

引用

在build.gradle中,引用workManager:

 implementation "android.arch.work:work-runtime:1.0.0-alpha07"

版本可以選用最新的

定義 Worker

新建一個jave類:MyWorker,繼承自Worker,必須實現doWork()方法,要在這個方法裏,操作後臺任務,

public class MyWorker extends Worker
{
    String tag = MyWorker.class.getSimpleName();
    @NonNull
    @Override
    public Result doWork()
    {
        return Result.SUCCESS;
    }

    @Override
    public void onStopped(boolean cancelled)
    {
        super.onStopped(cancelled);
        Log.e(tag,"Worker Stopped");
    }

}

定義WorkRequest

在主Activity中,定義WorkRequest,具體可以選擇兩個子類,OneTimeWorkRequest(任務只執行一遍)、PeriodicWorkRequest(任務週期性的執行),比如:

 PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.SECONDS).build();

這裏有一個注意點:這個週期任務,最小週期是15分鐘,源碼:

 /**
     * The minimum interval duration for {@link PeriodicWorkRequest} (in milliseconds).
     */
    public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.

 public void setPeriodic(long intervalDuration, long flexDuration) {
        if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
            Logger.warning(TAG, String.format(
                    "Interval duration lesser than minimum allowed value; Changed to %s",
                    MIN_PERIODIC_INTERVAL_MILLIS));
            intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
        }

       ....
    }

加入隊列

已經將Worker與WorkRequest相關聯,現在定義WorkManager,將WorkRequest加入隊列,

 //任務入隊,WorkManager調度執行
        WorkManager.getInstance().enqueue(request);

傳入數據

前臺和後臺服務,有時候需要傳入數據,在Activity定義Data,將需要傳入的數據包裝一下,然後通過WorkRequest的setInputData()傳入

        Data data = new Data.Builder().putInt("params1", 1).putString("params2", "hello").build();

         PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.SECONDS)
                .setInputData(data)
                .build();

這個傳入的值,可以在Worker中獲取

public class MyWorker extends Worker
{
    String tag = MyWorker.class.getSimpleName();
    @NonNull
    @Override
    public Result doWork()
    {
        int params1 = getInputData().getInt("params1",0);
        String params2 = getInputData().getString("params2");
        Log.d(tag,"獲得參數:"+params1+","+params2);
        return Result.SUCCESS;
    }

    @Override
    public void onStopped(boolean cancelled)
    {
        super.onStopped(cancelled);
        Log.e(tag,"Worker Stopped");
    }

}

返回數據

後臺在處理完任務以後,可以返回一些數據,返回數據,傳出數據需要使用outputData(),具體還是在Worker的doWork()方法裏

 Data resultData = new Data.Builder()
                .putString("result","success").build();
        setOutputData(resultData);

處理WorkStatus

當 WorkRequest入列後,WorkManager 會給它分配一個 work ID,WorkManager可以通過WorkRequest的id,獲取到WorkRequest的WorkStatus,返回的是LiveData 形式:

  WorkManager.getInstance().getStatusById(request.getId()).observe(this, new android.arch.lifecycle.Observer<WorkStatus>()
        {
            @Override
            public void onChanged(@Nullable WorkStatus workStatus)
            {
                if (workStatus != null && workStatus.getState() != null)
                {
                    Log.d("MainActivity", workStatus.getState() + "");
                }
            }
        });

WorkStatus 的state包括:

/**
 * The current state of a unit of work.
 */
public enum State {

    /**
     * The state for work that is enqueued (hasn't completed and isn't running)
     */
    ENQUEUED,

    /**
     * The state for work that is currently being executed
     */
    RUNNING,

    /**
     * The state for work that has completed successfully
     */
    SUCCEEDED,

    /**
     * The state for work that has completed in a failure state
     */
    FAILED,

    /**
     * The state for work that is currently blocked because its prerequisites haven't finished
     * successfully
     */
    BLOCKED,

    /**
     * The state for work that has been cancelled and will not execute
     */
    CANCELLED;

    /**
     * Returns {@code true} if this State is considered finished.
     *
     * @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and {@link #CANCELLED} states
     */
    public boolean isFinished() {
        return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
    }
}

WorkRequest加標籤

可以通過addTag給WorkRequest加入標籤,比如:

 PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.SECONDS)
                .setInputData(data)
                .addTag("A")
                .build();
PeriodicWorkRequest request2 = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.SECONDS)
                .setInputData(data)
                .addTag("A")
                .build();

通過addTag(),將WorkRequest成爲了一個組:A組。以後可以直接控制整個組就行了,組內的每個成員都會受到影響。比如通過WorkManager的cancelAllWorkByTag(String tag):取消一組帶有相同標籤的任務。

取消任務

WorkManager可以通過WorkRequest的id取消或者停止任務,

WorkManager.getInstance().cancelWorkById(request.id)

WorkManager 並不一定能結束任務,因爲任務有可能已經執行完畢了。
還有其他結束的方法:
cancelAllWork():取消所有任務。
cancelAllWorkByTag(String tag):取消一組帶有相同標籤的任務。
cancelUniqueWork( String uniqueWorkName):取消唯一任務。

添加約束

WorkManager 允許指定任務執行的環境,比如網絡已連接、電量充足時等,在滿足條件的情況下任務纔會執行。
現在支持的約束:

public boolean requiresBatteryNotLow ():執行任務時電池電量不能偏低。

public boolean requiresCharging ():在設備充電時才能執行任務。

public boolean requiresDeviceIdle ():設備空閒時才能執行。

public boolean requiresStorageNotLow ():設備儲存空間足夠時才能執行。

具體代碼:( Constraints 在WorkRequest中,通過setConstraints使用)

 Constraints constraints = new Constraints.Builder()
                .setRequiresDeviceIdle(true)//指定{@link WorkRequest}運行時設備是否爲空閒
                .setRequiresCharging(true)//指定要運行的{@link WorkRequest}是否應該插入設備
                .setRequiredNetworkType(NetworkType.UNMETERED)
                .setRequiresBatteryNotLow(true)//指定設備電池是否不應低於臨界閾值
                .setRequiresCharging(true)//網絡狀態
                .setRequiresDeviceIdle(true)//指定{@link WorkRequest}運行時設備是否爲空閒
                 .setRequiresStorageNotLow(true)//指定設備可用存儲是否不應低於臨界閾值
                  .addContentUriTrigger(myUri,false)//指定內容{@link android.net.Uri}時是否應該運行{@link WorkRequest}更新
                .build();

其他的都是一個boolean值,網絡狀態複雜一些

/**
     * 指定網絡狀態執行任務
     * NetworkType.NOT_REQUIRED:對網絡沒有要求
     * NetworkType.CONNECTED:網絡連接的時候執行
     * NetworkType.UNMETERED:不計費的網絡比如WIFI下執行
     * NetworkType.NOT_ROAMING:非漫遊網絡狀態
     * NetworkType.METERED:計費網絡比如3G,4G下執行。
     */

可以根據自己需要,來自由組合這些約束,在WorkRequest中,通過setConstraints設置約束

    PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.SECONDS)
                .setInputData(data)
                .setConstraints(constraints)
                .build();

鏈式任務

如果處理的不是一個任務,而是一組任務,可以按照一定順序來執行,也可以按照組合來執行,如果任務鏈中的任何一個任務,返回WorkerResult.FAILURE,任務鏈終止
按照一定順序執行,需要使用WorkManager的then()方法,將需要執行的任務依次加入

WorkManager.getInstance()
        .beginWith(work1)
        .then(work2)
        .then(work3)
        .enqueue()

這個是依次執行,work1執行完,work2才能執行,work2執行完,work3才能執行,上一個任務的返回值就會自動轉爲下一個任務的參數
組合執行:如果有這樣的需求:共有A、B、C、D、E這五個任務,要求 AB 串行,CD 串行,但兩個串之間要併發,並且最後要把兩個串的結果彙總到E。需要使用WorkContinuation的combine

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任務鏈
        WorkContinuation continuationAB = WorkManager.getInstance().beginWith(requestA).then(requestB);
        //C,D任務鏈
        WorkContinuation continuationCD = WorkManager.getInstance().beginWith(requestC).then(requestD);
        //合併上面兩個任務鏈,在接入requestE任務,入隊執行
        WorkContinuation.combine(continuationAB, continuationCD).then(requestE).enqueue();

任務唯一性

有時候需要在任務隊列裏,同一個任務只存在一個,避免任務的重複執行,需要使用WorkManager的 beginUniqueWork 這個方法:

WorkManager.getInstance()
        .beginUniqueWork("unique", ExistingWorkPolicy.REPLACE, request)
        .enqueue()

ExistingWorkPolicy的值:
REPLACE(取消現有的序列並將其替換爲新序列)
KEEP(保持現有順序並忽略新請求)
APPEND(將新序列附加到現有序列,在現有序列的最後一個任務完成後運行新序列的第一個任務)。



作者:WilburLi
鏈接:https://www.jianshu.com/p/de19752f159c
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章