Android WorkManager

本文主要內(nèi)容

WorkManager使用詳細(xì)介紹
自定義Worker的幾種實(shí)現(xiàn)
使用注意事項(xiàng)

特性

  • 兼容Android API 14+
  • API 23+使用JobScheduler
  • API 14-22 使用BroadcastReceiver + AlarmManager
  • 可配置任務(wù)執(zhí)行約束條件(比如在有網(wǎng)絡(luò)時(shí),充電時(shí),電量充足時(shí)等)
  • 執(zhí)行一次性任務(wù)和周期性任務(wù)
  • 跟蹤管理任務(wù):取消任務(wù),獲取任務(wù)結(jié)果
  • 支持串聯(lián)執(zhí)行任務(wù),可設(shè)置任務(wù)執(zhí)行順序
  • 保證任務(wù)執(zhí)行,即使是app或者設(shè)備重啟了

關(guān)鍵類

  • WorkManager
    采用單例模式(WorkManager.getInstance()),用于任務(wù)管理(添加,取消,獲取任狀態(tài)果,結(jié)果等)
  • Worker
    抽象任務(wù)類,任務(wù)抽象方法為:doWork()
  • WorkRequest :
    任務(wù)請(qǐng)求抽象類,WorkManager通過WorkRequest添加任務(wù),其實(shí)現(xiàn)類有:OneTimeWorkRequest和PeriodicWorkRequest
  • OneTimeWorkRequest
    一次性任務(wù)請(qǐng)求
  • PeriodicWorkRequest
    周期性任務(wù)請(qǐng)求(注意:周期性任務(wù)最小間隔時(shí)間為15分鐘)
  • Constraints
    執(zhí)行任務(wù)的約束條件,比如需要充電狀態(tài)才執(zhí)行(mRequiresCharging),電量充足時(shí)才執(zhí)行(mRequiresBatteryNotLow)等等
  • WorkInfo
    任務(wù)相關(guān)信息,包括WorkRequest產(chǎn)生的ID(UUID),當(dāng)前任務(wù)狀態(tài),結(jié)果數(shù)據(jù),標(biāo)識(shí)Tag,通過:WorkManager.getWorkInfoById(UUID) or WorkManager.getWorkInfoByIdLiveData(UUID)獲得

使用方法

添加WorkManager依賴

dependencies {
    def work_version = 2.0.1
    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"
    
    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"
    
    // optional - RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"
    
    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"
 }

WorkManager 初始化

默認(rèn)系統(tǒng)已經(jīng)給我們初始化了WorkManager,無需自己初始化。但如果要自己初始化,需要在AndroidManifest.xml文件里面添加以下標(biāo)簽刪除默認(rèn)初始化:

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    tools:node="remove" />

然后在Application.onCreate()中初始化WorkManager,通過WorkManager.getInstance()獲取實(shí)例,初始化如下:

// provide custom configuration
Configuration myConfig = new Configuration.Builder()
    // 設(shè)置日志級(jí)別
    .setMinimumLoggingLevel(android.util.Log.INFO)
    // 設(shè)置后臺(tái)任務(wù)線程池
    .setExecutor(Executors.newFixedThreadPool(8))
    .build();

// 初始化 WorkManager
WorkManager.initialize(this, myConfig);
// 獲取實(shí)例
WorkManager workManager = WorkManager.getInstance()

執(zhí)行一次性任務(wù): OneTimeWorkRequest

  • 執(zhí)行任務(wù)前,首先需自定任務(wù),繼承Worker抽象類,實(shí)現(xiàn)抽象方法doWork()即可,doWork()就是任務(wù)方法(有點(diǎn)類似Runnable接口的run()方法):
public class MyWorker extends Worker {

    public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @Override
    public Result doWork() {
        Log.d(TAG, "I am working");
        // 任務(wù)結(jié)果
        Data data = new Data.Builder().putString("result", "I am result from MyWork").build();
        // 成功返回任務(wù)結(jié)果,實(shí)際結(jié)果包含在data對(duì)象里面,如果無需返回結(jié)果返回:Result.success()
        // 失敗返回:Result.failure()
        // 需要重新執(zhí)行返回:Result.retry()
        return Result.success(data);
    }
}
  • 第二步創(chuàng)建WorkRequest,執(zhí)行一次性任務(wù)使用:OneTimeWorkRequest
