做自(zì)由與創造的先行者

Android構建離線優先應用(yòng)

Android開(kāi)發手冊

離線優先應用(yòng)是無需訪問互聯網就能(néng)執行全部核心功能(néng)或一部分關鍵核心功能(néng)的應用(yòng)。也(yě)就是說,它可以離線執行部分或全部業務邏輯。

構建離線優先應用(yòng)首先要考慮數據層,它提供對(duì)應用(yòng)數據和(hé)業務邏輯的訪問。應用(yòng)可能(néng)需要不時(shí)刷新這(zhè)些(xiē)來(lái)自(zì)設備外(wài)部來(lái)源的數據。爲此,它可能(néng)需要調用(yòng)網絡資源來(lái)保持更新。

網絡的可用(yòng)性并不一定總是能(néng)得到(dào)保證。設備往往免不了(le)會(huì)遇到(dào)網絡連接不穩定或速度緩慢的問題。用(yòng)戶也(yě)可能(néng)會(huì)遇到(dào)以下(xià)情況:

互聯網帶寬有限。

連接短暫中斷,例如在電梯或隧道(dào)中。

偶爾才能(néng)訪問數據。例如,使用(yòng)的平闆電腦(nǎo)僅支持 Wi-Fi 連接。

不管原因如何,應用(yòng)通常可以在上(shàng)述情況下(xià)正常運行。爲了(le)确保應用(yòng)可在離線狀态下(xià)正常運行,它應該具備以下(xià)能(néng)力:

在沒有可靠網絡連接的情況下(xià)仍可使用(yòng)。

立即向用(yòng)戶提供本地數據,而不是等待第一次網絡調用(yòng)完成或失敗。

提取數據的方式考慮到(dào)電池和(hé)數據狀态。例如,僅在理(lǐ)想情況下(xià)(例如充電或有 Wi-Fi 連接時(shí))請(qǐng)求提取數據。

滿足上(shàng)述标準的應用(yòng)通常稱爲離線優先應用(yòng)。

設計(jì)離線優先應用(yòng)

在設計(jì)離線優先應用(yòng)時(shí),首先應該設計(jì)數據層以及您可以對(duì)應用(yòng)數據執行的以下(xià)兩項主要操作(zuò):

讀取:檢索數據以供應用(yòng)的其他(tā)部分使用(yòng),例如向用(yòng)戶顯示信息。

寫入:持久存儲用(yòng)戶輸入供日後檢索之用(yòng)。

數據層中的存儲庫負責組合數據源以提供應用(yòng)數據。在離線優先應用(yòng)中,必須至少有一個數據源無需訪問網絡即可執行其最關鍵的任務。其中一項關鍵任務是讀取數據。

注意:離線優先應用(yòng)至少應能(néng)在不訪問網絡的情況下(xià)執行讀取操作(zuò)。

離線優先應用(yòng)中的模型數據

對(duì)于需要使用(yòng)網絡資源的每個存儲庫,離線優先應用(yòng)至少有 2 個數據源:

本地數據源

網絡數據源

注意:離線優先應用(yòng)中需要訪問網絡數據源的存儲庫應當始終具有本地數據源。

本地數據源

本地數據源是應用(yòng)的規範可信來(lái)源。應用(yòng)的較高(gāo)層讀取任何數據,都應将其作(zuò)爲專屬來(lái)源。這(zhè)樣可在處于兩次連接之間的狀态時(shí)确保數據一緻性。本地數據源通常由存儲空(kōng)間提供支持并持久存儲到(dào)磁盤。下(xià)面是将數據持久存儲到(dào)磁盤的一些(xiē)常用(yòng)方法:

結構化數據源,例如 Room 等關系型數據庫。

非結構化數據源。例如,Datastore 的協議(yì)緩沖區(qū)。

簡單文(wén)件

網絡數據源

