WorkManager詳解


WorkManager詳解

前言  

   WorkManager組件是用來管理后台工作任務。Android不是已經有很多管理后台任務的類,比如JobScheduler, AlarmManger;在比如AsyncTask, ThreadPool,WorkManager。WorkManager的優勢在哪里,我們為啥要使用WorkManager。我們從幾個方面來說明WorkManager的優勢。
 
  • WorkManager對比JobScheduler, AlarmManger的優勢:AlarmManager是一直存在,適用於類似鬧鍾那樣必須准時喚醒的任務,但是JobScheduler是Android 5.x之后才有。WorkManager的底層實現,會根據你的設備API的情況,自動選用JobScheduler, 或是AlarmManager來實現后台任務。什么?在哪里體現的?耐心等待下文的講解。

  • WorkManager對比AsyncTask, ThreadPool的優勢:WorkManager里面的任務在應用退出之后還可以繼續執行。AsyncTask, ThreadPool里面的任務在應用退出之后不會執行。WorkManager適用於那些在應用退出之后任務還需要繼續執行的需求(比如應用數據上報服務器的情況),對應那些在應用退出的之后任務也需要終止的情況就需要選擇ThreadPool、AsyncTask來實現。

  • WorkManager的出現,為應用程序中那些不需要及時完成的任務,提供統一的解決方案,以便在設備電量和用戶體驗之間達到一個比較好的平衡。

  • WorkManager能保證任務一定會被執行,即使你的應用程序當前不在運行中,哪怕你的設備重啟,任務仍然會在適當的時候被執行。這是因為WorkManager有自己的數據庫,關於任務的所有信息和數據都保存在這個數據庫中,因此,只要你的任務交給了WorkManager,哪怕你的應用程序徹底退出,或者設備重新啟動,WorkManager依然能夠保證完成你交給的任務。

兼容范圍廣

  WorkManager最低能兼容API Level 14,並且不需要你的設備安裝有Google Play Services。因此,你不用過於擔心兼容性問題,因為API Level 14已經能夠兼容幾乎100%的設備了。

WorkManager依據設備情況選擇方案

  WorkManager能依據設備的情況,選擇不同的執行方案。在API Level 23+,通過JobScheduler來完成任務,而在API Level 23以下的設備中,通過AlarmManager和Broadcast Receivers組合完成任務。但無論采用哪種方案,任務最終都是交由Executor來完成。 

  WorkManager不是一種新的工作線程,它的出現不是為了替代其它類型的工作線程。工作線程通常立即運行,並在執行完成后給到用戶反饋。而WorkManager不是即時的,它不能保證任務能立即得到執行。

在項目中使用WorkManager

1.在app的build.gradle中添加依賴。

app build.gradle
dependencies {
    implementation rootProject.ext.work_runtime
}

工程根目錄下的config.gradle
WorkRuntimeVersion = "2.4.0"
work_runtime = [
            "androidx.work:work-runtime:$WorkRuntimeVersion"
    ]

2.使用Worker定義任務 。

public class UploadLogWorker extends Worker {

    private static final String TAG = "WorkManager";

    public UploadLogWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    /**
     * 耗時的任務,在doWork()方法中執行
     * <p>
     * 有三種類型的返回值:
     * 執行成功返回Result.success()
     * 執行失敗返回Result.failure()
     * 需要重新執行返回Result.retry()
     */
    @NonNull
    @Override
    public Result doWork() {
        Log.i("==lwf==", "doWork");
        String inputData = getInputData().getString("input_data");
        // 任務執行完成后返回數據
        Data outputData = new Data.Builder().putString("output_data", inputData).build();
        return Result.success(outputData);
    }
}

3.使用WorkRequest配置任務。通過WorkRequest配置我們的任務何時運行以及如何運行。

  • 設置任務觸發條件。例如,我們可以設置在設備處於充電,網絡已連接,且電池電量充足的狀態下,才觸發我們設置的任務
Constraints constraints = new Constraints.Builder()
            .setRequiresCharging(true)//充電
            .setRequiredNetworkType(NetworkType.CONNECTED)//網絡已連接
            .setRequiresBatteryNotLow(true)//電池電量充足
            .build();