// 每個(gè)WorkRequest會(huì)初始化一個(gè)UUID作為唯一任務(wù)ID,查詢?nèi)蝿?wù)狀態(tài)或者取消任務(wù)都需要通過這個(gè)ID來實(shí)現(xiàn)
OneTimeWorkRequest request = new OneTimeWorkRequest
    // 任務(wù)為MyWork
    .Builder(MyWork.class)
    // 設(shè)置執(zhí)行條件為充電時(shí)才執(zhí)行
    .setConstraints(new Constraints.Builder().setRequiresCharging(true).build())
    // 設(shè)置延遲5分鐘執(zhí)行
    .setInitialDelay(5, TimeUnit.MINUTES)
    // 添加tag,用于標(biāo)識(shí)任務(wù),可以通過tag查詢?nèi)蝿?wù)狀態(tài),取消任務(wù)等操作
    .addTag("one_time_request_tag")
    // 設(shè)置輸入data,有點(diǎn)像輸入?yún)?shù),可以在Worker中通過getInputData()方法獲取
    .setInputData(new Data.Builder().putString("input_data", "I am input data").build())
    .build();
  • 第三步將OneTimeWorkRequest添加到任務(wù)隊(duì)列
// 添加任務(wù)到隊(duì)列,任務(wù)會(huì)在條件滿足的情況下才會(huì)執(zhí)行 
WorkManager.getInstance().enqueue(request);

添加周期性任務(wù):PeriodicWorkRequest

步驟跟添加一次性任務(wù)一樣,只是WorkRequest從OneTimeRequest改為PeriodicWorkRequest,示例如下:

// 創(chuàng)建每隔30分鐘執(zhí)行一次的周期性任務(wù)請(qǐng)求
PeriodicWorkRequest request = new PeriodicWorkRequest
    .Builder(MyWork.class, 30, TimeUnit.MINUTES)
    .build();
// 添加任務(wù)
WorkManager.getInstance().enqueue(request);

\color{#FF0000}{注意:PeriodicWorkRequest周期性任務(wù)最小間隔時(shí)間為15分鐘}

任務(wù)鏈(組合任務(wù),關(guān)聯(lián)任務(wù))

  • 可以將多個(gè)任務(wù)組合起來,可設(shè)置執(zhí)行的先后順序
  • 任務(wù)之間的結(jié)果又輸入輸出關(guān)系,上一個(gè)任務(wù)的輸出數(shù)據(jù)會(huì)是下一個(gè)任務(wù)輸入數(shù)據(jù),可通過Worker.getInputData()獲取輸入數(shù)據(jù),doWork()方法的返回值為輸出數(shù)據(jù):Result.success(data)
  • 任務(wù)鏈只能用于OneTimeWorkRequest

比如有四個(gè)任務(wù)A,B,C,D,我們需要先執(zhí)行完A和B兩個(gè)任務(wù),在執(zhí)行C任務(wù),最后在執(zhí)行D任務(wù),代碼如下:

WorkManager.getInstance()
    // 先執(zhí)行 A,B,并行執(zhí)行
    .beginWith(Arrays.asList(A, B))
    // 等 A 和 B 都執(zhí)行完成后,再執(zhí)行 C
    .then(C)
    // 等C執(zhí)行完,再執(zhí)行 D
    .then(D)
    // Don't forget to enqueue()
    .enqueue();

上面提到任務(wù)之間數(shù)據(jù)的輸入輸出關(guān)系,如果某個(gè)任務(wù)的前置任務(wù)是由多人任務(wù)組成,比如上面的例子:任務(wù)A和B的結(jié)果都會(huì)傳遞到任務(wù)C中,但任務(wù)結(jié)果Data對(duì)象是通過key-value的方式存儲(chǔ)數(shù)據(jù),如果存在相同的key結(jié)果如何組合傳遞給C,所以這里需要一個(gè)組合策略InputMerger,在這里WorkManager提供了兩種實(shí)現(xiàn):

  • OverwritingInputMerger 如果存在相同的key,就會(huì)覆蓋
  • ArrayCreatingInputMerger 合并結(jié)果,組合成一個(gè)列表

我們?cè)趧?chuàng)建任務(wù)C的WorkRequest時(shí)可通過setInputMerger()方法設(shè)置輸入數(shù)據(jù)組合規(guī)則

unique 任務(wù)(唯一的任務(wù))

unique任務(wù)在某種場(chǎng)景下會(huì)很實(shí)用,有點(diǎn)類似單例,指定名稱的任務(wù)/任務(wù)鏈只有一個(gè);再次添加相同名稱的任務(wù)時(shí),可以有幾種處理方式:

  • KEEP :保留原來的任務(wù),新添加的任務(wù)會(huì)被忽略
  • REPLACE :替換原來的任務(wù),原來的任務(wù)會(huì)被取消掉
  • APPEND :等原來的任務(wù)執(zhí)行完后,再執(zhí)行新添加的任務(wù)(這里貌似對(duì)唯一性的理解有些牽強(qiáng),可以理解成正執(zhí)行任務(wù)唯一性)

使用方法如下,主要調(diào)用:enqueueUniqueWork方法

// 唯一任務(wù)
WorkManager.getInstance()
    // my_work 唯一任務(wù)標(biāo)識(shí),ExistingWorkPolicy(KEEP,REPLACE,APPEND)
    .enqueueUniqueWork("unique_work_name", ExistingWorkPolicy.REPLACE, request);

// 唯一任務(wù)鏈
WorkManager.getInstance()
    .beginUniqueWork("unique_work_chain_name", ExistingWorkPolicy.REPLACE, Arrays.asList(requestA, requestB))
    .then(requestC)
    .then(requestD)
    .enqueue();

獲取任務(wù)狀態(tài),結(jié)果,取消任務(wù)

在任務(wù)整個(gè)生命周期中,有如下一些狀態(tài):

  • ENQUEUED :進(jìn)入隊(duì)列狀態(tài),在條件滿足的時(shí)候就會(huì)執(zhí)行
  • RUNNING :正在執(zhí)行
  • SUCCEEDED :doWork()返回Result.success() 后的狀態(tài),表明任務(wù)執(zhí)行成功,這種狀態(tài)只會(huì)在OneTimeWorkRequest中出現(xiàn)
  • FAILED :doWork()返回Result.failure() 后的狀態(tài),表示任務(wù)執(zhí)行失敗,也只會(huì)在OneTimeWorkRequest中出現(xiàn)
  • BLOCKED : 阻塞狀態(tài),一般出現(xiàn)在前一個(gè)任務(wù)還沒有執(zhí)行完(在unique任務(wù)的APPEND操作中)
  • CANCELLED :當(dāng)通過WorkManager取消一個(gè)沒有執(zhí)行完成的任務(wù)后的狀態(tài)
獲取任務(wù)信息:WorkInfo
  • WorkInfo包含了: 任務(wù)id(UUID), 任務(wù)狀態(tài)(State),任務(wù)結(jié)果(Data),任務(wù)tag(List<String>)
public WorkInfo(UUID id, State state, Data outputData, List<String> tags) {
    mId = id; // WorkRequest 的ID
    mState = state; // 任務(wù)當(dāng)前狀態(tài)
    mOutputData = outputData; // 任務(wù)執(zhí)行結(jié)果
    mTags = new HashSet<>(tags); // 任務(wù)tag,在創(chuàng)建WorkRequest設(shè)置的
}

WorkManager提供了以下的方法查詢WorkInfo信息,并提供了ListenableFuture和LiveData兩種結(jié)果返回方式:

ListenableFuture<WorkInfo> getWorkInfoById(UUID id)
LiveData<WorkInfo> getWorkInfoByIdLiveData(UUID id)

ListenableFuture<List<WorkInfo>> getWorkInfosByTag(String tag)
LiveData<List<WorkInfo>> getWorkInfosByTagLiveData(String tag)

ListenableFuture<List<WorkInfo>> getWorkInfosForUniqueWork(String uniqueWorkName);
LiveData<List<WorkInfo>> getWorkInfosForUniqueWorkLiveData(String uniqueWorkName)

獲取任務(wù)信息結(jié)合LiveData:

WorkManager.getInstance().getWorkInfoByIdLiveData(myWorkRequest.getId())
    .observe(lifecycleOwner, new Observer<WorkInfo>() {
        @Override
        public void onChanged(@Nullable WorkInfo workInfo) {
          if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
              displayMessage("You did good job!")
          }
        }
    });
取消任務(wù)
  • 取消任務(wù)框架并不能保證任務(wù)不被執(zhí)行(比如有些任務(wù)已經(jīng)開始執(zhí)行了或者已經(jīng)執(zhí)行完成了)
  • 如果是耗時(shí)任務(wù),在我們實(shí)現(xiàn)Worker的doWork()方法時(shí)可通過isStopped()方法檢測(cè)任務(wù)是否已被停止,如果被停止了則結(jié)束任務(wù)執(zhí)行,并回收資源

取消任務(wù)方法如下:

WorkManager.cancelAllWork()  取消所有任務(wù)
WorkManager.cancelWorkById(UUID id)  通過ID取消任務(wù)
WorkManager.cancelAllWorkByTag(String tag) 通過tag取消相關(guān)任務(wù)
WorkManager.cancelUniqueWork(String uniqueWorkName) 通過標(biāo)識(shí)取消唯一任務(wù)

WorkManager的Work實(shí)現(xiàn)

WorkManager提供了4種Work:Worker, CoroutineWorker, RxWorker, ListenableWorker

  • Worker

WorkManager會(huì)在后臺(tái)線程執(zhí)行我們的任務(wù),后臺(tái)線程來自默認(rèn)初始化時(shí)Configuration創(chuàng)建的Executor,當(dāng)然我們也可以自己初始化WorkManager,在上面初始化WorkManager有講到:

WorkManager.initialize(context,
    new Configuration.Builder()
        .setExecutor(Executors.newFixedThreadPool(8))
            .build());
  • CoroutineWorker

CoroutineWorker是Kotlin提供優(yōu)秀的協(xié)程實(shí)現(xiàn),示例如下:

class CoroutineDownloadWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result = coroutineScope {
        val jobs = (0 until 100).map {
            async {
                downloadSynchronously("https://www.google.com")
            }
        }

        // awaitAll will throw an exception if a download fails, which CoroutineWorker will treat as a failure
        jobs.awaitAll()
        Result.success()
    }
}

在這里doWork()是阻塞方法,CoroutineWorker跟Worker不一樣,它不在Executor中執(zhí)行,而是通過CoroutineDispatcher執(zhí)行,默認(rèn)提供了Dispatchers.IO來執(zhí)行后臺(tái)任務(wù),當(dāng)然你也可以自定義CoroutineDispatcher(通過重載CoroutineWorker變量:open val coroutineContext 實(shí)現(xiàn))

  • RxWorker

顧名思義是為RxJava提供的一種實(shí)現(xiàn),通過繼承RxWorker實(shí)現(xiàn)createWork()方法,返回的是RxJava中可訂閱對(duì)象:Single<Result>,跟Observable差不多

public class RxDownloadWorker extends RxWorker {

    public RxDownloadWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Single<Result> createWork() {
        return Observable.range(0, 100)
            .flatMap { download("https://www.google.com") }
            .toList()
            .map { return Result.success() };
    }
}

注意createWork()方法是在主線程調(diào)用,返回結(jié)果在后臺(tái)線程,通過重載 RxWorker.getBackgroundScheduler()自定義后臺(tái)線程

  • ListenableWorker

ListenableWorker提供更底層的任務(wù)執(zhí)行細(xì)節(jié),其任務(wù)執(zhí)行的入口方法是startWork()

public abstract @NonNull ListenableFuture<Result> startWork();

上面三種Worker都繼承自它,通過實(shí)現(xiàn)startWork方法,執(zhí)行任務(wù)不同調(diào)度方式,并抽象了具體任務(wù)doWork()方法,比如Worker的實(shí)現(xiàn)是:

public abstract @NonNull Result doWork();

@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;
}

如果你需要自己控制任務(wù)的執(zhí)行(執(zhí)行任務(wù)的線程),可以直接繼承ListenableWorker,并是現(xiàn)實(shí)startWork()方法,示例:

public class MyThreadWorker extends ListenableWorker {

    public MyThreadWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public ListenableFuture<Result> startWork() {
        @SuppressLint("RestrictedApi")
        final SettableFuture<Result> result = SettableFuture.create();
        new Thread(new Runnable() {
            @SuppressLint("RestrictedApi")
            @Override
            public void run() {
                Log.d(TAG, "i am doing work");
                result.set(Result.success());
            }
        }).run();
        return result;
    }
}

注意事項(xiàng)

  • Worker 都是通過反射實(shí)例化的,所以要注意自定義Worker的訪問權(quán)限,構(gòu)造函數(shù)為public,內(nèi)部類記得加上 public static 修飾符
  • WorkManager 適用于延期執(zhí)行任務(wù),或者是需要在app退出或者設(shè)備重啟后也能確保任務(wù)執(zhí)行
  • WorkManager不適用場(chǎng)景:正在運(yùn)行的進(jìn)程提供后臺(tái)任務(wù),需要立即執(zhí)行的后臺(tái)任務(wù)
  • PeriodicWorkRequest最小間隔時(shí)間為15分鐘

更多內(nèi)容見 官方文檔官方Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容