[譯] WorkManager 基礎入門

WorkManager 基礎入門

插圖來自 Virginia Poltrackhtml

歡迎來到咱們 WorkManager 系列的第二篇文章。WorkManager 是一個 Android Jetpack 庫,當知足工做的約束條件時,用來運行可延遲、須要保障的後臺工做。對於許多類型的後臺工做,WorkManager 是當前的最佳實踐方案。在第一篇博文中,咱們討論了 WorkManager 是什麼以及什麼時候使用 WorkManager。前端

在這篇博文中,我將介紹:java

  • 將你的後臺任務定義爲工做
  • 定義特定的工做應該如何運行
  • 運行你的工做
  • 使用鏈進行存在依賴的工做
  • 監視你的工做的狀態

我還將解釋 WorkManager 幕後發生的事情,以便你能夠就如何使用它作出明智的決定。android

從一個例子開始

假設你有一個圖片編輯應用,可以讓你給圖像加上濾鏡並將其上傳到網絡讓全世界看到。你但願建立一系列後臺任務,這些任務用於濾鏡,壓縮圖像和以後的上傳。在每一個環節,都有一個須要檢查的約束——給圖像加濾鏡時要有足夠的電量,壓縮圖像時要有足夠的存儲空間,以及上傳圖像時要有網絡鏈接。ios

這個例子如上圖所示git

這個例子正是具備如下特色的任務:github

  • 可延遲的,由於你不須要它當即執行,並且實際上可能但願等待某些約束被知足(例如等待網絡鏈接)。
  • 須要確保可以運行,不管應用程序是否退出,由於若是加了濾鏡後的圖像永遠沒能與世界共享,你的用戶會很是不滿意!

這些特色使咱們的圖像加濾鏡和上傳任務成爲 WorkManager 的完美用例。數據庫

添加 WorkManager 依賴

本文使用 Kotlin 書寫代碼,使用 KTX 庫(KoTlin eXtensions)。KTX 版本的庫提供了 擴展函數 爲了更簡潔和習慣的使用 Kotlin。你能夠添加以下依賴來使用 KTX 版本的 WorkManager:後端

dependencies {
 def work_version = "1.0.0-beta02"
 implementation "android.arch.work:work-runtime-ktx:$work_version"
}
複製代碼

你能夠在 這裏](developer.android.com/topic/libra…) 到該庫的最新版本。若是你想使用 Java 依賴,那就移除「-ktx」。數組

定義你的 work 作什麼

在咱們將多個任務鏈接在一塊兒以前,讓咱們關注如何執行一項工做。我將會着重細說上傳任務。首先,你須要建立本身的 Worker 實現類。我將會把咱們的類命名爲 UploadWorker,而後重寫 doWork() 方法。

Workers:

  • 定義你的工做實際作了什麼。
  • 接受輸入併產生輸出。輸入和輸出都以鍵值對錶示。
  • 始終返回表示成功,失敗或重試的值。

這是一個示例,展現瞭如何實現上傳圖像的 Worker

class UploadWorker(appContext: Context, workerParams: WorkerParameters)
    : Worker(appContext, workerParams) {

    override fun doWork(): Result {
        try {
            // Get the input
            val imageUriInput = inputData.getString(Constants.KEY_IMAGE_URI)

            // Do the work
            val response = upload(imageUriInput)

            // Create the output of the work
            val imageResponse = response.body()
            val imgLink = imageResponse.data.link
            // workDataOf (part of KTX) converts a list of pairs to a [Data] object.
            val outputData = workDataOf(Constants.KEY_IMAGE_URI to imgLink)

            return Result.success(outputData)

        } catch (e: Exception) {
            return Result.failure()
        }
    }

    fun upload(imageUri: String): Response {
        TODO(「Webservice request code here」)
        // Webservice request code here; note this would need to be run
        // synchronously for reasons explained below.
    }

}
複製代碼

