WorkManager 負責用來管理後臺任務,它適用於須要保證系統即便應用程序退出也會運行的任務, WorkManager根據設備API級別和應用程序狀態等因素選擇適當的方式來運行任務。若是WorkManager在應用程序運行時執行的任務,WorkManager能夠在應用程序進程的新線程中運行您的任務。若是您的應用程序未運行,WorkManager會選擇一種合適的方式來安排後臺任務 。具體取決於設備API級別和包含的依賴項,WorkManager可能會使用 JobScheduler,Firebase JobDispatcher或AlarmManager調度任務。 爲何在有了service之後,google還有出WorkManager框架呢? 1.service的濫用致使手機後臺任務不斷執行,耗電量大。 2.從開發者來講,Android8.0之後,Android對於後臺service管理的更加嚴格,應用在後臺啓動的服務必須是前臺服務,不然會致使應用crash。固然你也能夠選擇下降targetSdkVersion。 3.針對targetSdkVersion Google也針對的出了一些限制。ps:2019 年起,每次發佈新版本後,全部應用都必須在一年內將 Target API 更新到最新版本。 官方指導地址:官方指地址java
官方DEMO 官方DEMO 2.1 Gradle依賴配置android
def work = "2.1.0"
implementation"androidx.work:work-runtime:$work"
implementation"androidx.work:work-testing:$work"
// implementation"androidx.work:work-firebase:$work"
implementation"androidx.work:work-runtime-ktx:$work"
複製代碼
2.2 定義Worker類 自定義Worker類,繼承自Worker,而後複寫doWork() 方法,返回當前任務的結果 Result。doWork方法是執行在子線程的。git
class JetpackWork(context: Context,workerParameters: WorkerParameters) : Worker(context,workerParameters){
override fun doWork(): Result {
Log.e("workermanager","work start:")
Thread.sleep(2_000)
Log.e("workermanager","do work thread msg :"+Thread.currentThread().name)
return Result.success()
}
}
複製代碼
2.3 執行任務 (1)使用 OneTimeWorkRequest.Builder 建立對象Worker,將任務加入WorkManager隊列。而且OneTimeWorkRequest.Builder建立的是一個單次執行的任務。 (2)將任務排入WorkManager隊列,等待執行。 Worker不必定是當即執行的。WorkManager會選擇適當的時間運行Worker,平衡諸如系統負載,設備是否插入等考慮因素。可是若是咱們沒有指定任何約束條件,WorkManager會當即運行咱們的任務。github
var request = OneTimeWorkRequest.Builder(JetpackWork::class.java)
.build()
WorkManager.getInstance(this).enqueue(request)
複製代碼
2.4 重複執行任務 (1)使用PeriodicWorkRequest.Builder類建立循環任務,建立一個PeriodicWorkRequest對象 (2)而後將任務添加到WorkManager的任務隊列,等待執行。 (3)最小時間間隔是15分鐘。數據庫
public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.
複製代碼
var pRequest = PeriodicWorkRequest.Builder(JetpackWork::class.java,1,TimeUnit.SECONDS).build()
WorkManager.getInstance(this).enqueue(pRequest)
複製代碼
2.4 任務的狀態 經過獲取LiveData查看任務的狀態WorkInfo.State,只有當Activityh處於活躍狀態下才能夠監聽成功。bash
WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id).observe(this, Observer {
Log.e("workermanager","state :"+it?.state?.name)
})
複製代碼
2.5 任務約束Constraints WorkManager 容許咱們指定任務執行的環境,好比網絡已鏈接、電量充足時等,在知足條件的狀況下任務纔會執行。 (1)使用Constraints.Builder()建立並配置Constraints對象,能夠指定上訴任務運行時間時的約束。 (2)建立Worker時調用setConstraints指定約束條件。網絡
var constraint = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.setRequiresCharging(true).build()
var request = OneTimeWorkRequest.Builder(JetpackWork::class.java)
.setConstraints(constraint)
.build()
複製代碼
WorkManger提供瞭如下的約束做爲Work執行的條件: (1)setRequiredNetworkType:網絡鏈接設置 (2)setRequiresBatteryNotLow:是否爲低電量時運行 默認false (3)setRequiresCharging:是否要插入設備(接入電源),默認false (4)setRequiresDeviceIdle:設備是否爲空閒,默認false (5)setRequiresStorageNotLow:設備可用存儲是否不低於臨界閾值 2.6 取消任務 (1)從WorkRequest()獲取Worker的ID (2 調用WorkManager.getInstance().cancelWorkById(workRequest.id)根據ID取消任務。 WorkManager 對於已經正在運行或完成的任務是沒法取消任務的。app
WorkManager.getInstance(this).cancelWorkById(request.id)
複製代碼
2.7 添加TAG 經過爲WorkRequest對象分配標記字符串來對任務進行分組框架
var twoRequest = OneTimeWorkRequest.Builder(JetpackTwoWork::class.java)
.setConstraints(constraint)
.addTag("jetpack")
.build()
複製代碼
WorkManager.getStatusesByTag() 返回該標記的全部任務的列表信息。ide
WorkManager.getInstance(this).getWorkInfosByTag("jetpack")
複製代碼
WorkManager.cancelAllWorkByTag() 取消具備特定標記的全部任務
WorkManager.getInstance(this).cancelAllWorkByTag("jetpack")
複製代碼
經過獲取LiveData查看具備特定標記的全部任務的狀態WorkInfo.State
WorkManager.getInstance(this).getWorkInfosByTagLiveData("jetpack").observe(this, Observer {
})
複製代碼
3.1 數據交互 WorkManager能夠將參數傳遞給任務,並讓任務返回結果。傳遞和返回值都是鍵值對形式。 (1)使用 Data.Builder建立 Data 對象,保存參數的鍵值對。 (2)在建立WorkQuest以前調用WorkRequest.Builder.setInputData()傳遞Data的實例
var requestData = Data.Builder().putString("jetpack", "workermanager").build()
var request = OneTimeWorkRequest.Builder(JetpackWork::class.java)
.setConstraints(constraint)
.setInputData(requestData)
.build()
複製代碼
(3 在JetpackWork.doWork方法中經過getInputData獲取傳遞值。 (4)在構建Data對象,跟隨着任務的結果返回。
class JetpackWork(context: Context,workerParameters: WorkerParameters) : Worker(context,workerParameters){
override fun doWork(): Result {
Log.e("workermanager","work start:"+inputData.getString("jetpack"))
Thread.sleep(2_000)
Log.e("workermanager","do work thread msg :"+Thread.currentThread().name)
var outData = Data.Builder().putString("back","hi,jetpack").build()
return Result.success(outData)
}
}
複製代碼
(5) 經過LiveData監聽Worker返回的數據。
WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id).observe(this, Observer {
Log.e("workermanager", "out data :" + it?.outputData?.getString("back"))
})
複製代碼
3.2 鏈式任務
WorkManager.getInstance(this).beginWith(request).then(twoRequest).then(threeRequest).enqueue()
複製代碼
WorkContinuation chainAC = WorkManager.getInstance()
.beginWith(worker A)
.then(worker C);
WorkContinuation chainBD = WorkManager.getInstance()
.beginWith(worker B)
.then(worker D);
WorkContinuation chainAll = WorkContinuation
.combine(chainAC, chainBD)
.then(worker E);
chainAll.enqueue();
複製代碼
在這種狀況下,WorkManager在worker C以前運行workA,它也在workD以前運行workB, WorkB和workD都完成後,WorkManager 運行workE。 注意:雖然WorkManager依次運行每一個子鏈,但不能保證鏈1中的任務與 鏈2中的任務重疊,例如,workB可能在workC以前或以後運行,或者它們可能同時運行。惟一能夠保證的是每一個子鏈中的任務將按順序運行。 3.3 惟一的工做序列 咱們能夠建立一個惟一的工做序列,在任務隊列裏,同一個任務只存在一個,避免任務的重複執行。經過調用 beginUniqueWork() 來建立惟一的工做序列。 參數含義:一、工做序列的名稱 二、當有相同名稱序列時採起的策略方式 三、須要執行的Worker
WorkManager.getInstance(this).beginUniqueWork("jetpack",ExistingWorkPolicy.APPEND,request).enqueue()
複製代碼
ExistingWorkPolicy提供如下策略: (1)ExistingWorkPolicy.REPLACE:取消現有序列並將其替換爲新序列 (2)ExistingWorkPolicy.KEEP:保留現有序列並忽略您的新請求 (3)ExistingWorkPolicy.APPEND:將新序列附加到現有序列,在現有序列的最後一個任務完成後運行新序列的第一個任務。
下面咱們帶着三個問題來看代碼,梳理一下WorkManager的源碼 1.沒有任務約束Constraints的任務是如何執行的。 2.添加任務約束Constraints的任務是如何被觸發的。
public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
synchronized (sLock) {
WorkManagerImpl instance = getInstance();
if (instance == null) {
Context appContext = context.getApplicationContext();
if (appContext instanceof Configuration.Provider) {
initialize(
appContext,
((Configuration.Provider) appContext).getWorkManagerConfiguration());
instance = getInstance(appContext);
} else {
throw new IllegalStateException("WorkManager is not initialized properly. You "
+ "have explicitly disabled WorkManagerInitializer in your manifest, "
+ "have not manually called WorkManager#initialize at this point, and "
+ "your Application does not implement Configuration.Provider.");
}
}
return instance;
}
}
public static @Nullable WorkManagerImpl getInstance() {
synchronized (sLock) {
if (sDelegatedInstance != null) {
return sDelegatedInstance;
}
return sDefaultInstance;
}
}
複製代碼
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:exported="false"
android:multiprocess="true"
android:authorities="com.jandroid.multivideo.workmanager-init"
android:directBootAware="false" />
複製代碼
在WorkManagerInitializer的onCreate方法中調用了WorkManager.initialize的方法進行初始化。
public boolean onCreate() {
// Initialize WorkManager with the default configuration.
WorkManager.initialize(getContext(), new Configuration.Builder().build());
return true;
}
複製代碼
在WorkManager.initialize內部經過調用 WorkManagerImpl.initialize(context, configuration)完成WorkManagerImpl的初始化任務。 3. 下面重點看一下WorkManagerImpl.initializ內部作了那些初始化操做。 (1)sDelegatedInstance和sDefaultInstance都不爲空,說明已經初始化過,拋出異常 (2)調用WorkManagerImpl的構造方法完成初始化任務。 (3)configuration.getTaskExecutor())內部返回默認的線程池。 (4)WorkManagerTaskExecutor內部經過調用SerialExecutor實現線程的調度。
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
synchronized (sLock) {
if (sDelegatedInstance != null && sDefaultInstance != null) {
throw new IllegalStateException("WorkManager is already initialized. Did you "
+ "try to initialize it manually without disabling "
+ "WorkManagerInitializer? See "
+ "WorkManager#initialize(Context, Configuration) or the class level"
+ "Javadoc for more information.");
}
if (sDelegatedInstance == null) {
context = context.getApplicationContext();
if (sDefaultInstance == null) {
sDefaultInstance = new WorkManagerImpl(
context,
configuration,
new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
}
sDelegatedInstance = sDefaultInstance;
}
}
}
複製代碼
private @NonNull Executor createDefaultExecutor() {
return Executors.newFixedThreadPool(
// This value is the same as the core pool size for AsyncTask#THREAD_POOL_EXECUTOR.
Math.max(2, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4)));
}
複製代碼
public WorkManagerImpl(
@NonNull Context context,
@NonNull Configuration configuration,
@NonNull TaskExecutor workTaskExecutor,
boolean useTestDatabase) {
Context applicationContext = context.getApplicationContext();
WorkDatabase database = WorkDatabase.create(
applicationContext, configuration.getTaskExecutor(), useTestDatabase);
Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
List<Scheduler> schedulers = createSchedulers(applicationContext, workTaskExecutor);
Processor processor = new Processor(
context,
configuration,
workTaskExecutor,
database,
schedulers);
internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
}
複製代碼
public @NonNull List<Scheduler> createSchedulers(Context context, TaskExecutor taskExecutor) {
return Arrays.asList(
Schedulers.createBestAvailableBackgroundScheduler(context, this),
// Specify the task executor directly here as this happens before internalInit.
// GreedyScheduler creates ConstraintTrackers and controllers eagerly.
new GreedyScheduler(context, taskExecutor, this));
}
複製代碼
static Scheduler createBestAvailableBackgroundScheduler(
@NonNull Context context,
@NonNull WorkManagerImpl workManager) {
Scheduler scheduler;
if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
scheduler = new SystemJobScheduler(context, workManager);
setComponentEnabled(context, SystemJobService.class, true);
Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
} else {
scheduler = tryCreateGcmBasedScheduler(context);
if (scheduler == null) {
scheduler = new SystemAlarmScheduler(context);
setComponentEnabled(context, SystemAlarmService.class, true);
Logger.get().debug(TAG, "Created SystemAlarmScheduler");
}
}
return scheduler;
}
複製代碼
public Operation enqueue(
@NonNull List<? extends WorkRequest> workRequests) {
// This error is not being propagated as part of the Operation, as we want the
// app to crash during development. Having no workRequests is always a developer error.
if (workRequests.isEmpty()) {
throw new IllegalArgumentException(
"enqueue needs at least one WorkRequest.");
}
return new WorkContinuationImpl(this, workRequests).enqueue();
}
複製代碼
public @NonNull Operation enqueue() {
// Only enqueue if not already enqueued.
if (!mEnqueued) {
// The runnable walks the hierarchy of the continuations
// and marks them enqueued using the markEnqueued() method, parent first.
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;
}
複製代碼
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)); } } public void scheduleWorkInBackground() { WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl(); Schedulers.schedule( workManager.getConfiguration(), workManager.getWorkDatabase(), workManager.getSchedulers()); } 複製代碼
public static void schedule(
@NonNull Configuration configuration,
@NonNull WorkDatabase workDatabase,
List<Scheduler> schedulers) {
if (schedulers == null || schedulers.size() == 0) {
return;
}
WorkSpecDao workSpecDao = workDatabase.workSpecDao();
List<WorkSpec> eligibleWorkSpecs;
if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]);
// Delegate to the underlying scheduler.
for (Scheduler scheduler : schedulers) {
scheduler.schedule(eligibleWorkSpecsArray);
}
}
}
複製代碼
public void schedule(@NonNull WorkSpec... workSpecs) {
registerExecutionListenerIfNeeded();
// Keep track of the list of new WorkSpecs whose constraints need to be tracked.
// Add them to the known list of constrained WorkSpecs and call replace() on
// WorkConstraintsTracker. That way we only need to synchronize on the part where we
// are updating mConstrainedWorkSpecs.
List<WorkSpec> constrainedWorkSpecs = new ArrayList<>();
List<String> constrainedWorkSpecIds = new ArrayList<>();
for (WorkSpec workSpec: workSpecs) {
if (workSpec.state == WorkInfo.State.ENQUEUED
&& !workSpec.isPeriodic()
&& workSpec.initialDelay == 0L
&& !workSpec.isBackedOff()) {
if (workSpec.hasConstraints()) {
// Exclude content URI triggers - we don't know how to handle them here so the // background scheduler should take care of them. if (Build.VERSION.SDK_INT < 24 || !workSpec.constraints.hasContentUriTriggers()) { constrainedWorkSpecs.add(workSpec); constrainedWorkSpecIds.add(workSpec.id); } } else { Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id)); mWorkManagerImpl.startWork(workSpec.id); } } } // onExecuted() which is called on the main thread also modifies the list of mConstrained // WorkSpecs. Therefore we need to lock here. synchronized (mLock) { if (!constrainedWorkSpecs.isEmpty()) { Logger.get().debug(TAG, String.format("Starting tracking for [%s]", TextUtils.join(",", constrainedWorkSpecIds))); mConstrainedWorkSpecs.addAll(constrainedWorkSpecs); mWorkConstraintsTracker.replace(mConstrainedWorkSpecs); } } } 複製代碼
public void startWork(String workSpecId, WorkerParameters.RuntimeExtras runtimeExtras) {
mWorkTaskExecutor
.executeOnBackgroundThread(
new StartWorkRunnable(this, workSpecId, runtimeExtras));
}
複製代碼
public void run() {
mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
}
複製代碼
public boolean startWork(String id, WorkerParameters.RuntimeExtras runtimeExtras) {
WorkerWrapper workWrapper;
synchronized (mLock) {
// Work may get triggered multiple times if they have passing constraints
// and new work with those constraints are added.
if (mEnqueuedWorkMap.containsKey(id)) {
Logger.get().debug(
TAG,
String.format("Work %s is already enqueued for processing", id));
return false;
}
workWrapper =
new WorkerWrapper.Builder(
mAppContext,
mConfiguration,
mWorkTaskExecutor,
mWorkDatabase,
id)
.withSchedulers(mSchedulers)
.withRuntimeExtras(runtimeExtras)
.build();
ListenableFuture<Boolean> future = workWrapper.getFuture();
future.addListener(
new FutureListener(this, id, future),
mWorkTaskExecutor.getMainThreadExecutor());
mEnqueuedWorkMap.put(id, workWrapper);
}
mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
Logger.get().debug(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
return true;
}
複製代碼
public void run() {
mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
mWorkDescription = createWorkDescription(mTags);
runWorker();
}
private void runWorker() {
if (tryCheckForInterruptionAndResolve()) {
return;
}
mWorkDatabase.beginTransaction();
try {
mWorkSpec = mWorkSpecDao.getWorkSpec(mWorkSpecId);
if (mWorkSpec == null) {
Logger.get().error(
TAG,
String.format("Didn't find WorkSpec for id %s", mWorkSpecId));
resolve(false);
return;
}
// Do a quick check to make sure we don't need to bail out in case this work is already // running, finished, or is blocked. if (mWorkSpec.state != ENQUEUED) { resolveIncorrectStatus(); mWorkDatabase.setTransactionSuccessful(); Logger.get().debug(TAG, String.format("%s is not in ENQUEUED state. Nothing more to do.", mWorkSpec.workerClassName)); return; } // Case 1: // Ensure that Workers that are backed off are only executed when they are supposed to. // GreedyScheduler can schedule WorkSpecs that have already been backed off because // it is holding on to snapshots of WorkSpecs. So WorkerWrapper needs to determine // if the ListenableWorker is actually eligible to execute at this point in time. // Case 2: // On API 23, we double scheduler Workers because JobScheduler prefers batching. // So is the Work is periodic, we only need to execute it once per interval. // Also potential bugs in the platform may cause a Job to run more than once. if (mWorkSpec.isPeriodic() || mWorkSpec.isBackedOff()) { long now = System.currentTimeMillis(); // Allow first run of a PeriodicWorkRequest // to go through. This is because when periodStartTime=0; // calculateNextRunTime() always > now. // For more information refer to b/124274584 boolean isFirstRun = mWorkSpec.periodStartTime == 0; if (!isFirstRun && now < mWorkSpec.calculateNextRunTime()) { Logger.get().debug(TAG, String.format( "Delaying execution for %s because it is being executed " + "before schedule.", mWorkSpec.workerClassName)); // For AlarmManager implementation we need to reschedule this kind of Work. // This is not a problem for JobScheduler because we will only reschedule // work if JobScheduler is unaware of a jobId. resolve(true); return; } } // Needed for nested transactions, such as when we're in a dependent work request when
// using a SynchronousExecutor.
mWorkDatabase.setTransactionSuccessful();
} finally {
mWorkDatabase.endTransaction();
}
// Merge inputs. This can be potentially expensive code, so this should not be done inside
// a database transaction.
Data input;
if (mWorkSpec.isPeriodic()) {
input = mWorkSpec.input;
} else {
InputMerger inputMerger = InputMerger.fromClassName(mWorkSpec.inputMergerClassName);
if (inputMerger == null) {
Logger.get().error(TAG, String.format("Could not create Input Merger %s",
mWorkSpec.inputMergerClassName));
setFailedAndResolve();
return;
}
List<Data> inputs = new ArrayList<>();
inputs.add(mWorkSpec.input);
inputs.addAll(mWorkSpecDao.getInputsFromPrerequisites(mWorkSpecId));
input = inputMerger.merge(inputs);
}
WorkerParameters params = new WorkerParameters(
UUID.fromString(mWorkSpecId),
input,
mTags,
mRuntimeExtras,
mWorkSpec.runAttemptCount,
mConfiguration.getExecutor(),
mWorkTaskExecutor,
mConfiguration.getWorkerFactory());
// Not always creating a worker here, as the WorkerWrapper.Builder can set a worker override
// in test mode.
if (mWorker == null) {
mWorker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mAppContext,
mWorkSpec.workerClassName,
params);
}
if (mWorker == null) {
Logger.get().error(TAG,
String.format("Could not create Worker %s", mWorkSpec.workerClassName));
setFailedAndResolve();
return;
}
if (mWorker.isUsed()) {
Logger.get().error(TAG,
String.format("Received an already-used Worker %s; WorkerFactory should return "
+ "new instances",
mWorkSpec.workerClassName));
setFailedAndResolve();
return;
}
mWorker.setUsed();
// Try to set the work to the running state. Note that this may fail because another thread
// may have modified the DB since we checked last at the top of this function.
if (trySetRunning()) {
if (tryCheckForInterruptionAndResolve()) {
return;
}
final SettableFuture<ListenableWorker.Result> future = SettableFuture.create();
// Call mWorker.startWork() on the main thread.
mWorkTaskExecutor.getMainThreadExecutor()
.execute(new Runnable() {
@Override
public void run() {
try {
Logger.get().debug(TAG, String.format("Starting work for %s",
mWorkSpec.workerClassName));
mInnerFuture = mWorker.startWork();
future.setFuture(mInnerFuture);
} catch (Throwable e) {
future.setException(e);
}
}
});
// Avoid synthetic accessors.
final String workDescription = mWorkDescription;
future.addListener(new Runnable() {
@Override
@SuppressLint("SyntheticAccessor")
public void run() {
try {
// If the ListenableWorker returns a null result treat it as a failure.
ListenableWorker.Result result = future.get();
if (result == null) {
Logger.get().error(TAG, String.format(
"%s returned a null result. Treating it as a failure.",
mWorkSpec.workerClassName));
} else {
Logger.get().debug(TAG, String.format("%s returned a %s result.",
mWorkSpec.workerClassName, result));
mResult = result;
}
} catch (CancellationException exception) {
// Cancellations need to be treated with care here because innerFuture
// cancellations will bubble up, and we need to gracefully handle that.
Logger.get().info(TAG, String.format("%s was cancelled", workDescription),
exception);
} catch (InterruptedException | ExecutionException exception) {
Logger.get().error(TAG,
String.format("%s failed because it threw an exception/error",
workDescription), exception);
} finally {
onWorkFinished();
}
}
}, mWorkTaskExecutor.getBackgroundExecutor());
} else {
resolveIncorrectStatus();
}
}
複製代碼
(1)Worker:指定咱們須要執行的任務。 WorkManager API包含一個抽象的Worker類WorkManagerImpl,咱們須要繼承這個類而且在這裏執行工做。 (2)WorkRequest:表明一個單獨的任務。一個WorkRequest 對象指定哪一個 Woker 類應該執行該任務,並且,咱們還能夠向 WorkRequest 對象添加詳細信息,指定任務運行的環境等。每一個 WorkRequest 都有一個自動生成的惟一ID,咱們可使用該ID來執行諸如取消排隊的任務或獲取任務狀態等內容。 WorkRequest 是一個抽象類,在代碼中,咱們須要使用它的直接子類,OneTimeWorkRequest 或 PeriodicWorkRequest.。 (3)WorkRequest.Builder:用於建立WorkRequest對象的輔助類,一樣,咱們要使用它的一個子OneTimeWorkRequest.Builder 和PeriodicWorkRequest.Builder 。 (4)Constraints:指定任務在什麼時候運行(例如,「僅在鏈接到網絡時」)。咱們能夠經過Constraints.Builder 來建立Constraints對象,並在建立WorkRequest以前,將 Constraints 對象傳遞給 WorkRequest.Builder。 (5)WorkManager:將WorkRequest入隊和管理WorkRequest。咱們要將WorkRequest對象傳遞給 WorkManager ,WorkManager 以這樣的方式調度任務,以便分散系統資源的負載,同時遵照咱們指定的約束條件。 (6)WorkStatus:包含有關特定任務的信息。WorkManager 爲每一個 WorkRequest 對象提供一個()LiveData,LiveData持有一個WorkStatus對象,經過觀察LiveData,咱們能夠肯定任務的當前狀態,並在任務完成後獲取返回的任何值。 下面以貼一張執行的類圖信息。
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
android:enabled="false"
android:exported="false"
android:directBootAware="false">
<intent-filter>
<action
android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
複製代碼
public static class NetworkStateProxy extends ConstraintProxy {
}
複製代碼
@Override
public void onReceive(Context context, Intent intent) {
Logger.get().debug(TAG, String.format("onReceive : %s", intent));
Intent constraintChangedIntent = CommandHandler.createConstraintsChangedIntent(context);
context.startService(constraintChangedIntent);
}
static Intent createConstraintsChangedIntent(@NonNull Context context) {
Intent intent = new Intent(context, SystemAlarmService.class);
intent.setAction(ACTION_CONSTRAINTS_CHANGED);
return intent;
}
複製代碼
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
if (mIsShutdown) {
Logger.get().info(TAG,
"Re-initializing SystemAlarmDispatcher after a request to shut-down.");
// Destroy the old dispatcher to complete it's lifecycle. mDispatcher.onDestroy(); // Create a new dispatcher to setup a new lifecycle. initializeDispatcher(); // Set mIsShutdown to false, to correctly accept new commands. mIsShutdown = false; } if (intent != null) { mDispatcher.add(intent, startId); } // If the service were to crash, we want all unacknowledged Intents to get redelivered. return Service.START_REDELIVER_INTENT; } 複製代碼
public boolean add(@NonNull final Intent intent, final int startId) {
Logger.get().debug(TAG, String.format("Adding command %s (%s)", intent, startId));
assertMainThread();
String action = intent.getAction();
if (TextUtils.isEmpty(action)) {
Logger.get().warning(TAG, "Unknown command. Ignoring");
return false;
}
// If we have a constraints changed intent in the queue don't add a second one. We are // treating this intent as special because every time a worker with constraints is complete // it kicks off an update for constraint proxies. if (CommandHandler.ACTION_CONSTRAINTS_CHANGED.equals(action) && hasIntentWithAction(CommandHandler.ACTION_CONSTRAINTS_CHANGED)) { return false; } intent.putExtra(KEY_START_ID, startId); synchronized (mIntents) { boolean hasCommands = !mIntents.isEmpty(); mIntents.add(intent); if (!hasCommands) { // Only call processCommand if this is the first command. // The call to dequeueAndCheckForCompletion will process the remaining commands // in the order that they were added. processCommand(); } } return true; } 複製代碼
private void processCommand() {
assertMainThread();
PowerManager.WakeLock processCommandLock =
WakeLocks.newWakeLock(mContext, PROCESS_COMMAND_TAG);
try {
processCommandLock.acquire();
// Process commands on the background thread.
mWorkManager.getWorkTaskExecutor().executeOnBackgroundThread(new Runnable() {
@Override
public void run() {
synchronized (mIntents) {
mCurrentIntent = mIntents.get(0);
}
if (mCurrentIntent != null) {
final String action = mCurrentIntent.getAction();
final int startId = mCurrentIntent.getIntExtra(KEY_START_ID,
DEFAULT_START_ID);
Logger.get().debug(TAG,
String.format("Processing command %s, %s", mCurrentIntent,
startId));
final PowerManager.WakeLock wakeLock = WakeLocks.newWakeLock(
mContext,
String.format("%s (%s)", action, startId));
try {
Logger.get().debug(TAG, String.format(
"Acquiring operation wake lock (%s) %s",
action,
wakeLock));
wakeLock.acquire();
mCommandHandler.onHandleIntent(mCurrentIntent, startId,
SystemAlarmDispatcher.this);
} catch (Throwable throwable) {
Logger.get().error(
TAG,
"Unexpected error in onHandleIntent",
throwable);
} finally {
Logger.get().debug(
TAG,
String.format(
"Releasing operation wake lock (%s) %s",
action,
wakeLock));
wakeLock.release();
// Check if we have processed all commands
postOnMainThread(
new DequeueAndCheckForCompletion(SystemAlarmDispatcher.this));
}
}
}
});
} finally {
processCommandLock.release();
}
}
複製代碼
void onHandleIntent(
@NonNull Intent intent,
int startId,
@NonNull SystemAlarmDispatcher dispatcher) {
String action = intent.getAction();
if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {
handleConstraintsChanged(intent, startId, dispatcher);
} else if (ACTION_RESCHEDULE.equals(action)) {
handleReschedule(intent, startId, dispatcher);
} else {
Bundle extras = intent.getExtras();
if (!hasKeys(extras, KEY_WORKSPEC_ID)) {
Logger.get().error(TAG,
String.format("Invalid request for %s, requires %s.",
action,
KEY_WORKSPEC_ID));
} else {
if (ACTION_SCHEDULE_WORK.equals(action)) {
handleScheduleWorkIntent(intent, startId, dispatcher);
} else if (ACTION_DELAY_MET.equals(action)) {
handleDelayMet(intent, startId, dispatcher);
} else if (ACTION_STOP_WORK.equals(action)) {
handleStopWork(intent, startId, dispatcher);
} else if (ACTION_EXECUTION_COMPLETED.equals(action)) {
handleExecutionCompleted(intent, startId, dispatcher);
} else {
Logger.get().warning(TAG, String.format("Ignoring intent %s", intent));
}
}
}
}
複製代碼
public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
// WorkConstraintsTracker will call onAllConstraintsMet with list of workSpecs whose
// constraints are met. Ensure the workSpecId we are interested is part of the list
// before we call Processor#startWork().
if (!workSpecIds.contains(mWorkSpecId)) {
return;
}
synchronized (mLock) {
if (mCurrentState == STATE_INITIAL) {
mCurrentState = STATE_START_REQUESTED;
Logger.get().debug(TAG, String.format("onAllConstraintsMet for %s", mWorkSpecId));
// Constraints met, schedule execution
// Not using WorkManagerImpl#startWork() here because we need to know if the
// processor actually enqueued the work here.
boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);
if (isEnqueued) {
// setup timers to enforce quotas on workers that have
// been enqueued
mDispatcher.getWorkTimer()
.startTimer(mWorkSpecId, WORK_PROCESSING_TIME_IN_MS, this);
} else {
// if we did not actually enqueue the work, it was enqueued before
// cleanUp and pretend this never happened.
cleanUp();
}
} else {
Logger.get().debug(TAG, String.format("Already started work for %s", mWorkSpecId));
}
}
}
複製代碼
實現原理就是經過監聽各類約束條件變化的廣播,而後通過層層轉化,最終的處理邏輯和無限制條件的work流程一致。