Android Jetpack架構組件之WorkManager入門

——你可以失望,但不能絕望。累的時候可以慢一點,千萬不要後退,你還沒有拼勁全力。怎麼知道沒有奇蹟。

前言

一、簡介

(1)是什麼

(2)有什麼用 

(3)有什麼優點

二、基本使用

三、進階

四、源碼分析

五、內容推薦


前言

——最近抽空又學了一個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());
    }

更詳細的代碼就不貼了,大家要是腦補不了。在按流程仔細看一遍源碼會了解的更深。

本想畫個圖加深一下印象,結果發現是個手殘黨 ,對不住大家 。  

五、內容推薦

若您發現文章中存在錯誤或不足的地方,希望您能指出!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章