WorkManager 在 Kotlin 中的實(shí)踐

image

WorkManager 是一個 Android Jetpack 擴(kuò)展庫株婴,它可以讓您輕松規(guī)劃那些可延后恶座、異步但又需要可靠運(yùn)行的任務(wù)虏缸。對于絕大部分后臺執(zhí)行任務(wù)來說斜脂,使用 WorkManager 是目前 Android 平臺上的最佳實(shí)踐。

目前為止 WorkManager 系列已經(jīng)討論過:

在這篇文章中席怪,我們將討論:

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 類 (WorkerListenableWorkerRxWorker) 之外暑塑,還有唯一一個使用 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 使用中的見解或者問題荐类。

WorkManager 相關(guān)資源

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市茁帽,隨后出現(xiàn)的幾起案子玉罐,更是在濱河造成了極大的恐慌,老刑警劉巖潘拨,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吊输,死亡現(xiàn)場離奇詭異,居然都是意外死亡铁追,警方通過查閱死者的電腦和手機(jī)季蚂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來琅束,“玉大人扭屁,你說我怎么就攤上這事∩鳎” “怎么了料滥?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長艾船。 經(jīng)常有香客問我葵腹,道長,這世上最難降的妖魔是什么屿岂? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任践宴,我火速辦了婚禮,結(jié)果婚禮上爷怀,老公的妹妹穿的比我還像新娘阻肩。我一直安慰自己,他們只是感情好霉撵,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布磺浙。 她就那樣靜靜地躺著,像睡著了一般徒坡。 火紅的嫁衣襯著肌膚如雪撕氧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天喇完,我揣著相機(jī)與錄音伦泥,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛不脯,可吹牛的內(nèi)容都是我干的府怯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼防楷,長吁一口氣:“原來是場噩夢啊……” “哼牺丙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起复局,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤冲簿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后亿昏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峦剔,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年角钩,在試婚紗的時候發(fā)現(xiàn)自己被綠了吝沫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡递礼,死狀恐怖惨险,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宰衙,我是刑警寧澤平道,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站供炼,受9級特大地震影響一屋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜袋哼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一冀墨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涛贯,春花似錦诽嘉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至稀余,卻和暖如春悦冀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睛琳。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工盒蟆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留踏烙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓历等,卻偏偏與公主長得像讨惩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子寒屯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評論 2 359