網絡數據源是應用(yòng)的實際狀态。最好(hǎo)将本地數據源與網絡數據源同步。本地數據源也(yě)有可能(néng)滞後于網絡數據源,在這(zhè)種情況下(xià),應用(yòng)需要在重新聯網後進行更新。相反,網絡數據源可以滞後于本地數據源,待連接恢複後,應用(yòng)便可對(duì)其進行更新。應用(yòng)的網域層和(hé)界面層絕不應直接與網絡層通信,而應由托管 repository 負責與其通信并用(yòng)其更新本地數據源。

公開(kāi)資源

應用(yòng)對(duì)本地數據源和(hé)網絡數據源執行讀寫操作(zuò)的方式存在根本差異。查詢本地數據源既快(kuài)速又靈活,例如在使用(yòng) SQL 查詢時(shí)。相反,查詢網絡數據源可能(néng)又慢又受到(dào)限制,例如在通過 ID 逐步訪問 RESTful 資源時(shí)。這(zhè)導緻每種數據源對(duì)其提供的數據通常需要采用(yòng)自(zì)己的表示形式。因此,本地數據源和(hé)網絡數據源可能(néng)有自(zì)己的模型。

下(xià)面的目錄結構直觀體現(xiàn)了(le)這(zhè)一概念。AuthorEntity 表示從(cóng)應用(yòng)的本地數據庫讀取的作(zuò)者,而 NetworkAuthor 表示通過網絡序列化的作(zuò)者:

data/

├─ local/

│ ├─ entities/

│ │ ├─ AuthorEntity

│ ├─ dao/

│ ├─ NiADatabase

├─ network/

│ ├─ NiANetwork

│ ├─ models/

│ │ ├─ NetworkAuthor

├─ model/

│ ├─ Author

├─ repository/

接下(xià)來(lái)是 AuthorEntity 和(hé) NetworkAuthor 的詳細信息:

/**

* Network representation of [Author]

*/

@Serializable

data class NetworkAuthor(

val id: String,

val name: String,

val imageUrl: String,

val twitter: String,

val mediumPage: String,

val bio: String,

)

/**

* Defines an author for either an [EpisodeEntity] or [NewsResourceEntity].

* It has a many-to-many relationship with both entities

*/

@Entity(tableName = "authors")

data class AuthorEntity(

@PrimaryKey

val id: String,

val name: String,

@ColumnInfo(name = "image_url")

val imageUrl: String,

@ColumnInfo(defaultValue = "")

val twitter: String,

@ColumnInfo(name = "medium_page", defaultValue = "")

val mediumPage: String,

@ColumnInfo(defaultValue = "")

val bio: String,

)

最好(hǎo)将 AuthorEntity 和(hé) NetworkAuthor 都留在數據層内部,公開(kāi)第三種類型供外(wài)部層使用(yòng)。這(zhè)可以保護外(wài)部層免受本地數據源和(hé)網絡數據源中不會(huì)從(cóng)根本上(shàng)改變應用(yòng)行爲的細微更改影響。這(zhè)種做法如以下(xià)代碼段所示:

/**

* External data layer representation of a "Now in Android" Author

*/

data class Author(

val id: String,

val name: String,

val imageUrl: String,

val twitter: String,

val mediumPage: String,

val bio: String,

)

然後,網絡模型可定義一種用(yòng)于将其轉換爲本地模型的擴展方法,本地模型同樣也(yě)可定義一種用(yòng)于将其轉換爲外(wài)部表示形式的擴展方法,如下(xià)所示:

/**

* Converts the network model to the local model for persisting

* by the local data source

*/

fun NetworkAuthor.asEntity() = AuthorEntity(

id = id,

name = name,

imageUrl = imageUrl,

twitter = twitter,

mediumPage = mediumPage,

bio = bio,

)

/**

* Converts the local model to the external model for use

* by layers external to the data layer

*/

fun AuthorEntity.asExternalModel() = Author(

id = id,

name = name,

imageUrl = imageUrl,

twitter = twitter,

mediumPage = mediumPage,

bio = bio,

)

注意:如上(shàng)所示的映射器通常在不同模塊中定義的模型之間進行映射。因此,在使用(yòng)這(zhè)些(xiē)映射器的模塊中對(duì)其進行定義以免模塊緊密耦合通常是一種有益的做法。如需了(le)解詳情,請(qǐng)參閱模塊化指南。

