——你能夠失望,但不能絕望。累的時候能夠慢一點,千萬不要後退,你尚未拼勁全力。怎麼知道沒有奇蹟。android
——最近抽空又學了一個Jetpack組件 —— WorkManager,因爲工做繁忙,要學的東西還有不少,任務重,時間緊。雖然只學到了點皮毛,可是仍是要花點時間作個總結。由於人們常說:學而不思則罔,思而不學則殆。不思不學則網貸。因此要想致富,好的學習方法是必要的。也跟你們分享一下所學的知識。少走的點彎路。數據庫
—— WorkManager是Android Jetpack 中管理後臺任務的組件。
—— 常見的使用場景:1.向後端服務發送日誌或分析數據 2.按期將應用數據與服務器同步後端
—— 使用 WorkManager API 能夠輕鬆地調度後臺任務。可延遲運行(即不須要當即運行)而且在應用退出(進程未關閉)或應用重啓時可以可靠運行的任務。數組
(1)添加依賴bash
implementation android.arch.work:work-runtime:1.0.1複製代碼
(2)建立後臺任務(自定義類 繼承 Worker 並重寫doWork())服務器
public static class MyWorker extends Worker {
public MyWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@Override
public Result doWork() {
return Result.success();//返回成功
// return Result.failure();//返回失敗
// return Result.retry();//重試
}
}複製代碼
(3)建立請求架構
// 對於一次性 WorkRequest,請使用 OneTimeWorkRequest,對於週期性工做,請使用 PeriodicWorkRequest.
// 構建一次性請求
// OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class).build();
// 構建週期性請求
// PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(MyWorker.class,1, TimeUnit.HOURS).build();複製代碼
(4)執行請求(若是沒有設置約束條件則會當即執行)app
WorkManager.getInstance().enqueue(request);複製代碼
(5)取消和中止工做dom
WorkManager.getInstance().cancelWorkById(request.getId());複製代碼
總結:1.建立任務——2.配置請求——3.執行請求ide
(1)進階1:構建約束條件:
Uri uri = Uri.parse("xxxxx");
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) //指定須要在有網的狀況下
.setRequiresBatteryNotLow(true)//指定電量在可接受範圍內運行
.setRequiresStorageNotLow(true)//指定在存儲量在可接受範圍內運行
.addContentUriTrigger(uri,true)//當Uri發生變化的時候運行
.setRequiresDeviceIdle(true)//當設備處於空閒狀態時運行
.setRequiresCharging(true)//當設備處於充電狀態時運行
.build();
//在請求
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
.setConstraints(constraints)//添加約束
.build();
//當知足約束條件後纔會執行該任務
WorkManager.getInstance().enqueue(request);複製代碼
(2)進階2:延遲執行
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
.setInitialDelay(1,TimeUnit.HOURS)//延遲1小時執行
.build();複製代碼
(3)進階3:設置回退/重試的策略 當doWork()返回 Result.retry()時啓用 指定重試間隔時長
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
//第一個參數:設置策略模式。
//第二個參數:設置第一次重試時長
//第三個參數:設置時間單位
.setBackoffCriteria(BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build();複製代碼
(4)進階4:傳入參數/標記請求任務
Data imageData = new Data.Builder()
.putString(DateKey, "開始執行")
.build();
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
//傳入參數
.setInputData(imageData)
.build();
@Override
public Result doWork() {
//獲取傳入的參數
String data = getInputData().getString(DateKey);
LogUtils.e("data:"+data);
//建立輸出結果
Data outputData = new Data.Builder()
.putString(DateKey,"已經開始充電")
.build();
return Result.success(outputData);
}複製代碼
(5)進階5:標記請求任務
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class)
.addTag(TAG)
.build();
//取消使用特定標記的全部任務
// WorkManager.getInstance().cancelAllWorkByTag(TAG);
//會返回 LiveData 和具備該標記的全部任務的狀態列表
// WorkManager.getInstance().getWorkInfosByTagLiveData(TAG); 複製代碼
(6)進階6:監聽工做狀態
WorkManager.getInstance().getWorkInfoByIdLiveData(request1.getId())
.observe(this, new Observer<WorkInfo>() {
@Override
public void onChanged(@Nullable WorkInfo workInfo) {
if (workInfo != null && (workInfo.getState() == WorkInfo.State.SUCCEEDED)){
//獲取成功返回的結果
tvText.setText(workInfo.getOutputData().getString(DateKey));
}
}
});複製代碼
(7)進階7:連接工做:用於指定多個關聯任務並定義這些任務的運行順序(能夠執行多個任務)
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MyWorker.class).build();
OneTimeWorkRequest request1 = new OneTimeWorkRequest.Builder(MyWorker.class).build();
OneTimeWorkRequest request2 = new OneTimeWorkRequest.Builder(MyWorker.class).build();
OneTimeWorkRequest request3 = new OneTimeWorkRequest.Builder(MyWorker.class).setInputMerger(OverwritingInputMerger.class).build();
OneTimeWorkRequest request4 = new OneTimeWorkRequest.Builder(MyWorker.class).build();
// 爲了管理來自多個父級 OneTimeWorkRequest 的輸入,WorkManager 使用 InputMerger。
// WorkManager 提供兩種不一樣類型的 InputMerger:
// OverwritingInputMerger 會嘗試將全部輸入中的全部鍵添加到輸出中。若是發生衝突,它會覆蓋先前設置的鍵。
// ArrayCreatingInputMerger 會嘗試合併輸入,並在必要時建立數組。
WorkManager.getInstance()
//使用beginWith()能夠並行執行request、request一、request2
.beginWith(Arrays.asList(request, request1, request2)).
//使用then()能夠按順序執行任務
.then(request3)//在執行request3
.then(request4)//在執行request4
.enqueue();複製代碼
大致流程:
1.初始化時建立了WorkManager任務執行器管理線程:裏面建立了一個單線程池管理後臺任務與拿到主線程的handle執行UI更新
2.在Worker封裝了一個線程,經過繼承方式把咱們的後臺任務交給該線程
3.使用WorkRequest配置該任務線程的執行條件
4.最終將WorkManager與WorkRequest綁定在一塊兒。實際是把任務線程及配置信息交給WorkManager處理。
5.也就是調用了WorkManager任務執行器來運行線程與更新UI。
@ 基於依賴implementation android.arch.work:work-runtime:1.0.1 源碼分析
(1)組件的初始化
WorkManager的初始化在ContentProvider中,不須要手動添加。WorkManager是一個抽象類,它的大部分方法都是交給他的子類WorkManagerImpl實現的。
/**
* @Function workManager初始化
*/
public class WorkManagerInitializer extends ContentProvider {
@Override
public boolean onCreate() {
// Initialize WorkManager with the default configuration.
WorkManager.initialize(getContext(), new Configuration.Builder().build());
return true;
}
......
}
/**
* @Function WorkManager.initialize()最終使用單例模式建立WorkManagerImpl對象。
*/
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
WorkManagerImpl.initialize(context, configuration);
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
synchronized (sLock) {
...
if (sDelegatedInstance == null) {
context = context.getApplicationContext();
if (sDefaultInstance == null) {
//建立了WorkManagerImpl
sDefaultInstance = new WorkManagerImpl(
context,
configuration,
//建立了WorkManagerTaskExecutor
new WorkManagerTaskExecutor());
}
sDelegatedInstance = sDefaultInstance;
}
}
}複製代碼
核心類:WorkManagerTaskExecutor :主要是管理後臺線程與UI線程的執行。複製代碼
//經過該類 咱們能夠執行UI線程上的任務與後臺任務
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class WorkManagerTaskExecutor implements TaskExecutor {
//獲取達到UI線程的handler
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
//建立一個Executor 綁定到UI線程上 再經過調用該Executor能夠在UI線程上進行操做
private final Executor mMainThreadExecutor = new Executor() {
@Override
public void execute(@NonNull Runnable command) {
postToMainThread(command);
}
};
@Override
public void postToMainThread(Runnable r) {
mMainThreadHandler.post(r);
}
...
//建立了一個單線程池 管理workManager的後臺線程
private final ExecutorService mBackgroundExecutor =
Executors.newSingleThreadExecutor(mBackgroundThreadFactory);
... //省略部分調用方法
}複製代碼
接下去咱們看下核心類 :WorkManagerImpl
//按照執行順序,咱們先看下它的構造函數 作了哪些準備工做。
public WorkManagerImpl(
@NonNull Context context,
@NonNull Configuration configuration,
@NonNull TaskExecutor workTaskExecutor,
boolean useTestDatabase) {
Context applicationContext = context.getApplicationContext();
// 建立了一個room 數據庫用於保存 任務線程的配置信息
WorkDatabase database = WorkDatabase.create(applicationContext, useTestDatabase);
Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
// 建立Scheduler根據返回一個List<Scheduler>,
//裏面包含兩個Scheduler:GreedyScheduler,SystemJobScheduler/SystemAlarmScheduler
List<Scheduler> schedulers = createSchedulers(applicationContext);
//建Processor,Scheduler最後都調用Processor.startWork()去執行Worker中的邏輯,也就是咱們重寫的doWork()。
Processor processor = new Processor(
context,
configuration,
workTaskExecutor,
database,
schedulers);
//啓動APP時檢查APP是以前否強制中止退出或有未執行完的任務,是的話重啓WorkManager,保證任務能夠繼續執行。
internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
}複製代碼
因爲源碼太多這裏就不一一摘錄了,小弟不才,文采有限。寫不出通俗易懂的句子。你們將就看看大致過程就好。初始化階段就介紹到這裏。
回顧一下初始化過程:
1.首先建立了WorkManagerImpl類,並持有WorkManagerTaskExecutor類,該類是後臺線程與UI線程的主要執行者。
2.在WorkManagerImpl構造方法中建立了數據庫保存任務線程的信息,主要用於App重啓時保證任務能夠繼續執行。
3.又建立了Schedulers,用來知足不一樣條件的狀況下執行特定的任務。
4.啓動APP時從數據庫中獲取任務列表判斷是否由未執行的任務,並啓動 。保證在知足條件的狀況下能夠繼續執行。
分析到了這裏。咱們就回發現這裏還缺乏一個主要的組成部分。那就是咱們的任務。如何把咱們的後臺任務交給workManager處理呢。這就是咱們須要收到操做的部分。也就是咱們使用WorkManger的過程。
(2)建立後臺任務:Worker
//這是一個抽象類,因此須要自定義一個類來繼承該類並重寫 doWork()方法來編寫後臺任務
public abstract class Worker extends ListenableWorker {
...
//從該方法中能夠看出dowork()在一個線程中執行。getBackgroundExecutor()則是調用了單線程池來管理該線程。
@Override
public final @NonNull ListenableFuture<Result> startWork() {
mFuture = SettableFuture.create();
getBackgroundExecutor().execute(new Runnable() {
@Override
public void run() {
try {
Result result = doWork();
mFuture.set(result);
} catch (Throwable throwable) {
mFuture.setException(throwable);
}
}
});
return mFuture;
}
}複製代碼
(3)配置後臺任務的執行條件:WorkRequest
——WorkRequest配置後臺任務的執行條件,該類是一個抽象類,有WorkManager有兩種具體的實現OneTimeWorkRequest/PeriodicWorkRequest。
new OneTimeWorkRequest.Builder(MyWorker.class)
.setConstraints(constraints)//添加約束
.setInitialDelay(1,TimeUnit.HOURS)//進階2:延遲執行
.setBackoffCriteria(BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)//進階3:退避政策:當doWork()返回 Result.retry()時 啓用
.setInputData(imageData)//進階4:傳入參數
.addTag(TAG)//進階4:標記請求任務
.build();
//建立了配置信息類WorkSpec ,將執行條件和參數都保存到WorkSpec中
public abstract static class Builder<B extends Builder, W extends WorkRequest> {
...
Builder(@NonNull Class<? extends ListenableWorker> workerClass) {
mId = UUID.randomUUID();
mWorkSpec = new WorkSpec(mId.toString(), workerClass.getName());
addTag(workerClass.getName());
}
public final @NonNull B setBackoffCriteria(
@NonNull BackoffPolicy backoffPolicy,
long backoffDelay,
@NonNull TimeUnit timeUnit) {
mBackoffCriteriaSet = true;
mWorkSpec.backoffPolicy = backoffPolicy;
mWorkSpec.setBackoffDelayDuration(timeUnit.toMillis(backoffDelay));
return getThis();
}
...
public final @NonNull B setConstraints(@NonNull Constraints constraints) {
mWorkSpec.constraints = constraints;
return getThis();
}
...
}複製代碼
(4)執行任務
// WorkManager.getInstance().enqueue(request1)
@Override
@NonNull
public Operation enqueue(
@NonNull List<? extends WorkRequest> workRequests) {
...
return new WorkContinuationImpl(this, workRequests).enqueue();
}
@Override
public @NonNull Operation enqueue() {
if (!mEnqueued) {
//調用單線程池執行EnqueueRunnable 後面詳細分析下EnqueueRunnable
EnqueueRunnable runnable = new EnqueueRunnable(this);
mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
mOperation = runnable.getOperation();
} else {
Logger.get().warning(TAG,
String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds)));
}
return mOperation;
}複製代碼
//該線程會執行run()方法 並執行兩個重要的方法addToDatabase(), scheduleWorkInBackground();
public class EnqueueRunnable implements Runnable {
...
@Override
public void run() {
try {
if (mWorkContinuation.hasCycles()) {
throw new IllegalStateException(
String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
}
//將後臺任務及配置信息存到數據庫 並返回是否須要執行任務
boolean needsScheduling = addToDatabase();
if (needsScheduling) {
// Enable RescheduleReceiver, only when there are Worker's that need scheduling. final Context context = mWorkContinuation.getWorkManagerImpl().getApplicationContext(); PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true); scheduleWorkInBackground(); } mOperation.setState(Operation.SUCCESS); } catch (Throwable exception) { mOperation.setState(new Operation.State.FAILURE(exception)); } } //最後會啓用 初始化時建立的GreedyScheduler,SystemJobScheduler/SystemAlarmScheduler等調度類來執行工做. @VisibleForTesting public void scheduleWorkInBackground() { WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl(); Schedulers.schedule( workManager.getConfiguration(), workManager.getWorkDatabase(), workManager.getSchedulers()); }複製代碼
更詳細的代碼就不貼了,你們要是腦補不了。在按流程仔細看一遍源碼會了解的更深。
本想畫個圖加深一下印象,結果發現是個手殘黨 ,對不住你們 。
若您發現文章中存在錯誤或不足的地方,但願您能指出!