將該Constraints設置到WorkRequest。WorkRequest是一個抽象類,它有兩種實現,OneTimeWorkRequestPeriodicWorkRequest,分別對應的是一次性任務和周期性任務。

 
//只執行一次任務
OneTimeWorkRequest compressionWork = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
             .addTag("UploadTag")
             .setConstraints(constraints)//設置觸發條件
             .setInitialDelay(10, TimeUnit.SECONDS)//符合觸發條件后,延遲10秒執行
             .setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)//線性重試方案 最大5h 最小10s
             .build();
為任務設置Tag標簽。設置Tag后,你就可以通過該抱歉跟蹤任務的狀態WorkManager.getWorkInfosByTagLiveData(String tag)或者取消任務WorkManager.cancelAllWorkByTag(String tag)。
這里看到重試屬性,為何最大5h,最小10s ?原因如下:
public static final long MAX_BACKOFF_MILLIS = 5 * 60 * 60 * 1000; // 5 hours.

public static final long MIN_BACKOFF_MILLIS = 10 * 1000; // 10 seconds.

public void setBackoffDelayDuration(long backoffDelayDuration) {
        if (backoffDelayDuration > MAX_BACKOFF_MILLIS) {
            Logger.get().warning(TAG, "Backoff delay duration exceeds maximum value");
            backoffDelayDuration = MAX_BACKOFF_MILLIS;
        }
        if (backoffDelayDuration < MIN_BACKOFF_MILLIS) {
            Logger.get().warning(TAG, "Backoff delay duration less than minimum value");
            backoffDelayDuration = MIN_BACKOFF_MILLIS;
        }
        this.backoffDelayDuration = backoffDelayDuration;
    }

4.將任務提交給系統。WorkManager.enqueue()方法會將你配置好的WorkRequest交給系統來執行 

WorkManager.getInstance().enqueue(compressionWork);

5.觀察任務的狀態。

任務在提交給系統后,通過WorkInfo獲知任務的狀態,WorkInfo包含了任務的id,tag,以及Worker對象傳遞過來的outputData,以及任務當前的狀態。有三種方式可以得到WorkInfo對象。

WorkManager.getWorkInfosByTag()

WorkManager.getWorkInfoById()

WorkManager.getWorkInfosForUniqueWork()

如果你希望能夠實時獲知任務的狀態。這三個方法還有對應的LiveData方法。

WorkManager.getWorkInfosByTagLiveData()

WorkManager.getWorkInfoByIdLiveData()

WorkManager.getWorkInfosForUniqueWorkLiveData()

通過LiveData,我們便可以在任務狀態發生變化的時候,收到通知。

WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadWorkRequest.getId()).observe(this, new Observer<WorkInfo>()
        {
            @Override
            public void onChanged(WorkInfo workInfo)
            {
                if (workInfo != null && workInfo.getState() == WorkInfo.State.SUCCEEDED)
                {
                    String outputData = workInfo.getOutputData().getString("output_data");
                    Log.d("==lwf==", "outputData: " + outputData);
                }
            }
        });

6.取消任務。與觀察任務類似的,我們也可以根據Id或者Tag取消某個任務,或者取消所有任務。

WorkManager.getInstance(MainActivity.this).cancelAllWork();

7.WorkManager和Worker之間的參數傳遞。數據的傳遞通過Data對象來完成。

Data inputData = new Data.Builder().putString("input_data", "Hello World!").build();
        OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
                .setInputData(inputData)
                .build();
WorkManager.getInstance(this).enqueue(uploadWorkRequest);

8.周期任務PeriodicWorkRequest。

PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest
                .Builder(U
ploadLogWorker.class, 15, TimeUnit.MINUTES)
                .setConstraints(constraints)//設置觸發條件
                .build();

需要注意的是:周期性任務的間隔時間不能小於15分鍾。

原因如下:

public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.

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

9.任務鏈。如果你有一系列的任務需要順序執行,那么可以利用WorkManager.beginWith().then().then()...enqueue()方法。例如:我們在上傳數據之前,需要先對數據進行壓縮

WorkManager.getInstance(this).beginWith(compressWorkRequest).then(uploadWorkRequest).enqueue();