讀取

讀取是離線優先應用(yòng)中應用(yòng)數據的基本操作(zuò)。因此,您必須确保您的應用(yòng)可以讀取數據,并确保一旦有新數據可用(yòng),應用(yòng)便可以顯示這(zhè)些(xiē)數據。能(néng)夠做到(dào)這(zhè)一點的應用(yòng)屬于響應式應用(yòng),因爲它們會(huì)公開(kāi)具有可觀察類型的讀取 API。

在下(xià)面的代碼段中,OfflineFirstTopicRepository 會(huì)爲其所有讀取 API 返回 Flows。這(zhè)樣,當它收到(dào)來(lái)自(zì)網絡數據源的更新時(shí),便可以更新自(zì)己的讀取器。換言之,它允許 OfflineFirstTopicRepository 在本地數據源失效時(shí)推送更改。因此,OfflineFirstTopicRepository 的每個讀取器都必須準備好(hǎo)在應用(yòng)恢複網絡連接時(shí)處理(lǐ)可能(néng)觸發的數據更改。此外(wài),OfflineFirstTopicRepository 還會(huì)直接從(cóng)本地數據源讀取數據。它隻能(néng)先更新本地數據源,通過這(zhè)種更新将數據更改通知(zhī)讀取器。

class OfflineFirstTopicsRepository(

private val topicDao: TopicDao,

private val network: NiaNetworkDataSource,

) : TopicsRepository {

override fun getTopicsStream(): Flow<>> =

topicDao.getTopicEntitiesStream()

.map { it.map(TopicEntity::asExternalModel) }

}

注意:從(cóng)離線優先應用(yòng)中的存儲庫讀取數據應直接從(cóng)本地數據源讀取。所有更新均應先寫入本地數據源,本地數據源會(huì)更新其使用(yòng)方,因爲它可觀察。

錯誤處理(lǐ)策略

離線優先應用(yòng)采用(yòng)特有的錯誤處理(lǐ)方式,具體方式取決于出現(xiàn)錯誤的是哪一種數據源。以下(xià)幾小(xiǎo)節将概要介紹這(zhè)些(xiē)策略。

本地數據源

從(cóng)本地數據源讀取數據時(shí)遇到(dào)的錯誤應當極少。爲防止讀取器出錯,請(qǐng)對(duì)讀取器從(cóng)中收集數據的 Flows 使用(yòng) catch 操作(zuò)符。

在 ViewModel 中使用(yòng) catch 操作(zuò)符的代碼如下(xià)所示:

class AuthorViewModel(

authorsRepository: AuthorsRepository,

...

) : ViewModel() {

private val authorId: String = ...

// Observe author information

private val authorStream: Flow =

authorsRepository.getAuthorStream(

id = authorId

)

.catch { emit(Author.empty()) }

}

注意:catch 操作(zuò)符隻能(néng)防止因異常而導緻應用(yòng)崩潰,後備 Flow 仍會(huì)終止。如需在發生異常後恢複從(cóng)該數據流收集數據,請(qǐng)考慮使用(yòng) retry 方法。

網絡數據源

從(cóng)網絡數據源讀取數據時(shí)如果發生錯誤,應用(yòng)需要采用(yòng)啓發法來(lái)重試提取數據。常見的啓發法包括:

指數退避算(suàn)法

在指數退避算(suàn)法中,應用(yòng)不斷嘗試從(cóng)網絡數據源讀取數據,兩次嘗試間的時(shí)間間隔也(yě)不斷增加,直到(dào)讀取成功或其他(tā)條件決定其應停止讀取爲止。

評估應用(yòng)是否應繼續退避的标準包括:

網絡數據源指出的錯誤類型。例如,如果網絡調用(yòng)返回的錯誤指出沒有連接,就應該重試該網絡調用(yòng)。反之,如果 HTTP 請(qǐng)求未獲授權,那麽在獲得正确的憑據之前,就不應重試該 HTTP 請(qǐng)求。

允許的最大(dà)重試次數。

網絡連接監控

在此方法中,在應用(yòng)确定可以連接到(dào)網絡數據源之前,系統會(huì)将讀取請(qǐng)求加入隊列。連接建立後,系統會(huì)将讀取請(qǐng)求移出隊列,讀取數據并更新本地數據源。在 Android 上(shàng),可使用(yòng) Room 數據庫維護此隊列,并使用(yòng) WorkManager 将其作(zuò)爲持久性工(gōng)作(zuò)排空(kōng)。

寫入

讀取離線優先應用(yòng)中數據的建議(yì)方式是使用(yòng)可觀察類型,而寫入 API 的等效方式是異步 API,例如挂起函數。這(zhè)可以避免阻塞界面線程,并且有助于處理(lǐ)錯誤,因爲離線優先應用(yòng)中的寫入操作(zuò)可能(néng)會(huì)在跨越網絡邊界時(shí)失敗。

interface UserDataRepository {

/**

* Updates the bookmarked status for a news resource

*/

suspend fun updateNewsResourceBookmark(newsResourceId: String, bookmarked: Boolean)

}

在上(shàng)面的代碼段中,所選的異步 API 是協程,因爲上(shàng)述方法挂起。

寫入策略

在離線優先應用(yòng)中寫入數據時(shí),可以考慮采取三種策略。具體選擇哪種策略取決于要寫入的數據類型以及應用(yòng)的要求:

僅在線寫入

嘗試跨網絡邊界寫入數據。如果成功,就更新本地數據源,否則抛出異常并留待調用(yòng)方進行适當響應。

此策略通常用(yòng)于必須近乎實時(shí)地在線執行的寫入事(shì)務。例如,銀行轉賬。由于寫入可能(néng)會(huì)失敗,因此通常有必要告知(zhī)用(yòng)戶寫入失敗,或者從(cóng)一開(kāi)始就阻止用(yòng)戶嘗試寫入數據。在此類情況下(xià),您可以采取的策略可能(néng)包括:

如果應用(yòng)需要訪問互聯網才能(néng)寫入數據,可以選擇不向用(yòng)戶顯示可供用(yòng)戶寫入數據的界面,或至少也(yě)要停用(yòng)該界面。

您可以使用(yòng)一個用(yòng)戶無法關閉的彈出式消息或一個短暫提示來(lái)通知(zhī)用(yòng)戶他(tā)們處于離線狀态。

加入隊列的寫入

如果您有想要寫入的對(duì)象,請(qǐng)将其插入隊列。當應用(yòng)恢複在線狀态時(shí),繼續使用(yòng)指數退避算(suàn)法排空(kōng)隊列。在 Android 上(shàng),排空(kōng)離線隊列是一項持久性工(gōng)作(zuò),通常委托給 WorkManager。

此方法适合以下(xià)情況:

将數據寫入網絡并非必不可少。

事(shì)務對(duì)時(shí)效的要求不高(gāo)。

如果操作(zuò)失敗,并非一定要通知(zhī)用(yòng)戶。

适合此方法的用(yòng)例包括分析事(shì)件和(hé)日志記錄。

延遲寫入

先寫入本地數據源,然後将寫入請(qǐng)求加入隊列,以便盡快(kuài)通知(zhī)網絡數據源。這(zhè)一點非常重要,因爲當應用(yòng)恢複在線狀态時(shí),網絡數據源與本地數據源之間可能(néng)會(huì)存在沖突。下(xià)一部分将詳細介紹如何解決沖突。

當數據對(duì)應用(yòng)至關重要時(shí),此方法是正确的選擇。例如,在待辦事(shì)項列表離線優先應用(yòng)中,用(yòng)戶離線添加的任何任務都必須存儲在本地,以避免數據丢失的風(fēng)險。

注意:由于存在潛在沖突,在離線優先應用(yòng)中寫入數據通常比讀取數據需要考慮更多方面。離線優先應用(yòng)想要被視(shì)爲離線優先,并不一定需要在離線狀态下(xià)能(néng)夠寫入數據。

