1. 程式人生 > >Android WorkManager官方指南

Android WorkManager官方指南

文章目錄

1. 使用WorkManager排程任務

WorkManager API可以方便地指定可延遲的非同步任務以及何時執行它們。這些API允許您建立任務並將其交給WorkManager立即執行或在適當的時機執行。

WorkManager根據裝置API級別和應用程式狀態等因素選擇適當的方法來執行您的任務。如果WorkManager在應用程式執行時執行您的任務之一,那麼WorkManager可以在應用程式的程序中的新執行緒中執行您的任務。如果您的應用程式沒有執行,WorkManager會選擇適當的方式來排程後臺任務——這取決於裝置API級別和包含的依賴,WorkManager可以使用

JobSchedulerFirebase JobDispatcherAlarmManager。您不需要編寫裝置邏輯來確定裝置具有什麼功能,並選擇合適的API;相反,您可以將任務交給WorkManager,讓它選擇最佳選項。

注意:WorkManager用於需要保證即使應用程式退出系統也會執行它們的任務,比如將應用程式資料上傳到伺服器。它不是用於程序內後臺工作,如果應用程式程序消失了,可以安全地終止;對於這樣的情況,我們建議使用執行緒池

1.1 主題

WorkManager基礎

使用WorkManager在您選擇的環境下排程單個任務,或者以指定的間隔重複執行任務。

1.2
WorkManager高階功能

設定鏈式任務序列,設定傳遞和返回值的任務,並設定命名的、獨特的工作序列。

1.3 附加資源

WorkManager是Android Jetpack架構元件。在Sunflower演示應用程式中使用它。

2. WorkManager基礎

使用WorkManager,您可以輕鬆地設定一個任務並將其交給系統,該任務可以在您指定的條件下執行。

這個概述涵蓋了最基本的WorkManager特性。在這個頁面中,您將學習如何設定任務,指定它應該執行的條件,並將其交給系統。您還將學習如何設定重複作業。

有關更高階的WorkManager特性的資訊,如作業連結以及傳遞和返回值,請參閱WorkManager高階特性。還有更多可用的特性;對於詳細資訊,請參閱WorkManager參考文件

注意:要將WorkManager庫匯入到Android專案中,請參見將元件新增到專案中

2.1 類與概念

WorkManager API使用了幾個不同的類。在某些情況下,您需要對API類中的一個子類進行分類。

這些是最重要的WorkManager類:

Worker:指定需要執行的任務。WorkManager APIs包括抽象Worker類。您繼承此類並在此處執行工作。

WorkRequest:表示一個單獨的任務。至少,WorkRequest物件指定哪個Worker類應該執行任務。但是,您還可以向WorkRequest物件新增細節,指定任務應該在哪些環境下執行。每個WorkRequest都有一個自動生成的惟一ID;可以使用該ID執行諸如取消佇列中的任務或獲取任務的狀態之類的操作。WorkRequest是一個抽象類;在程式碼中,您將使用某個子類,OneTimeWorkRequestPeriodicWorkRequest

WorkRequest.Builder:建立WorkRequest物件的幫助類。再次,您將使用一個子類,OneTimeWorkRequest.BuilderPeriodicWorkRequest.Builder

Constraints:指定限制任務什麼時候執行(例如,“僅當裝置連線到網路時”)。你通過建立Constraints.Builder建立 Constraints物件。在建立WorkRequest之前,傳遞Constraints 物件給WorkRequest.Builder。

WorkManager:對工作請求進行排隊和管理。您將WorkRequest物件傳遞給WorkManager來執行任務。WorkManager以分散系統資源負載的方式排程任務,同時遵守您指定的約束。

WorkStatus:包含有關特定任務的資訊。WorkManager為每個WorkRequest物件提供LiveData。LiveData儲存一個WorkStatus物件;通過觀察LiveData,您可以確定當前任務的狀態,並在任務完成後獲得到返回值。

2.2 典型工作流

假設你正在編寫一個photo library app,並且該應用程式需要週期性地壓縮其儲存的影象。您希望使用WorkMeaveAPI來安排影象壓縮。在這種情況下,您並不特別關心壓縮什麼時候發生;您想設定任務並忘記它。

首先,您將定義Worker類,並重寫它的doWork()方法。您的worker類指定如何執行該操作,但沒有任何有關任務何時執行的資訊。

KOTLIN

class CompressWorker : Worker()  {

    override fun doWork(): Result {

        // Do the work here--in this case, compress the stored images.
        // In this example no parameters are passed; the task is
        // assumed to be "compress the whole library."
        myCompress()

        // Indicate success or failure with your return value:
        return Result.SUCCESS

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)

    }

}

JAVA

public class CompressWorker extends Worker {
    @Override
    public Worker.Result doWork() {

        // Do the work here--in this case, compress the stored images.
        // In this example no parameters are passed; the task is
        // assumed to be "compress the whole library."
        myCompress();

        // Indicate success or failure with your return value:
        return Result.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

接下來,基於Worker建立OneTimeWorkRequest物件,然後使用WorkManager將任務排到佇列中:
KOTLIN

val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>().build()
WorkManager.getInstance().enqueue(compressionWork)

JAVA

OneTimeWorkRequest compressionWork =
        new OneTimeWorkRequest.Builder(CompressWorker.class)
    .build();
WorkManager.getInstance().enqueue(compressionWork);

WorkManager選擇適當的時間來執行任務,平衡諸如系統負載、裝置是否插入,等等。在大多數情況下,如果不指定任何約束,WorkManager立即執行您的任務。如果需要檢查任務狀態,可以通過獲取適當的LiveData控制代碼來獲得WorkStatus物件。例如,如果您想檢查任務是否已完成,您可以使用這樣的程式碼:

KOTLIN

WorkManager.getInstance().getStatusById(compressionWork.id)
                .observe(lifecycleOwner, Observer { workStatus ->
                    // Do something with the status
                    if (workStatus != null && workStatus.state.isFinished) {
                        // ...
                    }
                })

JAVA

WorkManager.getInstance().getStatusById(compressionWork.getId())
    .observe(lifecycleOwner, workStatus -> {
        // Do something with the status
        if (workStatus != null && workStatus.getState().isFinished()) {
            // ...
        }
    });

有關使用LiveData的更多資訊,請參見LiveData概述

2.3 任務約束

如果希望,可以指定任務執行時的約束。例如,您可能希望指定該任務只在裝置空閒時執行,並連線到電源。在這種情況下,您需要建立一個OneTimeWorkRequest.Builder物件,並使用該生成器建立實際的OneTimeWorkRequest

KOTLIN

// Create a Constraints object that defines when the task should run
val myConstraints = Constraints.Builder()
        .setRequiresDeviceIdle(true)
        .setRequiresCharging(true)
        // Many other constraints are available, see the
        // Constraints.Builder reference
        .build()

// ...then create a OneTimeWorkRequest that uses those constraints
val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>()
        .setConstraints(myConstraints)
        .build()

JAVA

// Create a Constraints object that defines when the task should run
Constraints myConstraints = new Constraints.Builder()
    .setRequiresDeviceIdle(true)
    .setRequiresCharging(true)
    // Many other constraints are available, see the
    // Constraints.Builder reference
     .build();

// ...then create a OneTimeWorkRequest that uses those constraints
OneTimeWorkRequest compressionWork =
                new OneTimeWorkRequest.Builder(CompressWorker.class)
     .setConstraints(myConstraints)
     .build();

然後像以前一樣將新的OneTimeWorkRequest請求物件傳遞給WorkManager.enqueue()。WorkManager在查詢執行任務的時間時會考慮約束。

2.4 取消任務

您可以在任務加入佇列後取消任務。要取消任務,您需要它的工作ID,您可以從WorkRequest物件中獲取它。例如,下面的程式碼取消了前一節的壓縮工作請求:

KOTLIN

val compressionWorkId:UUID = compressionWork.getId()
WorkManager.getInstance().cancelWorkById(compressionWorkId)

JAVA

UUID compressionWorkId = compressionWork.getId();
WorkManager.getInstance().cancelWorkById(compressionWorkId);

WorkManager盡最大努力取消任務,但這樣存在很大的不確定性——當您試圖取消任務時,任務可能已經執行或完成。WorkManager還提供方法,以取消唯一工作序列中的所有任務,或者取消具有指定標記的所有任務,這也是盡最大努力取消任務的基礎上。

2.5 Tagged work

您可以通過將標記字串分配給任何WorkRequest物件來對任務進行邏輯分組。若要設定標籤,請呼叫WorkRequest.Builder.addTag(),例如:

KOTLIN

val cacheCleanupTask =
        OneTimeWorkRequestBuilder<MyCacheCleanupWorker>()
    .setConstraints(myConstraints)
    .addTag("cleanup")
    .build()

JAVA

OneTimeWorkRequest cacheCleanupTask =
        new OneTimeWorkRequest.Builder(MyCacheCleanupWorker.class)
    .setConstraints(myConstraints)
    .addTag("cleanup")
    .build();

WorkManager類提供了幾種實用方法,允許您使用特定標記操作所有任務。例如,WorkManager.cancelAllWorkByTag()取消具有特定標記的所有任務,WorkManager.getStatusesByTag()返回具有該標記的所有任務的WorkStatus列表。

2.6 週期性任務

您可能有一個需要重複執行的任務。例如,照片管理器APP壓縮照片可能不止一次。更有可能的是,它會經常檢查其共享照片,並檢視是否有新的或更改的影象需要壓縮。這個重複的任務可以壓縮它找到的影象,或者,當它找到需要壓縮的影象時,它可以啟動新的“壓縮此影象”任務。

要建立迴圈任務,請使用PeriodicWorkRequest.Builder類建立PeriodicWorkRequest物件,然後以與OneTimeWorkRequest物件相同的方式將PeriodicWorkRequest加入佇列。例如,假設我們定義了一個PhotoCheckWorker類來識別需要壓縮的影象。如果您想每12小時執行一次清單任務,您將建立一個像這樣的PeriodicWorkRequest物件:

KOTLIN

val photoCheckBuilder =
        PeriodicWorkRequestBuilder<PhotoCheckWorker>(12, TimeUnit.HOURS)
// ...if you want, you can apply constraints to the builder here...

// Create the actual work object:
val photoCheckWork = photoCheckBuilder.build()
// Then enqueue the recurring task:
WorkManager.getInstance().enqueue(photoCheckWork)

JAVA

new PeriodicWorkRequest.Builder photoCheckBuilder =
        new PeriodicWorkRequest.Builder(PhotoCheckWorker.class, 12,
                                        TimeUnit.HOURS);
// ...if you want, you can apply constraints to the builder here...

// Create the actual work object:
PeriodicWorkRequest photoCheckWork = photoCheckBuilder.build();
// Then enqueue the recurring task:
WorkManager.getInstance().enqueue(photoCheckWork);

WorkManager試圖在您請求的時間間隔內執行您的任務,但要遵守您施加的約束以及其它要求。

3. WorkManager高階主題

WorkManager使建立和預約複雜的任務請求變得容易。您可以使用API來實現這樣的場景:

3.1 Chained tasks

應用程式可能需要按特定順序執行多個任務。WorkManager允許您建立多個任務並將其加入工作序列中,並指定它們應該執行的順序。

例如,假設應用程式有三個OneTimeWorkRequest物件:WorkA、WorkB和WorkC。任務必須按該順序執行。要使它們入隊,使用WorkManager.beginWith()方法建立一個序列,傳遞第一個OneTimeWorkRequest物件;該方法返回一個WorkContinuation物件,該物件定義一系列任務。然後按順序使用WorkContinuation.then()新增剩餘的OneTimeWorkRequest物件,最後使用WorkContinuation.enqueue()對整個序列進行入隊:

KOTLIN

WorkManager.getInstance()
    .beginWith(workA)
        // Note: WorkManager.beginWith() returns a
        // WorkContinuation object; the following calls are
        // to WorkContinuation methods
    .then(workB)    // FYI, then() returns a new WorkContinuation instance
    .then(workC)
    .enqueue()

JAVA

WorkManager.getInstance()
    .beginWith(workA)
        // Note: WorkManager.beginWith() returns a
        // WorkContinuation object; the following calls are
        // to WorkContinuation methods
    .then(workB)    // FYI, then() returns a new WorkContinuation instance
    .then(workC)
    .enqueue();

根據每個任務指定的約束,WorkManager按要求的順序執行任務。如果任何任務返回Worker.Result.FAILURE,則整個序列結束。

您還可以將多個OneTimeWorkRequest物件傳遞給任何beginWith()和.then()呼叫。如果將幾個OneTimeWorkRequest物件傳遞給一個方法呼叫,WorkManager在執行序列的其餘部分之前(並行地)執行所有這些任務。例如:

KOTLIN

WorkManager.getInstance()
    // First, run all the A tasks (in parallel):
    .beginWith(workA1, workA2, workA3)
    // ...when all A tasks are finished, run the single B task:
    .then(workB)
    // ...then run the C tasks (in any order):
    .then(workC1, workC2)
    .enqueue()

JAVA

WorkManager.getInstance()
    // First, run all the A tasks (in parallel):
    .beginWith(workA1, workA2, workA3)
    // ...when all A tasks are finished, run the single B task:
    .then(workB)
    // ...then run the C tasks (in any order):
    .then(workC1, workC2)
    .enqueue();

您可以通過使用WorkContinuation.combine()方法將多個鏈合併來建立更復雜的序列。例如,假設您想執行這樣的序列:

image

圖1 可以使用WorkContinuation設定複雜鏈式任務。

要設定這個序列,建立兩個獨立的鏈,然後將它們組合成第三個:

KOTLIN

val chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)
val chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD)
val chain3 = WorkContinuation
    .combine(chain1, chain2)
    .then(workE)
chain3.enqueue()

JAVA

WorkContinuation chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB);
WorkContinuation chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD);
WorkContinuation chain3 = WorkContinuation
    .combine(chain1, chain2)
    .then(workE);
chain3.enqueue();

在這種情況下,WorkMeor在工作B之前執行WorkA。它還執行WorkC之前Word。工作和工作完成後,WorkMeor執行WORKE。

注意:雖然WorkManager按順序執行每個子鏈,但是不能保證鏈1中的任務如何與鏈2中的任務重疊。例如,WorkB可能在workC之前或之後執行,或者它們可能同時執行。唯一的保證是每個子鏈中的任務將按順序執行;也就是說,workB直到workA完成後才啟動。

存在一些特定情況,有一系列WorkContinuation方法提供短時處理。例如,有一個WorkContinuation.combine(OneTimeWorkRequest,WorkContinuation…)方法,它指示WorkManager完成所有指定的WorkContinuation鏈,然後以指定的OneTimeWorkRequest結束。有關詳細資訊,請參見WorkContinuation。

3.2 Unique工作序列

您可以建立一個Unique的工作序列,通過呼叫beginUniqueWork()而不是beginWith()啟動序列。每個唯一的工作序列都有一個名稱;WorkManager只允許一次使用一個名稱的工作序列。在建立新的唯一工作序列時,指定如果已經存在具有相同名稱的未完成序列,則WorkManager應該做什麼:

  • 取消現有的序列並將其替換為新的序列

  • Keep現有的順序,忽略您的新請求

  • 將新序列Append到現有序列上,在現有序列的最後一個任務完成後執行新序列的第一個任務

