現在,靜态界面已十分罕見(jiàn)。當用戶與界面互動或應用需要(yào)顯示新數(shù)據時(shí),界面狀态會發生變化。
本文檔将介紹界面狀态的生成和(hé)管理指南。閱讀完本文檔後,您應能(néng)夠:
了(le)解應使用哪些 API 來生成界面狀态。這(zhè)取決于狀态容器 (state holder) 中可(kě)用的狀态變化來源的性質,并遵循單向數(shù)據流原則。
了(le)解您應該如(rú)何限定界面狀态生成的作(zuò)用域,以便密切注意系統資源。
了(le)解應該如(rú)何公開(kāi)界面狀态以供界面使用。
從(cóng)根本上(shàng)說,狀态生成是将這(zhè)些變化逐步應用于界面狀态的過程。狀态始終存在,并且會随着事件而發生變化。
界面狀态生成流水線
Android 應用中的狀态生成可(kě)以理解為(wèi)一(yī)種處理流水線,其中包含:
輸入:狀态變化的來源。這(zhè)些來源可(kě)能(néng)包括:
界面層本地(dì):可(kě)能(néng)是用戶事件(例如(rú)用戶在任務管理應用中輸入“待辦事項”的标題),也可(kě)能(néng)是提供對界面邏輯的訪問(wèn)權限的 API,這(zhè)些事件/API 會導緻界面狀态發生變化。例如(rú),在 Jetpack Compose 中對 DrawerState 調用 open 方法。
界面層外(wài)部:這(zhè)些來源來自(zì)網域層或數(shù)據層,并會導緻界面狀态發生變化。例如(rú),從(cóng) NewsRepository 或其他(tā)事件完成加載的新聞。
以上(shàng)各項兼而有之。
狀态容器:用于将業務邏輯和(hé)/或界面邏輯應用于狀态變化來源并處理用戶事件以生成界面狀态的類型。
輸出:應用可(kě)以呈現以向用戶提供所需信息的界面狀态。
狀态生成流水線組建
後續部分将介紹各種輸入最适合采用的狀态生成技術(shù),以及匹配的輸出 API。每個狀态生成流水線都(dōu)是輸入和(hé)輸出的組合,并應滿足以下(xià)條件:
可(kě)感知生命周期:如(rú)果界面不可(kě)見(jiàn)或未處于活動狀态,除非明(míng)确要(yào)求,否則狀态生成流水線不應消耗任何資源。
易于使用:界面應能(néng)夠輕松呈現生成的界面狀态。狀态生成流水線輸出的相關注意事項因不同的 View API(例如(rú) View 系統或 Jetpack Compose)而異。
注意:在接下(xià)來的部分中,我們讨論的所有 API 都(dōu)使用慣用的 Kotlin 和(hé) Jetpack Compose 代碼。不過,對于 Java 編程語言或 Kotlin 的其他(tā) API 中等效的類似代碼,此指南同樣适用。
狀态生成流水線中的輸入
狀态生成流水線中的輸入可(kě)以通(tōng)過以下(xià)方式提供其狀态變化來源:
可(kě)能(néng)是同步或異步的一(yī)次性操作(zuò),例如(rú)對 suspend 函數(shù)的調用。
流 API,例如(rú) Flows。
以上(shàng)二者兼用。
以下(xià)各部分介紹了(le)如(rú)何為(wèi)上(shàng)述每項輸入組建狀态生成流水線。
使用一(yī)次性 API 作(zuò)為(wèi)狀态變化來源
将 MutableStateFlow API 用作(zuò)可(kě)觀測的可(kě)變狀态容器。在 Jetpack Compose 應用中,您還可(kě)以考慮 mutableStateOf,尤其是在使用 Compose Text API 時(shí)。這(zhè)兩個 API 都(dōu)提供了(le)允許對它們托管的值進行(xíng)安全原子更新的方法(無論更新是同步的還是異步的)。
通(tōng)過後台線程更改界面狀态
最好在主調度程序上(shàng)啓動協程以生成界面狀态。也就是說,在以下(xià)代碼段中的 withContext 代碼塊之外(wài)。不過,如(rú)果您需要(yào)在其他(tā)後台上(shàng)下(xià)文中更新界面狀态,可(kě)以通(tōng)過使用以下(xià) API 來實現:
使用 withContext 方法可(kě)在其他(tā)并發上(shàng)下(xià)文中運行(xíng)協程。
使用 MutableStateFlow 時(shí),照常使用 update 方法。
使用 Compose State 時(shí),使用 Snapshot.withMutableSnapshot 來保證在并發上(shàng)下(xià)文中對 State 進行(xíng)原子更新。
使用流 API 作(zuò)為(wèi)狀态變化來源
對于随着時(shí)間(jiān)推移連續生成多個值的狀态變化來源,一(yī)種簡單直接的狀态生成方法是将所有來源的輸出聚合為(wèi)一(yī)個緊密的整體。
使用 Kotlin Flow 時(shí),您可(kě)以通(tōng)過 combine 函數(shù)來實現此目的。您可(kě)在 InterestsViewModel 中的“Now in Android”示例中查看(kàn)示例:
class InterestsViewModel(
authorsRepository: AuthorsRepository,
topicsRepository: TopicsRepository
) : ViewModel() {
val uiState = combine(
authorsRepository.getAuthorsStream(),
topicsRepository.getTopicsStream(),
) { availableAuthors, availableTopics ->
InterestsUiState.Interests(
authors = availableAuthors,
topics = availableTopics
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = InterestsUiState.Loading
)
}
注意:您可(kě)以使用 stateIn 運算符将組合 Flow 轉換為(wèi) StateFlow 作(zuò)為(wèi)界面狀态的可(kě)觀測 API。
使用 stateIn 運算符創建 StateFlows,可(kě)使界面能(néng)夠更精細地(dì)控制狀态生成流水線的活動,因為(wèi)它可(kě)能(néng)隻需在界面可(kě)見(jiàn)時(shí)才處于活動狀态。
如(rú)果僅當界面可(kě)見(jiàn),同時(shí)正在以生命周期感知方式收集數(shù)據流時(shí),流水線才應處于活動狀态,則使用 SharingStarted.WhileSubscribed()。
如(rú)果隻要(yào)用戶可(kě)能(néng)返回界面(即界面位于後台堆棧上(shàng)或屏幕外(wài)的其他(tā)标簽頁中),流水線就應該處于活動狀态,則使用 SharingStarted.Lazily。
如(rú)果聚合基于數(shù)據流的狀态來源不适用,Kotlin Flow 等流 API 可(kě)提供一(yī)組豐富的轉換(例如(rú)合并、扁平化等),以幫助将數(shù)據流處理為(wèi)界面狀态。
要(yào)點:在大多數(shù)情況下(xià),combine 都(dōu)是從(cóng)流 API 生成狀态的可(kě)行(xíng)方法。
使用一(yī)次性的流 API 作(zuò)為(wèi)狀态變化來源
如(rú)果狀态生成流水線依賴一(yī)次性調用和(hé)數(shù)據流作(zuò)為(wèi)狀态變化來源,則數(shù)據流就是決定性的約束條件。因此,應将一(yī)次性調用轉換為(wèi)數(shù)據流 API,或将其輸出傳輸到數(shù)據流中并恢複處理,如(rú)上(shàng)文的數(shù)據流部分中所述。
對于數(shù)據流,這(zhè)通(tōng)常意味着創建一(yī)個或多個專用後備 MutableStateFlow 實例以傳播狀态變化。您還可(kě)以從(cóng) Compose 狀态創建快(kuài)照流。
狀态生成流水線初始化
初始化狀态生成流水線需要(yào)設置流水線運行(xíng)的初始條件。這(zhè)可(kě)能(néng)涉及提供對啓動流水線至關重要(yào)的初始輸入值(例如(rú),适用于新聞報道(dào)詳情視(shì)圖的 id),或提供對啓動異步加載至關重要(yào)的初始輸入值。
您應該盡可(kě)能(néng)延遲狀态生成流水線的初始化,以節省系統資源。實際上(shàng),這(zhè)通(tōng)常意味着等到出現輸出的使用方。Flow API 通(tōng)過 stateIn 方法中的 started 參數(shù)允許執行(xíng)此操作(zuò)。如(rú)果這(zhè)種做(zuò)法不适用,請(qǐng)定義幂等 initialize() 函數(shù),以明(míng)确啓動狀态生成流水線,如(rú)以下(xià)代碼段所示:
class MyViewModel : ViewModel() {
private var initializeCalled = false
// This function is idempotent provided it is only called from the UI thread.
@MainThread
fun initialize() {
if(initializeCalled) return
initializeCalled = true
viewModelScope.launch {
// seed the state production pipeline
}
}
}
警告:請(qǐng)避免在 ViewModel 的 init 塊或構造函數(shù)中啓動異步操作(zuò)。異步操作(zuò)不應是創建對象時(shí)的附帶效應,因為(wèi)異步代碼在對象完全初始化之前可(kě)能(néng)會對該對象執行(xíng)讀寫操作(zuò)。這(zhè)也稱為(wèi)對象洩露,可(kě)能(néng)會導緻細微(wēi)且難以診斷的錯誤。使用 Compose 狀态時(shí),這(zhè)一(yī)點尤為(wèi)重要(yào)。當 ViewModel 存儲了(le) Compose 狀态字段時(shí),請(qǐng)勿在更新 Compose 狀态字段的 ViewModel 的 init 塊中啓動協程,否則可(kě)能(néng)會出現 IllegalStateException。
網站建設開(kāi)發|APP設計開(kāi)發|小程序建設開(kāi)發