——你可以失望,但不能絕望。累的時候可以慢一點,千萬不要後退,你還沒有拼勁全力。怎麼知道沒有奇蹟。
前言
——最近抽空又學了一個Jetpack組件 —— WorkManager,由於工作繁忙,要學的東西還有很多,任務重,時間緊。雖然只學到了點皮毛,但是還是要花點時間做個總結。因爲人們常說:學而不思則罔,思而不學則殆。不思不學則網貸。所以要想致富,好的學習方法是必要的。也跟大家分享一下所學的知識。少走的點彎路。
一、簡介
(1)是什麼
—— WorkManager是Android Jetpack 中管理後臺任務的組件。
—— 常見的使用場景:1.向後端服務發送日誌或分析數據 2.定期將應用數據與服務器同步
(2)有什麼用
—— 使用 WorkManager API 可以輕鬆地調度後臺任務。可延遲運行(即不需要立即運行)並且在應用退出(進程未關閉)或應用重啓時能夠可靠運行的任務。
(3)有什麼優點
- 1.兼容JobScheduler與BroadcastReceiver 和 AlarmManager
- 2.工作約束滿足多種情況
- 3.可使用一次性或週期性執行的任務
- 4.監控和管理計劃任務
- 5.提供API將任務鏈接起來
- 6.遵循低電耗模式等省電功能
二、基本使用
(1)添加依賴
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)執行請求(如果沒有設置約束條件則會立即執行)
WorkManager.getInstance().enqueue(request);
(5)取消和停止工作
WorkManager.getInstance().cancelWorkById(request.getId());
總結:1.創建任務——2.配置請求——3.執行請求
三、進階
(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、request1、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());
}
更詳細的代碼就不貼了,大家要是腦補不了。在按流程仔細看一遍源碼會了解的更深。
本想畫個圖加深一下印象,結果發現是個手殘黨 ,對不住大家 。
五、內容推薦
- 《CSDN》《簡書》
- 《Android Jetpack架構組件之Navigation入門》
- 《Android Jetpack架構組件之Room入門及源碼分析》
- 《Android Jetpack架構組件之ViewModel入門到精通》
- 《Android Jetpack架構組件之LiveData》
若您發現文章中存在錯誤或不足的地方,希望您能指出!