同步和(hé)解決沖突

離線優先應用(yòng)恢複連接時(shí),需要使本地數據源中的數據與網絡數據源中的數據一緻。此過程稱爲同步。應用(yòng)與網絡數據源同步主要有兩種方式:

基于拉取的同步

基于推送的同步

基于拉取的同步

在基于拉取的同步中,應用(yòng)在需要的時(shí)候連接到(dào)網絡數據源讀取最新應用(yòng)數據。此方法的一種常用(yòng)啓發法基于用(yòng)戶導航,采用(yòng)這(zhè)種方法時(shí),應用(yòng)僅在向用(yòng)戶提供數據之前提取數據。

當應用(yòng)預計(jì)短時(shí)間到(dào)中等長度的時(shí)間内沒有網絡連接時(shí),最适合使用(yòng)此方法。這(zhè)是因爲數據刷新需要伺機而爲,長時(shí)間沒有網絡連接會(huì)提高(gāo)用(yòng)戶嘗試使用(yòng)過時(shí)緩存或空(kōng)緩存訪問應用(yòng)目的地的幾率。

假設在一個應用(yòng)中,使用(yòng)頁面令牌爲某個特定屏幕提取無限滾動列表中的項目。該應用(yòng)的實現(xiàn)可延遲連接到(dào)網絡數據源,将數據持久存儲到(dào)本地數據源,然後從(cóng)本地數據源讀取數據以向用(yòng)戶顯示信息。在沒有網絡連接的情況下(xià),存儲庫可以隻向本地數據源請(qǐng)求數據。以下(xià)是 Jetpack Paging 庫通過其 RemoteMediator API 使用(yòng)的模式。

class FeedRepository(...) {

fun feedPagingSource(): PagingSource { ... }

}

class FeedViewModel(

private val repository: FeedRepository

) : ViewModel() {

private val pager = Pager(

config = PagingConfig(

pageSize = NETWORK_PAGE_SIZE,

enablePlaceholders = false

),

remoteMediator = FeedRemoteMediator(...),

pagingSourceFactory = feedRepository::feedPagingSource

)

val feedPagingData = pager.flow

}

下(xià)表總結了(le)基于拉取的同步的優缺點:

優點 缺點

實現(xiàn)起來(lái)相對(duì)容易。 容易消耗大(dà)量流量。這(zhè)是因爲重複訪問導航目的地會(huì)觸發不必要的操作(zuò),重新提取未更改的信息。您可以通過适當的緩存來(lái)減少此問題。若要使用(yòng)緩存,可在界面層使用(yòng) cachedIn 操作(zuò)符或在網絡層使用(yòng) HTTP 緩存。

絕不會(huì)提取不需要的數據。 不能(néng)使用(yòng)關系型數據很(hěn)好(hǎo)地擴展,因爲拉取的模型需要自(zì)給自(zì)足。如果待同步的模型依賴于需要提取的其他(tā)模型來(lái)填充自(zì)己,那麽上(shàng)面提到(dào)的消耗大(dà)量流量的問題将變得更加嚴重。此外(wài),它還可能(néng)導緻父模型的存儲庫與嵌套模型的存儲庫之間存在依賴關系。

基于推送的同步

在基于推送的同步中,本地數據源會(huì)盡力嘗試模拟網絡數據源的副本集。它會(huì)在首次啓動時(shí)主動提取适當數量的數據來(lái)設置基準,之後依靠來(lái)自(zì)服務器的通知(zhī)提醒自(zì)己數據何時(shí)過時(shí)。

收到(dào)過時(shí)通知(zhī)後,應用(yòng)連接到(dào)網絡數據源,隻更新标記爲過時(shí)的數據。這(zhè)項工(gōng)作(zuò)将委托給 Repository,由其連接到(dào)網絡數據源,并将提取的數據持久存儲到(dào)本地數據源。由于存儲庫通過可觀測類型公開(kāi)其數據,因此讀取器将收到(dào)所有更改的通知(zhī)。

