您好,登錄后才能下訂單哦!
這篇文章主要介紹Android WorkManager的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
一、原文翻譯
WorkManager API 可以很容易的指定可延遲的異步任務。允許你創建任務,并把它交給WorkManager來立即運行或在適當的時間運行。WorkManager根據設備API的級別和應用程序狀態等因素來選擇適當的方式運行任務。如果WorkManager在應用程序運行時執行你的任務,它會在應用程序進程的新線程中執行。如果應用程序沒有運行,WorkManager會根據設備API級別和包含的依賴項選擇適當的方式安排后臺任務,可能會使用JobScheduler、Firebase JobDispatcher或AlarmManager。你不需要編寫設備邏輯來確定設備有哪些功能和選擇適當的API;相反,你只要把它交給WorkManager讓它選擇最佳的方式。
Note:WorkManager適用于需要保證即使應用程序退出系統也能運行任務,比如上傳應用數據到服務器。不適用于當應用程序退出后臺進程能安全終止工作,這種情況推薦使用ThreadPools。
功能:
基礎功能
使用WorkManager創建運行在你選擇的環境下的單個任務或指定間隔的重復任務
WorkManager API使用幾個不同的類,有時,你需要繼承一些類。
Worker 指定需要執行的任務。有一個抽象類Worker,你需要繼承并在此處工作。在后臺線程同步工作的類。WorkManager在運行時實例化Worker類,并在預先指定的線程調用doWork方法(見Configuration.getExecutor())。此方法同步處理你的工作,意味著一旦方法返回,Worker被視為已經完成并被銷毀。如果你需要異步執行或調用異步API,應使用ListenableWorker。如果因為某種原因工作沒搶占,相同的Worker實例不會被重用。即每個Worker實例只會調用一次doWork()方法,如果需要重新運行工作單元,需要創建新的Worker。Worker最大10分鐘完成執行并ListenableWorker.Result。如果過期,則會被發出信號停止。(Worker的doWork()方法是同步的,方法執行完則結束,不會重復執行,且默認超時時間是10分鐘,超過則被停止。)
WorkRequest 代表一個獨立的任務。一個WorkRequest對象至少指定哪個Worker類應該執行該任務。但是,你還可以給WorkRequest添加詳細信息,比如任務運行時的環境。每個WorkRequest有一個自動生成的唯一ID,你可以使用ID來取消排隊的任務或獲取任務的狀態。WorkRequest是一個抽象類,你需要使用它一個子類,OneTimeWorkRequest或PeriodicWorkRequest。
WorkRequest.Builder 創建WorkRequest對象的幫助類,你需要使用子類OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder。
Constraints(約束) 指定任務執行時的限制(如只有網絡連接時)。使用Constraints.Builder創建Constraints對象,并在創建WorkRequest對象前傳遞給WorkRequest.Builder。
WorkManager 排隊和管理WorkRequest。將WorkRequest對象傳遞給WorkManager來將任務添加到隊列。WorkManager 使用分散加載系統資源的方式安排任務,同時遵守你指定的約束。
WorkManager使用一種底層作業調度服務基于下面的標注
使用JobScheduler API23+
使用AlarmManager + BroadcastReceiver API14-22
WorkInfo 包含有關特定任務的信息。WorkManager為每個WorkRequest對象提供一個LiveData。LiveData持有WorkInfo對象,通過觀察LiveData,你可以確定任務的當前狀態,并在任務完成后獲取任何返回的值。
二、源碼簡單分析
android.arch.work:work-runtime-1.0.0-beta03
WorkerManager的具體實現類是WorkManagerImpl。
WorkManager不同的方法,會創建不同的***Runnable類來執行。
下面是整體的包結構
以EnqueueRunnable為例
@Override public void run() { try { if (mWorkContinuation.hasCycles()) { throw new IllegalStateException( String.format("WorkContinuation has cycles (%s)", mWorkContinuation)); } boolean needsScheduling = addToDatabase(); if (needsScheduling) { 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)); } } /** * Schedules work on the background scheduler. */ @VisibleForTesting public void scheduleWorkInBackground() { WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl(); Schedulers.schedule( workManager.getConfiguration(), workManager.getWorkDatabase(), workManager.getSchedulers()); }
主要執行在Schedulers類中
/** * Schedules {@link WorkSpec}s while honoring the {@link Scheduler#MAX_SCHEDULER_LIMIT}. * * @param workDatabase The {@link WorkDatabase}. * @param schedulers The {@link List} of {@link Scheduler}s to delegate to. */ public static void schedule( @NonNull Configuration configuration, @NonNull WorkDatabase workDatabase, List<Scheduler> schedulers) { if (schedulers == null || schedulers.size() == 0) { return; } ... 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); } } }
下面看下Scheduler的子類
最后會創建WorkerWrapper包裝類,來執行我們定義的Worker類。
@WorkerThread @Override 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; } // running, finished, or is blocked. if (mWorkSpec.state != ENQUEUED) { resolveIncorrectStatus(); mWorkDatabase.setTransactionSuccessful(); 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(); if (now < mWorkSpec.calculateNextRunTime()) { resolve(false); return; } } 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 { 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 { 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(); } }
這里使用了androidx.work.impl.utils.futures.SettableFuture,并調用了addListener方法,該回調方法會在調用set時執行。
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 { 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());
下面看下核心的Worker類
@Override public final @NonNull ListenableFuture<Result> startWork() { mFuture = SettableFuture.create(); getBackgroundExecutor().execute(new Runnable() { @Override public void run() { Result result = doWork(); mFuture.set(result); } }); return mFuture; }
可見,在調用doWork()后,任務執行完調用了set方法,此時會回調addListener方法。
addListener回調中主要用來判斷當前任務的狀態,所以如果任務被停止,此處展示捕獲的異常信息。
比如調用一個任務的cancel方法,會展示下面的信息。
1. 2019-02-02 15:35:41.682 30526-30542/com.outman.study.workmanagerdemo I/WM-WorkerWrapper: Work [ id=3d775394-e0d7-44e3-a670-c3527a3245ee, tags={ com.outman.study.workmanagerdemo.SimpleWorker } ] was cancelled 2. java.util.concurrent.CancellationException: Task was cancelled. 3. at androidx.work.impl.utils.futures.AbstractFuture.cancellationExceptionWithCause(AbstractFuture.java:1184) 4. at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:514) 5. at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475) 6. at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:264) 7. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 8. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 9. at java.lang.Thread.run(Thread.java:764)
以上是“Android WorkManager的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。