有兩點須要注意:

  • 輸入和輸出是做爲 Data 傳遞,它本質上是原始類型和數組的映射。Data 對象應該至關小 —— 實際上能夠輸入/輸出的總大小有限制。這由 MAX_DATA_BYTES 設置。若是您須要將更多數據傳入和傳出 Worker,則應將數據放在其餘地方,例如 Room database。做爲一個例子,我傳入上面圖像的 URI,而不是圖像自己。
  • 在代碼中,我展現了兩個返回示例:Result.success()Result.failure()。還有一個 Result.retry() 選項,它將在以後的時間再次重試你的工做。

定義您的 work 應該如何運行

一方面 Worker 定義工做的做用,另外一方面 WorkRequest 定義應該如何以及什麼時候運行工做

如下是爲 UploadWorker 建立 OneTimeWorkRequest 的示例。也能夠有重複性的 PeriodicWorkRequest

// workDataOf (part of KTX) converts a list of pairs to a [Data] object.
val imageData = workDataOf(Constants.KEY_IMAGE_URI to imageUriString)

val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
        .setInputData(imageData)
        .build()
複製代碼

WorkRequest 將輸入 imageData: Data 對象,並儘快運行。

假設 UploadWork 並不老是應該當即運行 —— 它應該只在設備有網絡鏈接時運行。你能夠經過添加 Constraints 對象來完成此操做。你能夠建立這樣的約束:

val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build()
複製代碼

如下是其餘受支持約束的示例:

val constraints = Constraints.Builder()
        .setRequiresBatteryNotLow(true)
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .setRequiresCharging(true)
        .setRequiresStorageNotLow(true)
        .setRequiresDeviceIdle(true)
        .build()
複製代碼

最後,還記得 Result.retry() 嗎?我以前說過,若是 Worker 返回 Result.retry(),WorkManager 將從新計劃工做。你能夠在建立新的 WorkRequest 時自定義退避條件。這容許你定義什麼時候應重試運行。

退避條件由兩個屬性定義:

  • BackoffPolicy,默認爲指數性的,可是能夠設置爲線性。
  • 持續時間,默認爲 30 秒。

用於對上傳工做進行排隊的組合代碼以下,包括約束,輸入和自定義退避策略:

// Create the Constraints
val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build()

// Define the input
val imageData = workDataOf(Constants.KEY_IMAGE_URI to imageUriString)

// Bring it all together by creating the WorkRequest; this also sets the back off criteria
val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
        .setInputData(imageData)
        .setConstraints(constraints)        
        .setBackoffCriteria(
                BackoffPolicy.LINEAR, 
                OneTimeWorkRequest.MIN_BACKOFF_MILLIS, 
                TimeUnit.MILLISECONDS)
        .build()
複製代碼

運行 work

這些都很好,但你尚未真正調度好你的工做去運行。如下是告訴 WorkManager 調度工做所需的一行代碼:

WorkManager.getInstance().enqueue(uploadWorkRequest)
複製代碼

你首先須要獲取 WorkManager 的實例,這是一個負責執行你的工做的單例。調用 enqueue 來啓動 WorkManager 跟蹤和調度工做的整個過程。

在幕後 —— 工做是怎麼運行的

那麼,WorkManager 能爲您作些什麼呢?默認狀況下,WorkManager 會:

  • 脫離主線程運行你的工做(假設你正在繼承 Worker 類,如上面的 UploadWorker 所示)。
  • 保障 你的工做將會運行(即便你重啓設備或應用程序退出,它也不會忘記運行你的工做)。
  • 根據用戶 API 級別的最佳實踐運行(如上一篇文章所述)。

