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
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。