Android WorkManager淺談
一、原文翻譯
WorkManager API 可以很容易的指定可延遲的異步任務(wù)。允許你創(chuàng)建任務(wù),并把它交給WorkManager來立即運(yùn)行或在適當(dāng)?shù)臅r間運(yùn)行。WorkManager根據(jù)設(shè)備API的級別和應(yīng)用程序狀態(tài)等因素來選擇適當(dāng)?shù)姆绞竭\(yùn)行任務(wù)。如果WorkManager在應(yīng)用程序運(yùn)行時執(zhí)行你的任務(wù),它會在應(yīng)用程序進(jìn)程的新線程中執(zhí)行。如果應(yīng)用程序沒有運(yùn)行,WorkManager會根據(jù)設(shè)備API級別和包含的依賴項(xiàng)選擇適當(dāng)?shù)姆绞桨才藕笈_任務(wù),可能會使用JobScheduler、Firebase JobDispatcher或AlarmManager。你不需要編寫設(shè)備邏輯來確定設(shè)備有哪些功能和選擇適當(dāng)?shù)腁PI;相反,你只要把它交給WorkManager讓它選擇最佳的方式。
Note:WorkManager適用于需要保證即使應(yīng)用程序退出系統(tǒng)也能運(yùn)行任務(wù),比如上傳應(yīng)用數(shù)據(jù)到服務(wù)器。不適用于當(dāng)應(yīng)用程序退出后臺進(jìn)程能安全終止工作,這種情況推薦使用ThreadPools。

功能:
基礎(chǔ)功能
- 使用WorkManager創(chuàng)建運(yùn)行在你選擇的環(huán)境下的單個任務(wù)或指定間隔的重復(fù)任務(wù)
- WorkManager API使用幾個不同的類,有時,你需要繼承一些類。
- Worker 指定需要執(zhí)行的任務(wù)。有一個抽象類Worker,你需要繼承并在此處工作。在后臺線程同步工作的類。WorkManager在運(yùn)行時實(shí)例化Worker類,并在預(yù)先指定的線程調(diào)用doWork方法(見Configuration.getExecutor())。此方法同步處理你的工作,意味著一旦方法返回,Worker被視為已經(jīng)完成并被銷毀。如果你需要異步執(zhí)行或調(diào)用異步API,應(yīng)使用ListenableWorker。如果因?yàn)槟撤N原因工作沒搶占,相同的Worker實(shí)例不會被重用。即每個Worker實(shí)例只會調(diào)用一次doWork()方法,如果需要重新運(yùn)行工作單元,需要創(chuàng)建新的Worker。Worker最大10分鐘完成執(zhí)行并ListenableWorker.Result。如果過期,則會被發(fā)出信號停止。(Worker的doWork()方法是同步的,方法執(zhí)行完則結(jié)束,不會重復(fù)執(zhí)行,且默認(rèn)超時時間是10分鐘,超過則被停止。)
- WorkRequest 代表一個獨(dú)立的任務(wù)。一個WorkRequest對象至少指定哪個Worker類應(yīng)該執(zhí)行該任務(wù)。但是,你還可以給WorkRequest添加詳細(xì)信息,比如任務(wù)運(yùn)行時的環(huán)境。每個WorkRequest有一個自動生成的唯一ID,你可以使用ID來取消排隊(duì)的任務(wù)或獲取任務(wù)的狀態(tài)。WorkRequest是一個抽象類,你需要使用它一個子類,OneTimeWorkRequest或PeriodicWorkRequest。
- WorkRequest.Builder 創(chuàng)建WorkRequest對象的幫助類,你需要使用子類OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder。
- Constraints(約束) 指定任務(wù)執(zhí)行時的限制(如只有網(wǎng)絡(luò)連接時)。使用Constraints.Builder創(chuàng)建Constraints對象,并在創(chuàng)建WorkRequest對象前傳遞給WorkRequest.Builder。
- WorkManager 排隊(duì)和管理WorkRequest。將WorkRequest對象傳遞給WorkManager來將任務(wù)添加到隊(duì)列。WorkManager 使用分散加載系統(tǒng)資源的方式安排任務(wù),同時遵守你指定的約束。
- WorkManager使用一種底層作業(yè)調(diào)度服務(wù)基于下面的標(biāo)注
- 使用JobScheduler API23+
- 使用AlarmManager + BroadcastReceiver API14-22
- WorkInfo 包含有關(guān)特定任務(wù)的信息。WorkManager為每個WorkRequest對象提供一個LiveData。LiveData持有WorkInfo對象,通過觀察LiveData,你可以確定任務(wù)的當(dāng)前狀態(tài),并在任務(wù)完成后獲取任何返回的值。