讓咱們探討一下 WorkManager 如何確保你的工做脫離主線程運行並保證執行。在幕後,WorkManager 包括如下部分:

  • 內部 TaskExecutor:一個單線程 Executor,處理全部排隊工做的請求。若是您不熟悉 Executors,能夠在這裏閱讀更多相關信息。

  • WorkManager 數據庫:一個本地數據庫,可跟蹤全部工做的全部信息和狀態。這包括工做的當前狀態,工做的輸入和輸出以及對工做的任何約束限制。此數據庫使 WorkManager 可以保證你的工做可以完成 —— 若是你的用戶的設備從新啓動而且工做中斷,則能夠從數據庫中提取工做的全部詳細信息,並在設備再次啓動時從新啓動工做。

  • WorkerFactory:一個默認工廠,用於建立 Worker 的實例。咱們將在之後的博文中介紹爲何以及如何配置它。

  • Default Executor:一個默認的執行程序,運行你的工做,除非你另行指定。這確保在默認狀況下,你的工做是同步運行的,而且在主線程以外運行。

  • 這些部分能夠被重寫以具備不一樣的行爲。

來自:Working with WorkManager Android 開發者大會展現 2018

當你安排 WorkRequest

  1. 內部 TaskExecutor 當即將你的 WorkRequest 信息保存到 WorkManager 數據庫。
  2. 稍後,當知足 WorkRequestConstraints 時(能夠當即發生),Internal TaskExecutor 會告訴 WorkerFactory 建立一個 Worker
  3. 以後,默認的 Executor 調用你的 WorkerdoWork() 方法脫離主線程

經過這種方式,默認狀況下,你的工做均可以保證執行脫離主線程運行。

如今,若是你想使用除默認 Executor 以外的一些其餘機制來運行你的工做,也是能夠的!對協程(CoroutineWorker)和 RxJava(RxWorker)的開箱即用支持做爲工做的手段。

或者,你可使用 ListenableWorker 準確指定工做的執行方式。Worker 其實是 ListenableWorker 的一個實現,它默認在默認的 Executor 上運行你的工做,所以是同步的。因此,若是你想要徹底控制工做的線程策略或異步運行工做,你能夠將 ListenableWorker 子類化(具體細節將在後面的文章中討論)。

WorkManager 雖然將全部工做信息保存到數據庫中有些麻煩,但它仍是會作,這使得它成了很是適合須要保障執行的任務。這也是使得 WorkManager 輕鬆應對對於不須要保障且只須要在後臺線程上執行的任務的的緣由。例如,假設你已經下載了圖像,而且但願根據該圖像更改 UI 部分的顏色。這是應該脫離主線程運行的工做,可是,由於它與 UI 直接相關,因此若是關閉應用程序則不須要繼續。因此在這樣的狀況下,不要使用 WorkManager —— 堅持使用像 Kotlin 協程那樣輕量的東西或建立本身的 Executor

使用鏈進行依賴性工做

咱們的濾鏡示例包含的不只僅是一個任務 —— 咱們想要給多個圖像加濾鏡,而後壓縮並上傳。若是要一個接一個地或並行地運行一系列 WorkRequests,則可使用 。示例圖顯示了一個鏈,其中有三個並行運行的濾鏡任務,後面是壓縮任務和上傳任務,按順序運行:

使用 WorkManager 很是簡單。假設你已經用適當的約束建立了全部 WorkRequests,代碼以下所示:

WorkManager.getInstance()
    .beginWith(Arrays.asList(
                             filterImageOneWorkRequest, 
                             filterImageTwoWorkRequest, 
                             filterImageThreeWorkRequest))
    .then(compressWorkRequest)
    .then(uploadWorkRequest)
    .enqueue()
複製代碼

三個圖片濾鏡 WorkRequests 並行執行。一旦完成全部濾鏡 WorkRequests (而且只有完成全部三個),就會發生 compressWorkRequest,而後是 uploadWorkRequest

鏈的另外一個優勢是:一個 WorkRequest 的輸出做爲下一個 WorkRequest 的輸入。所以,假設你正確設置了輸入和輸出數據,就像我上面的 UploadWorker 示例所作的那樣,這些值將自動傳遞。

爲了處理並行的三個濾鏡工做請求的輸出,可使用 InputMerger,特別是 ArrayCreatingInputMerger。代碼以下:

val compressWorkRequest = OneTimeWorkRequestBuilder<CompressWorker>()
        .setInputMerger(ArrayCreatingInputMerger::class.java)
        .setConstraints(constraints)
        .build()
