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是一個抽象類,它有兩種實現,OneTimeWorkRequest和PeriodicWorkRequest,分別對應的是一次性任務和周期性任務。
//只執行一次任務 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();
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; }
原來是在這里選擇的