二、源碼簡單分析
android.arch.work:work-runtime-1.0.0-beta03
WorkerManager的具體實(shí)現(xiàn)類是WorkManagerImpl。
WorkManager不同的方法,會創(chuàng)建不同的***Runnable類來執(zhí)行。
下面是整體的包結(jié)構(gòu)

以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());
}
主要執(zhí)行在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的子類

最后會創(chuàng)建WorkerWrapper包裝類,來執(zhí)行我們定義的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,并調(diào)用了addListener方法,該回調(diào)方法會在調(diào)用set時執(zhí)行。
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;
}
可見,在調(diào)用doWork()后,任務(wù)執(zhí)行完調(diào)用了set方法,此時會回調(diào)addListener方法。
addListener回調(diào)中主要用來判斷當(dāng)前任務(wù)的狀態(tài),所以如果任務(wù)被停止,此處展示捕獲的異常信息。
比如調(diào)用一個任務(wù)的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)
以上就是我的簡單分析,還有好多沒有說到,后面有時間會繼續(xù)。
有不對的歡迎批評指正。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android 通過API獲取數(shù)據(jù)庫中的圖片文件方式
- Android 動態(tài)添加view或item并獲取數(shù)據(jù)的實(shí)例
- Android WorkManager使用以及源碼分析
- Android WorkManager實(shí)現(xiàn)后臺定時任務(wù)流程詳解
- Android?Jetpack庫重要組件WorkManager的使用
- Android開發(fā)Jetpack組件WorkManager用例詳解
- Android使用Kotlin API實(shí)踐WorkManager
- android kotlin集成WorkManager實(shí)現(xiàn)定時獲取數(shù)據(jù)的步驟
相關(guān)文章
android使用PullToRefresh框架實(shí)現(xiàn)ListView下拉刷新上拉加載更多
這篇文章主要介紹了android使用PullToRefresh框架實(shí)現(xiàn)ListView下拉刷新上拉加載更多,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
Android實(shí)現(xiàn)層疊卡片式banner
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)層疊卡片式banner,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-10-10
Android 自定義縮短Toast顯示時間的實(shí)例代碼
這篇文章主要介紹了Android 自定義縮短Toast顯示時間,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01
Android四大組件之BroadcastReceiver詳解
今天小編就為大家分享一篇關(guān)于Android四大組件之BroadcastReceiver詳解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01
Android調(diào)用前后攝像頭同時工作實(shí)例代碼
本篇文章主要介紹了Android調(diào)用前后攝像頭同時工作實(shí)例代碼,這里整理了詳細(xì)的代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-07-07
android studio集成unity導(dǎo)出工程的實(shí)現(xiàn)
本文主要介紹了android studio集成unity導(dǎo)出工程的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
Android實(shí)現(xiàn)類似網(wǎng)易新聞選項(xiàng)卡動態(tài)滑動效果
這篇文章主要介紹了Android實(shí)現(xiàn)類似網(wǎng)易新聞選項(xiàng)卡動態(tài)滑動效果的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-11-11
基于barcodescanner實(shí)現(xiàn)Android二維碼掃描功能
這篇文章主要為大家詳細(xì)介紹了基于barcodescanner實(shí)現(xiàn)Android二維碼掃描功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07