複製代碼

請注意,InputMerger 是添加到 compressWorkRequest 中的,而不是並行的三個濾鏡請求中的。

假設每一個濾鏡工做請求的輸出是映射到圖像 URI 的鍵 「KEY_IMAGE_URI」。添加 ArrayCreatingInputMerger 的做用是並行請求的輸出,當這些輸出具備匹配的時,它會建立一個包含全部輸出值的數組,映射到單個鍵。可視化圖表以下:

ArrayCreatingInputMerger 功能可視化

所以,compressWorkRequest 的輸入將最終成爲映射到濾鏡圖像 URI 數組的 「KEY_IMAGE_URI」 對。

觀察你的 WorkRequest 狀態

監視工做的最簡單方法是使用 LiveData 類。若是你不熟悉 LiveData,它是一個生命週期感知的可監視數據持有者 —— 這裏 對此有更詳細的描述。

調用 getWorkInfoByIdLiveData 返回一個 WorkInfoLiveDataWorkInfo 包含輸出的數據和表示工做狀態的枚舉。當工做順利完成後,它的 State 就會是 SUCCEEDED。所以,例如,你能夠經過編寫一些監視代碼來實現當工做完成時自動顯示該圖像:

// In your UI (activity, fragment, etc)
WorkManager.getInstance().getWorkInfoByIdLiveData(uploadWorkRequest.id)
        .observe(lifecycleOwner, Observer { workInfo ->
            // Check if the current work's state is "successfully finished" if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) { displayImage(workInfo.outputData.getString(KEY_IMAGE_URI)) } }) 複製代碼

有幾點須要注意:

  • 每一個 WorkRequest 都有一個惟一的 id,該惟一 id 是查找關聯 WorkInfo 的一種方法。
  • WorkInfo 更改時進行監視並被通知的能力是 LiveData 提供的功能。

工做有一個由不一樣 State 表明的生命週期。監視 LiveData<WorkInfo> 時,你會看到這些狀態;例如,你可能會看到:

「happy path」 或工做狀態

工做狀態經歷的 「happy path」 以下:

  1. BLOCKED:只有當工做在鏈中而且不是鏈中的下一個工做時纔會出現這種狀態。
  2. ENQUEUED:只要工做是工做鏈中的下一個而且有資格運行,工做就會進入這個狀態。這項工做可能仍在等待 Constraint 被知足。
  3. RUNNING:在這種狀態時,工做正在運行。對於 Worker,這意味着 doWork() 方法已經被調用。
  4. SUCCEEDED:當 doWork() 返回 Result.success() 時,工做進入這種最終狀態。

如今,當工做處於 RUNNING 狀態,你能夠調用 Result.retry()。這將會致使工做退回 ENQUEUED 狀態。工做也可能隨時被 CANCELLED

若是工做運行的結果是 Result.failure() 而不是成功。它的狀態將會以 FAILED 結束,所以,狀態的完整流程圖以下所示:

(來自:Working with WorkManager Android 開發者峯會 2018)

想看精彩的視頻講解,請查看 WorkManager Android 開發者峯會演講

總結

這就是 WorkManager API 的基礎知識。使用咱們剛剛介紹的代碼片斷,你如今就能夠:

  • 建立包含輸入/輸出的 Worker
  • 使用 WorkRequestConstraint、啓動輸入和退出策略配置 Worker 的運行方式。
  • 調度 WorkRequest
  • 瞭解默認狀況下 WorkManager 在線程和保障運行方面的幕後工做。
  • 建立複雜鏈式相互依賴的工做,能夠順序運行和並行運行。
  • 使用 WorkInfo 監視你的 WorkRequest 的狀態。

想親自試試 WorkManager 嗎?查看 codelab,包含 KotlinJava 代碼。

隨着咱們繼續更新本系列,請繼續關注有關 WorkManager 主題的更多博客文章。 有什麼問題或者你但願咱們寫到的東西嗎?請在評論區告訴咱們!

感謝 Pietro Maggi

WorkManager 相關資源

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索