class UserDataRepository(...) {

suspend fun synchronize() {

val userData = networkDataSource.fetchUserData()

localDataSource.saveUserData(userData)

}

}

在此方法中,應用(yòng)對(duì)網絡數據源的依賴要低(dī)得多,而且長時(shí)間無法使用(yòng)網絡數據源也(yě)能(néng)正常運行。它可以在離線狀态下(xià)提供讀寫訪問,因爲系統假定本地存儲着來(lái)自(zì)網絡數據源的最新信息。

下(xià)表總結了(le)基于推送的同步的優缺點:

優點 缺點

應用(yòng)可以無限期離線使用(yòng)。 爲了(le)解決沖突,對(duì)數據進行版本控制非常重要。

可将流量消耗降到(dào)最低(dī)。應用(yòng)僅提取經過更改的數據。 需要考慮同步期間的寫入問題。

非常适合關系型數據。每個存儲庫隻負責爲其支持的模型提取數據。 網絡數據源需要支持同步。

混合同步

某些(xiē)應用(yòng)采用(yòng)混合方法,具體基于拉取還是基于推送根據數據而定。例如,某個社交媒體應用(yòng)可能(néng)會(huì)使用(yòng)基于拉取的同步按需提取用(yòng)戶的關注 Feed,因爲 Feed 更新的頻率較高(gāo)。然而,同一應用(yòng)可能(néng)會(huì)選擇使用(yòng)基于推送的同步來(lái)提取已登錄用(yòng)戶的相關數據,包括其用(yòng)戶名、個人資料照片等。

最終,離線優先同步的選擇取決于産品要求和(hé)可用(yòng)的技術基礎架構。

注意:應用(yòng)的同步方法取決于應用(yòng)的需求以及支持本地數據源和(hé)網絡數據源的基礎架構的限制。

沖突解決

如果應用(yòng)處于離線狀态時(shí)在本地寫入的數據與網絡數據源的數據不一緻,說明(míng)存在沖突,必須解決沖突後才能(néng)進行同步。

解決沖突問題通常需要借助版本控制。應用(yòng)需要通過一些(xiē)簿記來(lái)跟蹤發生更改的時(shí)間。這(zhè)樣,它就能(néng)将元數據傳遞給網絡數據源。然後,由網絡數據源負責提供絕對(duì)可信來(lái)源。根據應用(yòng)的需求,可以考慮的沖突解決策略還有很(hěn)多。對(duì)于移動應用(yòng),常見的方法是“最後寫入内容生效”。

最後寫入内容生效

在此方法中,設備将時(shí)間戳元數據附加到(dào)其寫入網絡數據源的數據中。網絡數據源在收到(dào)這(zhè)些(xiē)數據後,會(huì)舍棄比當前狀态舊的所有數據而接受比當前狀态新的數據。

在上(shàng)圖中,兩部設備都處于離線狀态,并且最初都與網絡數據源同步。離線時(shí),它們都在本地寫入數據并跟蹤自(zì)己寫入數據的時(shí)間。當二者恢複在線狀态并與網絡數據源同步時(shí),網絡數據源通過持久存儲來(lái)自(zì)設備 B 的數據來(lái)解決沖突,因爲設備 B 寫入數據的時(shí)間更晚。

離線優先應用(yòng)中的 WorkManager

在前面介紹的讀取和(hé)寫入策略中,有兩個常用(yòng)的實用(yòng)程序:

隊列

讀取:用(yòng)于将讀取操作(zuò)推遲到(dào)網絡連接可用(yòng)時(shí)。

寫入:用(yòng)于将寫入操作(zuò)推遲到(dào)網絡連接可用(yòng)時(shí),并将寫入操作(zuò)重新加入隊列進行重試。

網絡連接監視(shì)器

讀取:在應用(yòng)連接時(shí)用(yòng)作(zuò)排空(kōng)讀取隊列的信号,也(yě)用(yòng)于同步

寫入:在應用(yòng)連接時(shí)用(yòng)作(zuò)排空(kōng)寫入隊列的信号,也(yě)用(yòng)于同步