假設在上傳數據之前,除了壓縮數據,還需要更新本地數據。壓縮與更新本地數據二者沒有順序,但與上傳數據存在先后順序。

WorkManager.getInstance(this).beginWith(compressWorkRequest, updateLocalWorkRequest).then(uploadWorkRequest).enqueue();

如果有更復雜的任務鏈,還可以考慮使用WorkContinuation.combine()方法,將任務鏈組合起來。

OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(UploadLogWorker.class).build();
        OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(UploadLogWorker.class).build();
        OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(UploadLogWorker.class).build();
        //A任務鏈
        WorkContinuation continuationA = WorkManager.getInstance().beginWith(requestA);
        //B任務鏈
        WorkContinuation continuationB = WorkManager.getInstance().beginWith(requestB);
        //合並上面兩個任務鏈,在接入requestE任務,入隊執行
        List<WorkContinuation> list = new ArrayList<>();
        list.add(continuationA);
        list.add(continuationB);
        WorkContinuation continuation = WorkContinuation.combine(list).then(requestC);
        continuation.enqueue();

 

 采用WorkContinuation.combine()的任務鏈執行順序

 

 解析

Workmanager在怎么選擇使用的JobScheduler, AlarmManger?

我們再提交任務的時候,是都使用Workmanager.getInstance(Context),看看這個是怎么實現的。

   public static @NonNull WorkManager getInstance(@NonNull Context context) {
        return WorkManagerImpl.getInstance(context);
    }
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
        synchronized (sLock) {
            WorkManagerImpl instance = getInstance();
            if (instance == null) {
                Context appContext = context.getApplicationContext();
                if (appContext instanceof Configuration.Provider) {
                    initialize(
                            appContext,
                            ((Configuration.Provider) appContext).getWorkManagerConfiguration());
                    instance = getInstance(appContext);
                } else {
                    throw new IllegalStateException("WorkManager is not initialized properly.  You "
                            + "have explicitly disabled WorkManagerInitializer in your manifest, "
                            + "have not manually called WorkManager#initialize at this point, and "
                            + "your Application does not implement Configuration.Provider.");
                }
            }

            return instance;
        }
    }

看到這里進行了初始化initialize,為什么開機就能運行呢?

反編譯APK,發現AndroidManifest.xml

<provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:exported="false"
            android:multiprocess="true"
            android:authorities="com.example.myapplication.workmanager-init"
            android:directBootAware="false" />
注冊了WorkManagerInitializer
public class WorkManagerInitializer extends ContentProvider

原來WorkManagerInitializer是一個ContentProvider

public boolean onCreate() {
        // Initialize WorkManager with the default configuration.
        WorkManager.initialize(getContext(), new Configuration.Builder().build());
        return true;
    }
    public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        WorkManagerImpl.initialize(context, configuration);
    }

原來是這樣初始化的,在WorkManagerImpl初始化過程中選擇了JobScheduler, AlarmManger方式

public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            @NonNull WorkDatabase database) {
        Context applicationContext = context.getApplicationContext();
        Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
        List<Scheduler> schedulers =
                createSchedulers(applicationContext, configuration, workTaskExecutor);
        Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);
        internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
    }
    public List<Scheduler> createSchedulers(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor taskExecutor) {

        return Arrays.asList(
                Schedulers.createBestAvailableBackgroundScheduler(context, this),
                // Specify the task executor directly here as this happens before internalInit.
                // GreedyScheduler creates ConstraintTrackers and controllers eagerly.
                new GreedyScheduler(context, configuration, taskExecutor, this));
    }

 

static Scheduler createBestAvailableBackgroundScheduler(
            @NonNull Context context,
            @NonNull WorkManagerImpl workManager) {

        Scheduler scheduler;

        if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
            scheduler = new SystemJobScheduler(context, workManager);
            setComponentEnabled(context, SystemJobService.class, true);
            Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
        } else {
            scheduler = tryCreateGcmBasedScheduler(context);
            if (scheduler == null) {
                scheduler = new SystemAlarmScheduler(context);
                setComponentEnabled(context, SystemAlarmService.class, true);
                Logger.get().debug(TAG, "Created SystemAlarmScheduler");
            }
        }
        return scheduler;
    }

原來是在這里選擇的


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM