WorkManager 是一個 Android Jetpack 擴(kuò)展庫株婴,它可以讓您輕松規(guī)劃那些可延后恶座、異步但又需要可靠運(yùn)行的任務(wù)虏缸。對于絕大部分后臺執(zhí)行任務(wù)來說斜脂,使用 WorkManager 是目前 Android 平臺上的最佳實(shí)踐。
目前為止 WorkManager 系列已經(jīng)討論過:
在這篇文章中席怪,我們將討論:
- 在 Kotlin 中如何使用 WorkManager
- CoroutineWorker 類
- 如何使用 TestListenableWorkerBuilder 測試您的 CoroutineWorker 類
Kotlin 版的 WorkManager
本文的示例代碼是用 Kotlin 編寫的并使用了 KTX 庫 (Kotlin Extensions)瓦哎。KTX 版的 WorkManager 提供了更簡潔且慣用的 Kotlin 擴(kuò)展函數(shù)茸习。如 WorkManager 發(fā)布日志 中描述的那樣,只需要在 build.gradle 文件中添加 androidx.work:work-runtime-ktx 依賴項(xiàng)犯犁,即可使用 KTX 版的 WorkManager属愤。該組件包含 CoroutineWorker 和其他有用的 WorkManager 擴(kuò)展方法。
更簡潔且慣用
當(dāng)您需要構(gòu)造一個數(shù)據(jù)對象酸役,并且需要將它傳入Worker 類或者從 Worker 類返回時春塌,KTX 版 WorkManager 提供了一種語法糖。在這種情況下簇捍,用 Java 語法實(shí)現(xiàn)的代碼如下所示:
Data myData = new Data.Builder()
.putInt(KEY_ONE_INT, aInt)
.putIntArray(KEY_ONE_INT_ARRAY, aIntArray)
.putString(KEY_ONE_STRING, aString)
.build();
而在 Kotlin 中只壳,我們可以借助 workDataOf 輔助函數(shù)將代碼寫的更簡潔:
inline fun workDataOf(vararg pairs: Pair<String, Any?>): Data
因此可以將前面的 Java 表達(dá)式改寫成:
val data = workDataOf(
KEY_MY_INT to myIntVar,
KEY_MY_INT_ARRAY to myIntArray,
KEY_MY_STRING to myString
)
CoroutineWorker
除了可以使用 Java 實(shí)現(xiàn)的 Worker 類 (Worker、ListenableWorker 和 RxWorker) 之外暑塑,還有唯一一個使用 Kotlin 協(xié)程實(shí)現(xiàn)的 Work 類——CoroutineWorker吼句。
Worker 類與 CoroutineWorker 類的主要區(qū)別在于: CoroutineWorker 類的 doWork() 方法是一個可以執(zhí)行異步任務(wù)的掛起函數(shù),而 Worker 類的 doWork() 方法只能執(zhí)行同步任務(wù)事格。CoroutineWorker 的另一個特性是可以自動處理任務(wù)的暫停和取消惕艳,而 Worker 類需要實(shí)現(xiàn) onStopped() 方法來處理這些情況。
獲得完整上下文信息驹愚,請參閱官方文檔 在 WorkManager 中進(jìn)行線程處理远搪。在這里,我想重點(diǎn)介紹一下什么是 CoroutineWorker逢捺,并且涵蓋一些細(xì)小的但很重要的區(qū)別谁鳍,以及深入了解如何使用在 WorkManager v2.1 中引入的新測試特性,來測試您的 CoroutineWorker 類。
正如前面寫的那樣倘潜,CoroutineWorker#doWork() 只是一個掛起函數(shù)绷柒。它默認(rèn)是在 Dispatchers.Default 上啟動的:
class MyWork(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
// 做點(diǎn)什么
Result.success()
} catch (error: Throwable) {
Result.failure()
}
}
}
需要切記的是,這是使用 CoroutineWorker 代替 Worker 或 ListenableWorker 時的根本區(qū)別:
與 Worker 不同涮因,此代碼不會在 WorkManager 的 Configuration 中指定的 Executor 上運(yùn)行废睦。
正如剛才所說,CoroutineWorker#doWork() 默認(rèn)是在 Dispatchers.Default 啟動的养泡。您可以使用 withContext() 對此配置進(jìn)行自定義嗜湃。
class MyWork(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
return try {
// 做點(diǎn)什么
Result.success()
} catch (error: Throwable) {
Result.failure()
}
}
}
很少需要改變 CoroutineWorker 使用的 Dispatcher,因?yàn)?Dispatchers.Default 可以滿足大多數(shù)情況下的需求澜掩。
要了解關(guān)于如何在 Kotlin 中使用 WorkManager购披,可以嘗試這個 codelab。
測試 Worker 類
WorkManager 有幾個額外的工具類输硝,可以很方便地測試您的 Work今瀑。您可以在 WorkManager 測試文檔頁面 和新的 使用 WorkManager 2.1.0 進(jìn)行測試 的指南中了解更多相關(guān)信息。測試工具的原始實(shí)現(xiàn)使得自定義 WorkManager 成為可能点把,這樣一來我們便可以使其表現(xiàn)為同步執(zhí)行橘荠,進(jìn)而可以使用 WorkManagerTestInitHelper#getTestDriver() 來模擬延遲和測試周期性任務(wù)。
WorkManager v2.1 版中增加了一個新的工具類: TestListenableWorkerBuilder郎逃,它引入了一種全新的測試 Worker 類的方式哥童。
這對于 CoroutineWorker 類來說是一個非常重要的更新,因?yàn)槟梢酝ㄟ^ TestListenableWorkerBuilder 直接運(yùn)行 Worker 類褒翰,來測試它們的邏輯是否正確贮懈。
@RunWith(JUnit4::class)
class MyWorkTest {
private lateinit var context: Context
@Before
fun setup() {
context = ApplicationProvider.getApplicationContext()
}
@Test
fun testMyWork() {
// 獲取 ListenableWorker 的實(shí)例
val worker =
TestListenableWorkerBuilder<MyWork>(context).build()
// 同步的運(yùn)行 worker
val result = worker.startWork().get()
assertThat(result, `is`(Result.success()))
}
}
這里的重點(diǎn)是可以同步獲取 CoroutineWorker 的運(yùn)行結(jié)果,然后可以直接檢查 Worker 類的邏輯行為是否正確优训。
使用 TestListenableWorkerBuilder 也可以將輸入數(shù)據(jù)傳遞給 Worker 或設(shè)置 runAttemptCount朵你,這對于測試 Worker 內(nèi)部的重試邏輯是非常有用的。
比如揣非,如果要將一些數(shù)據(jù)上傳到服務(wù)器抡医,考慮到連接可能出現(xiàn)問題,您也許會添加一些重試邏輯:
class MyWork(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val serverUrl = inputData.getString("SERVER_URL")
return try {
// 通過 URL 做點(diǎn)什么
Result.success()
} catch (error: TitleRefreshError) {
if (runAttemptCount <3) {
Result.retry()
} else {
Result.failure()
}
}
}
}
然后您可以在測試中早敬,使用 TestListenableWorkerBuilder 來測試這個重試邏輯是否正確:
@Test
fun testMyWorkRetry() {
val data = workDataOf("SERVER_URL" to "[http://fake.url](http://fake.url)")
// 獲取 ListenableWorker忌傻,并將 RunAttemptCount 設(shè)置為 2
val worker = TestListenableWorkerBuilder<MyWork>(context)
.setInputData(data)
.setRunAttemptCount(2)
.build()
// 啟動同步執(zhí)行的任務(wù)
val result = worker.startWork().get()
assertThat(result, `is`(Result.retry()))
}
@Test
fun testMyWorkFailure() {
val data = workDataOf("SERVER_URL" to "[http://fake.url](http://fake.url)")
// 獲取 ListenableWorker,并將 RunAttemptCount 設(shè)置為 3
val worker = TestListenableWorkerBuilder<MyWork>(context)
.setInputData(data)
.setRunAttemptCount(3)
.build()
// 啟動同步執(zhí)行的任務(wù)
val result = worker.startWork().get()
assertThat(result, `is`(Result.failure()))
}
總結(jié)
隨著 WorkManager v2.1 以及 workManager-testing 中新特性的發(fā)布搞监,CoroutineWorker 因其簡單易用而大放光彩∷ⅲ現(xiàn)在您可以非常容易的對 Worker 類進(jìn)行測試,并且 WorkManager 在 Kotlin 中的整體使用體驗(yàn)也非常棒琐驴。
如果您還沒有在項(xiàng)目中使用 CoroutineWorker 以及 workmanager-runtime-ktx 中包含的其他擴(kuò)展俘种,強(qiáng)烈建議您在項(xiàng)目中使用它們秤标。當(dāng)使用 Kotlin 進(jìn)行開發(fā) (已經(jīng)成為我的日常) 時,這是我使用 WorkManager 的首選方式安疗。
希望這篇文章對您有所幫助抛杨,歡迎您在評論區(qū)積極留言够委,分享您在 WorkManager 使用中的見解或者問題荐类。