這(zhè)兩種情況都是 WorkManager 擅長的持久性工(gōng)作(zuò)的例子。例如,在 Now in Android 示例應用(yòng)中,同步本地數據源時(shí)将 WorkManager 用(yòng)作(zuò)讀取隊列監視(shì)器和(hé)網絡監視(shì)器。在啓動時(shí),該應用(yòng)會(huì)執行以下(xià)操作(zuò):

将讀取同步工(gōng)作(zuò)加入隊列,以确保本地數據源和(hé)網絡數據源相同。

排空(kōng)讀取同步隊列,并在應用(yòng)處于在線狀态時(shí)開(kāi)始同步。

使用(yòng)指數退避算(suàn)法執行從(cóng)網絡數據源讀取數據的操作(zuò)。

将讀取結果持久存儲到(dào)本地數據源中,解決可能(néng)發生的任何沖突。

公開(kāi)本地數據源中的數據,供應用(yòng)的其他(tā)層使用(yòng)。

使用(yòng) WorkManager 将同步工(gōng)作(zuò)加入隊列後,使用(yòng) KEEP ExistingWorkPolicy 将其指定爲唯一工(gōng)作(zuò):

class SyncInitializer : Initializer {

override fun create(context: Context): Sync {

WorkManager.getInstance(context).apply {

// Queue sync on app startup and ensure only one

// sync worker runs at any time

enqueueUniqueWork(

SyncWorkName,

ExistingWorkPolicy.KEEP,

SyncWorker.startUpSyncWork()

)

}

return Sync

}

}

注意:“Now in Android”中的讀取隊列非常簡單,使用(yòng) enqueueUniqueWork API 來(lái)表示就足矣。爲了(le)進一步保證隊列的排空(kōng)順序,需要使用(yòng) Room 或 Datastore 等數據持久化 API 實現(xiàn)更可靠的隊列實現(xiàn)。然後,您可以設置一個 Worker 來(lái)按順序排空(kōng)此隊列。

其中,SyncWorker.startupSyncWork() 的定義如下(xià):

/**

Create a WorkRequest to call the SyncWorker using a DelegatingWorker.

This allows for dependency injection into the SyncWorker in a different

module than the app module without having to create a custom WorkManager

configuration.

*/

fun startUpSyncWork() = OneTimeWorkRequestBuilder()

// Run sync as expedited work if the app is able to.

// If not, it runs as regular work.

.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)

.setConstraints(SyncConstraints)

// Delegate to the SyncWorker.

.setInputData(SyncWorker::class.delegatedData())

.build()

val SyncConstraints

get() = Constraints.Builder()

.setRequiredNetworkType(NetworkType.CONNECTED)

.build()

具體而言,由 SyncConstraints 定義的 Constraints 要求 NetworkType 爲 NetworkType.CONNECTED。也(yě)就是說,它會(huì)等到(dào)網絡可用(yòng)後再運行。

當網絡可用(yòng)後,工(gōng)作(zuò)器将 SyncWorkName 指定的唯一工(gōng)作(zuò)隊列委托給适當的 Repository 實例來(lái)排空(kōng)該隊列。如果同步失敗,doWork() 方法會(huì)返回 Result.retry()。WorkManager 将采用(yòng)指數退避算(suàn)法自(zì)動重試同步。否則,返回 Result.success() 完成同步。

class SyncWorker(...) : CoroutineWorker(appContext, workerParams), Synchronizer {

override suspend fun doWork(): Result = withContext(ioDispatcher) {

// First sync the repositories in parallel

val syncedSuccessfully = awaitAll(

async { topicRepository.sync() },

async { authorsRepository.sync() },

async { newsRepository.sync() },

).all { it }

if (syncedSuccessfully) Result.success()

else Result.retry()

}

}

網站(zhàn)建設開(kāi)發|APP設計(jì)開(kāi)發|小(xiǎo)程序建設開(kāi)發
下(xià)一篇:抽象化新 API
上(shàng)一篇:Android數據層