如果您有一個不應該多次排隊的任務,那麼Unique工作序列可能是有用的。例如,如果您的app需要將其資料同步到網路,則可以將名為“sync”的序列加入佇列,並指定如果已經存在具有該名稱的序列,則應忽略新任務。如果你需要逐步建立一個長的任務鏈,那麼Unique工作序列也是有用的。例如,一個圖片編輯應用程式可以讓使用者撤消一長串的動作。每一個撤消操作可能需要一段時間,但它們必須按正確的順序執行。在這種情況下,應用程式可以建立一個"undo"鏈,並根據需要將每個撤銷操作附加到鏈上。

3.3 輸入引數和返回值

為了獲得更大的靈活性,可以將引數傳遞給任務並使任務返回結果。傳遞和返回的值是鍵值對。若要將引數傳遞給任務,請在建立WorkRequest物件之前呼叫WorkRequest.Builder.setInputData()方法。該方法使用Data.Builder建立並持有資料物件。Work類可以通過呼叫Worker.getInputData()來訪問這些引數。為了輸出返回值,任務呼叫Worker.setOutputData(),它接受一個Data物件;您可以通過觀察任務的LiveData來獲得輸出。

例如,假設您有一個Worker類,執行耗時的計算。下面的程式碼顯示了Worker類的外觀:

KOTLIN

// Define the parameter keys:
const val KEY_X_ARG = "X"
const val KEY_Y_ARG = "Y"
const val KEY_Z_ARG = "Z"

// ...and the result key:
const val KEY_RESULT = "result"

// Define the Worker class:
class MathWorker : Worker()  {

    override fun doWork(): Result {
        val x = inputData.getInt(KEY_X_ARG, 0)
        val y = inputData.getInt(KEY_Y_ARG, 0)
        val z = inputData.getInt(KEY_Z_ARG, 0)

        // ...do the math...
        val result = myCrazyMathFunction(x, y, z);

        //...set the output, and we're done!
        val output: Data = mapOf(KEY_RESULT to result).toWorkData()
        setOutputData(output)

        return Result.SUCCESS
    }
}

JAVA

// Define the Worker class:
public class MathWorker extends Worker {

    // Define the parameter keys:
    public static final String KEY_X_ARG = "X";
    public static final String KEY_Y_ARG = "Y";
    public static final String KEY_Z_ARG = "Z";
    // ...and the result key:
    public static final String KEY_RESULT = "result";

    @Override
    public Worker.Result doWork() {


        // Fetch the arguments (and specify default values):
        int x = getInputData().getInt(KEY_X_ARG, 0);
        int y = getInputData().getInt(KEY_Y_ARG, 0);
        int z = getInputData().getInt(KEY_Z_ARG, 0);

        // ...do the math...
        int result = myCrazyMathFunction(x, y, z);

        //...set the output, and we're done!
        Data output = new Data.Builder()
            .putInt(KEY_RESULT, result)
            .build();
        setOutputData(output);
        return Result.SUCCESS;
    }
}

要建立work並傳遞引數,您將使用這樣的程式碼:

KOTLIN

val myData: Data = mapOf("KEY_X_ARG" to 42,
                       "KEY_Y_ARG" to 421,
                       "KEY_Z_ARG" to 8675309)
                     .toWorkData()

// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
        .setInputData(myData)
        .build()
WorkManager.getInstance().enqueue(mathWork)

JAVA

// Create the Data object:
Data myData = new Data.Builder()
    // We need to pass three integers: X, Y, and Z
    .putInt(KEY_X_ARG, 42)
    .putInt(KEY_Y_ARG, 421)
    .putInt(KEY_Z_ARG, 8675309)
    // ... and build the actual Data object:
    .build();

// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
OneTimeWorkRequest mathWork = new OneTimeWorkRequest.Builder(MathWorker.class)
        .setInputData(myData)
        .build();
WorkManager.getInstance().enqueue(mathWork);

任務的WorkStatus中返回的值是可用:

KOTLIN

WorkManager.getInstance().getStatusById(mathWork.id)
        .observe(this, Observer { status ->
            if (status != null && status.state.isFinished) {
                val m