前言
用於支持Android在后台的任務運行,提供延遲、周期性,約束性需求的后台任務。任務是交給系統統一調度的,適合一些輕量級的后台功能使用。還能支持在Doze模式下運行后台任務,WorkManager會在Doze模式的窗口期運行任務。
WorkManager的設計用意就是取代后台服務,由系統統一管理你的周期性后台服務,並且自動兼容API23以下,API23以下自動在底層使用AlarmManager + BroadcastReceiver實現,而高於API23會使用JobScheduler實現。所以這是一個能取代鬧鍾定時器的后台功能。並且在高版本里鬧鍾功能其實已經不太能正常使用了。使用WorkManager取代所有周期或者長時間的后台工作是必需必要的。
依賴
// (Java only) implementation "androidx.work:work-runtime:2.3.4"//java 語言選這個 // Kotlin + coroutines implementation "androidx.work:work-runtime-ktx:2.3.4"//kotlin 選這個 // optional - RxJava2 support implementation "androidx.work:work-rxjava2:2.3.4"//配合rxjava2 使用
一個簡單的小Demo快速了解
照例用一個極簡的demo來運行體驗一下。
創建work,繼承Worker,實現doWork方法,這個方法是執行后台功能實現的地方。
public class MyWork extends Worker { public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { Log.e("調試_臨時_log", "this_doWork"); return Result.success();//結果返回為成功 } }
創建Work請求並且添加到WorkManager里:
private void startWork(){ OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class)//一次性Work請求 .setInitialDelay(10, TimeUnit.SECONDS)//初始延遲10秒 .build(); WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);//添加到WorkManager隊列中 }
這樣我們就可以在10秒后看到log了。
了解Worker基本功能
數據的傳入與獲取
在work里
public class MyWork extends Worker { public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { String data = getInputData().getString("putData"); Log.e("調試_臨時_log", "傳入數據 putData = " + data); return Result.success(); } }
在Activity里創建work請求,並且傳入數據
private void startWork(){ Data data = new Data.Builder().putString("putData","輸入數據").build();//創建需要傳入的數據,注意不支持序列化數據傳入 OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class)//一次性Work請求 .setInitialDelay(2, TimeUnit.SECONDS) .setInputData(data) .build(); WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);//添加到WorkManager隊列中 }
結果:
2020-07-10 17:51:10.638 24649-24698/com.yt.demo E/調試_臨時_log: 傳入數據 putData = 輸入數據
work的返回結果與監聽狀態
處理了后台任務后總會有成功與否的結果。
在doWork方法里返回結果:
一共有3種結果可以返回,如下注釋:
@NonNull @Override public Result doWork() { return Result.success(); // Result.success();//成功 // Result.failure();//失敗 // Result.retry();//重試 }
另外成功與失敗的結果還能攜帶數據返回。
@NonNull @Override public Result doWork() { Data data = new Data.Builder().putString("data", "返回數據").build(); return Result.success(data); }
Activity里監聽work的狀態:
請注意!在這里的監聽的返回的數據是LiveData。 這意味着只有當前Activity或者Fragment在前台時才能接收到此數據。
private void startWork(){ OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class)//一次性Work請求 .setInitialDelay(5, TimeUnit.SECONDS) .build(); WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest.getId()).observe(this, new Observer<WorkInfo>() { @Override public void onChanged(WorkInfo workInfo) { switch (workInfo.getState()){ case BLOCKED: Log.e("調試_臨時_log", "堵塞"); break; case RUNNING: Log.e("調試_臨時_log", "正在運行"); break; case ENQUEUED: Log.e("調試_臨時_log", "任務入隊"); break; case CANCELLED: Log.e("調試_臨時_log", "取消"); break; case FAILED: Log.e("調試_臨時_log", "失敗"); break; case SUCCEEDED: Log.e("調試_臨時_log", "成功"); Log.e("調試_臨時_log", "this_data = " + workInfo.getOutputData().getString("data")); break; } } }); WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);//添加到WorkManager隊列中 }
結果:
2020-07-10 20:11:20.088 31078-31078/com.yt.demo E/調試_臨時_log: 任務入隊 2020-07-10 20:11:25.172 31078-31078/com.yt.demo E/調試_臨時_log: 正在運行 2020-07-10 20:11:25.195 31078-31078/com.yt.demo E/調試_臨時_log: 成功 2020-07-10 20:11:25.195 31078-31078/com.yt.demo E/調試_臨時_log: this_data = 返回數據
不選擇監聽,即刻獲得某個Work的當前狀態值
try { WorkInfo workInfo = WorkManager.getInstance(MainActivity.this).getWorkInfoById(mOneTimeWorkRequest.getId()).get(); Log.e("調試_臨時_log", "this_" + workInfo.getState()); } catch (ExecutionException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); }
work的進度發布
有時候有需求需要知道work任務執行的進度。下面的MyWork模擬發送耗時任務進度,使用setProgressAsync方法發布進度。
public class MyWork extends Worker { public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { for (int i = 0; i < 10; i++) { Data data = new Data.Builder().putInt("Progress", i).build(); setProgressAsync(data); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } return Result.success(); } }
監聽進度:
private void startWork() { OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class)//一次性Work請求 .setInitialDelay(5, TimeUnit.SECONDS) .build(); WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest.getId()).observe(this, new Observer<WorkInfo>() { @Override public void onChanged(WorkInfo workInfo) { if (workInfo.getState() == WorkInfo.State.RUNNING) { Log.e("調試_臨時_log", "當前進度 = " + workInfo.getProgress().getInt("Progress", -1)); } } }); WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);//添加到WorkManager隊列中 }
Work的停止
work的停止,只會work在運行時執行onStopped,已經執行完成去取消任務是不會觸發onStopped方法的。
work里的代碼:
public class MyWork extends Worker { private boolean mIsStop = false; public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { for (int i = 0; i < 10; i++) { if (mIsStop){ break; } Data data = new Data.Builder().putInt("Progress", i).build(); setProgressAsync(data); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } return Result.success(); } @Override public void onStopped() { Log.e("調試_臨時_log", "this_onStopped"); mIsStop = true; super.onStopped(); } }
Activity代碼:
public class MainActivity extends AppCompatActivity { private ActivityMianDemoBinding mBinding; private OneTimeWorkRequest oneTimeWorkRequest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = ActivityMianDemoBinding.inflate(getLayoutInflater()); setContentView(mBinding.getRoot()); mBinding.btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setInitialDelay(5, TimeUnit.SECONDS) .build(); WorkManager.getInstance(MainActivity.this).getWorkInfoByIdLiveData(oneTimeWorkRequest.getId()).observe(MainActivity.this, new Observer<WorkInfo>() { @Override public void onChanged(WorkInfo workInfo) { switch (workInfo.getState()) { case RUNNING: Log.e("調試_臨時_log", "當前進度 = " + workInfo.getProgress().getInt("Progress", -1)); break; case CANCELLED: Log.e("調試_臨時_log", "this_取消任務"); break; } } }); WorkManager.getInstance(MainActivity.this).enqueue(oneTimeWorkRequest); } }); mBinding.btn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { WorkManager.getInstance(MainActivity.this).cancelWorkById(oneTimeWorkRequest.getId());//取消任務 } }); } }
結果:
2020-07-13 11:38:27.160 14030-14030/com.yt.cccomponentizationdemo E/調試_臨時_log: 當前進度 = 0 2020-07-13 11:38:28.210 14030-14030/com.yt.cccomponentizationdemo E/調試_臨時_log: 當前進度 = 1 2020-07-13 11:38:29.199 14030-14030/com.yt.cccomponentizationdemo E/調試_臨時_log: 當前進度 = 2 2020-07-13 11:38:29.902 14030-14075/com.yt.cccomponentizationdemo E/調試_臨時_log: this_onStopped 2020-07-13 11:38:29.973 14030-14030/com.yt.cccomponentizationdemo E/調試_臨時_log: this_取消任務
了解創建Work請求
請求有兩種
- OneTimeWorkRequest 一次性Work請求
- PeriodicWorkRequest 周期性Work請求
OneTimeWorkRequest
設置初始延遲時間 setInitialDelay
上面已經有很多例子了,就不在重復說明了
OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setInitialDelay(2, TimeUnit.SECONDS)//設置初始延時時間 .build();
設置傳入數據 setInputData
上面已經有很多例子了,就不在重復說明了
Data data = new Data.Builder().putString("data","demo").build(); OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setInputData(data)//設置傳入數據 .build();
添加Tag addTag
注意,這個添加Tag有點奇怪。如果一直添加相同的Tag,這個Tag可以被多次添加,並且在使用getWorkInfosByTagLiveData 進行監聽回調時List<WorkInfo>也會有多個,並且無法好像無法刪除這個list數量(取消任務也不行)。但是只會返回一次數據。請謹慎使用,我暫時沒明白如何使用它。
OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setInitialDelay(2, TimeUnit.SECONDS)//設置初始延時時間 .addTag("tag1")//添加TAG .build(); WorkManager.getInstance(MainActivity.this).getWorkInfosByTagLiveData("tag1").observe(MainActivity.this, new Observer<List<WorkInfo>>() { @Override public void onChanged(List<WorkInfo> workInfos) { if (workInfos == null && workInfos.isEmpty()) { return; } Log.e("調試_臨時_log", "this_ workInfos.size = " + workInfos.size()); WorkInfo workInfo = workInfos.get(0); switch (workInfo.getState()) { case RUNNING: Log.e("調試_臨時_log", "this_進行中"); break; case CANCELLED: Log.e("調試_臨時_log", "this_取消"); break; case SUCCEEDED: Log.e("調試_臨時_log", "this_成功"); break; } } }); WorkManager.getInstance(MainActivity.this).enqueue(oneTimeWorkRequest);
設置任務的結果保存時間 keepResultsForAtLeast
oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .keepResultsForAtLeast(10, TimeUnit.MINUTES)//結果延遲保存 .build();
設置退避策略 setBackoffCriteria
一般當我們任務執行失敗的時候任務需要重試的時候會用到這個函數,在任務執行失敗的時候Worker類的doWork()函數返回Result.RETRY告訴這個任務要重試。那重試的策略就是通過setBackoffCriteria()函數來設置的。
BackoffPolicy有兩個值:
BackoffPolicy.LINEAR(每次重試的時間線性增加,比如第一次10分鍾,第二次就是20分鍾)
BackoffPolicy.EXPONENTIAL(每次重試時間指數增加)。
oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setBackoffCriteria(BackoffPolicy.LINEAR, 10,TimeUnit.MINUTES)//退避策略 線性增加 10分鍾重試 .build();
PeriodicWorkRequest
因為前面沒有說明過PeriodicWorkRequest,所以這里說明下PeriodicWorkRequest創建的一些細節。首先Builder(MyWork.class, 15 ,TimeUnit.MINUTES) 三個參數,第二個參數是重復觸發的時間,第三個參數是單位。
請注意,這里的重復周期時間是有要求的,大於等於15分鍾,這個在Builder方法的注釋里有說明PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS最小間隔時間。
請再次注意,PeriodicWorkRequest在給WorkManager入隊后就會立馬執行,所以你如果需要一開始就延遲需要自行設延遲時間。
另外PeriodicWorkRequest的可以設置的屬性與OneTimeWorkRequest一致。可以參考上面的說明,這里就不在重復。
PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(MyWork.class, 15 ,TimeUnit.MINUTES) .build(); WorkManager.getInstance(MainActivity.this).enqueue(periodicWorkRequest);
約束條件Constraints
Constraints constraints = new Constraints.Builder() .setRequiresDeviceIdle(true)//觸發時設備是否為空閑 .setRequiresCharging(true)//觸發時設備是否充電 .setRequiredNetworkType(NetworkType.UNMETERED)//觸發時網絡狀態 .setRequiresBatteryNotLow(true)//指定設備電池是否不應低於臨界閾值 .setRequiresStorageNotLow(true)//指定設備可用存儲是否不應低於臨界閾值 .addContentUriTrigger(myUri,false)//指定內容{@link android.net.Uri}時是否應該運行{@link WorkRequest}更新 .build(); OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setConstraints(constraints) .build(); WorkManager.getInstance(MainActivity.this).enqueue(oneTimeWorkRequest);
網絡狀態條件
public enum NetworkType { /** * 這項工作不需要網絡 */ NOT_REQUIRED, /** * 這項工作需要任何有效的網絡連接 */ CONNECTED, /** * 這項工作需要未計量的網絡連接 */ UNMETERED, /** * 此項工作需要非漫游網絡連接 */ NOT_ROAMING, /** * 此項工作需要計量網絡連接 */ METERED }
WorkManager
主要用於work的入隊與取消,設置監聽功能。
任務入隊
WorkManager.getInstance(MainActivity.this).enqueue(oneTimeWorkRequest);
取消指定ID任務
WorkManager.getInstance(MainActivity.this).cancelWorkById(oneTimeWorkRequest.getId());
取消全部任務
WorkManager.getInstance(MainActivity.this).cancelAllWork();
創建唯一任務
WorkManager.getInstance(MainActivity.this).beginUniqueWork("unique", ExistingWorkPolicy.REPLACE, oneTimeWorkRequest).enqueue();
或者
WorkManager.getInstance(MainActivity.this).enqueueUniqueWork("unique", ExistingWorkPolicy.REPLACE, oneTimeWorkRequest);
任務類型說明
public enum ExistingWorkPolicy { /** 如果存在具有相同唯一名稱的待處理(未完成)任務,請取消並刪除它。然后,插入新指定的任務 */ REPLACE, /** * 如果存在具有相同唯一名稱的待處理(未完成)任務,則不執行任何操作。 否則,插入新指定的任務。 */ KEEP, /** * 如果存在具有相同唯一名稱的待處理(未完成)任務,請將*新指定的任務作為該任務序列所有葉子的子項附加。否則,請插入*新指定的任務作為新序列的開始。 */ APPEND }
監聽唯一任務
WorkManager.getInstance(MainActivity.this).getWorkInfosForUniqueWorkLiveData("unique").observe(MainActivity.this, new Observer<List<WorkInfo>>() { @Override public void onChanged(List<WorkInfo> workInfos) { } });
End