離線優先應用(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)發