界面的作(zuò)用是在屏幕上(shàng)顯示應用數(shù)據,并充當主要(yào)的用戶互動點。每當數(shù)據發生變化時(shí),無論是因為(wèi)用戶互動(例如(rú)按了(le)某個按鈕),還是因為(wèi)外(wài)部輸入(例如(rú)網絡響應),界面都(dōu)應随之更新,以反映這(zhè)些變化。實際上(shàng),界面是從(cóng)數(shù)據層獲取的應用狀态的直觀呈現。
不過,從(cóng)數(shù)據層獲取的應用數(shù)據的格式通(tōng)常不同于您需要(yào)顯示的信息的格式。例如(rú),您可(kě)能(néng)隻需要(yào)在界面中顯示部分數(shù)據,或者可(kě)能(néng)需要(yào)合并兩個不同的數(shù)據源,以便提供切合用戶需求的信息。無論您應用的是什麽邏輯,都(dōu)需要(yào)向界面傳遞完全呈現界面所需的所有信息。界面層是一(yī)個流水線,負責将應用數(shù)據變化轉換為(wèi)界面可(kě)以呈現的形式,然後将其顯示出來。
基本案例研究
讓我們以一(yī)個可(kě)獲取新聞報道(dào)供用戶閱讀的應用為(wèi)例。該應用有一(yī)個報道(dào)屏幕,用于顯示可(kě)供閱讀的報道(dào);另外(wài),該應用允許已登錄的用戶為(wèi)真正出衆的報道(dào)添加書(shū)簽。考慮到随時(shí)都(dōu)可(kě)能(néng)有大量的報道(dào),讀者應能(néng)夠按類别浏覽報道(dào)。總的來說,該應用可(kě)讓用戶執行(xíng)以下(xià)操作(zuò):
查看(kàn)可(kě)供閱讀的報道(dào)。
按類别浏覽報道(dào)。
登錄帳号并為(wèi)特定報道(dào)添加書(shū)簽。
使用部分收費(fèi)功能(néng)(如(rú)果符合相應條件)。
以下(xià)幾個部分使用此示例作(zuò)為(wèi)案例研究,以便介紹單向數(shù)據流的原則,并展示在界面層的應用架構上(shàng)下(xià)文中,這(zhè)些原則有助于解決的問(wèn)題。
界面層架構
“界面”這(zhè)一(yī)術(shù)語是指用于顯示數(shù)據的 activity 和(hé) fragment 等界面元素,無論它們使用哪個 API(Views 還是 Jetpack Compose)來顯示數(shù)據。由于數(shù)據層的作(zuò)用是存儲和(hé)管理應用數(shù)據,以及提供對應用數(shù)據的訪問(wèn)權限,因此界面層必須執行(xíng)以下(xià)步驟:
使用應用數(shù)據,并将其轉換為(wèi)界面可(kě)以輕松呈現的數(shù)據。
使用界面可(kě)呈現的數(shù)據,并将其轉換為(wèi)用于向用戶呈現的界面元素。
使用來自(zì)這(zhè)些組合在一(yī)起的界面元素的用戶輸入事件,并根據需要(yào)反映它們對界面數(shù)據的影響。
根據需要(yào)重複第 1-3 步。
本指南的其餘部分展示了(le)如(rú)何實現用于執行(xíng)這(zhè)些步驟的界面層。具體來說,本指南涵蓋以下(xià)任務和(hé)概念:
如(rú)何定義界面狀态。
單向數(shù)據流 (UDF),作(zuò)為(wèi)提供和(hé)管理界面狀态的方式。
如(rú)何根據 UDF 原則使用可(kě)觀察數(shù)據類型公開(kāi)界面狀态。
如(rú)何實現使用可(kě)觀察界面狀态的界面。
其中最基本的便是定義界面狀态。
定義界面狀态
請(qǐng)參閱上(shàng)文所述的案例研究。簡言之,界面會顯示一(yī)個報道(dào)列表,以及每篇報道(dào)的部分元數(shù)據。該應用向用戶顯示的這(zhè)些信息便是界面狀态。
換言之,如(rú)果界面是相對用戶而言的,那(nà)麽界面狀态就是相對應用而言的。這(zhè)就像同一(yī)枚硬币的兩面,界面是界面狀态的直觀呈現。對界面狀态所做(zuò)的任何更改都(dōu)會立即反映在界面中。
不可(kě)變性
以上(shàng)示例中的界面狀态定義是不可(kě)變的。這(zhè)樣的主要(yào)好處是,不可(kě)變對象可(kě)保證即時(shí)提供應用的狀态。這(zhè)樣一(yī)來,界面便可(kě)專注于發揮單一(yī)作(zuò)用:讀取狀态并相應地(dì)更新其界面元素。因此,切勿直接在界面中修改界面狀态,除非界面本身是其數(shù)據的唯一(yī)來源。違反這(zhè)個原則會導緻同一(yī)條信息有多個可(kě)信來源,從(cóng)而導緻數(shù)據不一(yī)緻和(hé)輕微(wēi)的 bug。
例如(rú),如(rú)果案例研究中來自(zì)界面狀态的 NewsItemUiState 對象中的 bookmarked 标記在 Activity 類中已更新,那(nà)麽該标記會與數(shù)據層展開(kāi)競争,以争取成為(wèi)報道(dào)的“已添加書(shū)簽”狀态的來源。不可(kě)變數(shù)據類對于防止此類反模式非常有用。
要(yào)點:隻有數(shù)據源或數(shù)據所有者才應負責更新其公開(kāi)的數(shù)據。
本指南中的命名慣例
在本指南中,界面狀态類是根據其描述的屏幕或部分屏幕的功能(néng)命名的。具體命名慣例如(rú)下(xià):
功能(néng) + UiState。
例如(rú),用于顯示新聞的屏幕的狀态可(kě)以稱為(wèi) NewsUiState,新聞報道(dào)列表中的新聞報道(dào)的狀态可(kě)以為(wèi) NewsItemUiState。
使用單向數(shù)據流管理狀态
上(shàng)一(yī)部分中指出,界面狀态是呈現界面所需的詳細信息的不可(kě)變快(kuài)照。不過,應用中數(shù)據的動态特性意味着狀态可(kě)能(néng)會随時(shí)間(jiān)而變化。這(zhè)可(kě)能(néng)是因為(wèi)用戶互動,也可(kě)能(néng)是因為(wèi)其他(tā)事件修改了(le)用于填充應用的底層數(shù)據。
這(zhè)些互動可(kě)以受益于處理它們的 mediator,從(cóng)而定義要(yào)為(wèi)每個事件應用的邏輯,并對後備數(shù)據源執行(xíng)必要(yào)的轉換,以便創建界面狀态。這(zhè)些互動及其邏輯可(kě)以位于界面本身中,但(dàn)随着界面開(kāi)始擔任其名稱所表明(míng)的角色以外(wài)的角色(數(shù)據所有者、提供方、轉換器等),這(zhè)可(kě)能(néng)很(hěn)快(kuài)就會變得難以掌控。此外(wài),這(zhè)可(kě)能(néng)會影響可(kě)測試性,因為(wèi)生成的代碼是緊密耦合的代碼,沒有可(kě)辨别的邊界。歸根結底,界面能(néng)夠受益于減輕的負擔。除非界面狀态非常簡單,否則界面的唯一(yī)職責應該是使用和(hé)顯示界面狀态。
本部分介紹了(le)單向數(shù)據流 (UDF),這(zhè)是一(yī)種架構模式,有助于強制實施這(zhè)種健康的職責分離。
狀态容器
符合以下(xià)條件的類稱為(wèi)狀态容器:負責提供界面狀态,并且包含執行(xíng)相應任務所必需的邏輯。狀态容器有多種大小,具體取決于所管理的界面元素的作(zuò)用域(從(cóng)底部應用欄等單個微(wēi)件,到整個屏幕或導航目的地(dì),不一(yī)而足)。
在後一(yī)種情況下(xià),典型的實現是 ViewModel 的實例,不過根據應用的要(yào)求,使用簡單的類可(kě)能(néng)就足夠了(le)。例如(rú),案例研究中的“新聞”應用使用 NewsViewModel 類作(zuò)為(wèi)狀态容器,以便為(wèi)該部分顯示的屏幕畫(huà)面提供界面狀态。
要(yào)點:ViewModel 類型是推薦的實現,用于管理屏幕級界面狀态,具有數(shù)據層訪問(wèn)權限。此外(wài),它會在配置發生變化後自(zì)動繼續存在。ViewModel 類用于定義要(yào)為(wèi)應用中的事件應用的邏輯,并提供更新後的狀态作(zuò)為(wèi)結果。
您可(kě)以通(tōng)過多種方式為(wèi)界面與其狀态提供方之間(jiān)的互相依賴關系建模。不過,由于界面與其 ViewModel 類之間(jiān)的互動在很(hěn)大程度上(shàng)可(kě)以理解為(wèi)事件輸入及其随後的狀态輸出
狀态向下(xià)流動、事件向上(shàng)流動的這(zhè)種模式稱為(wèi)單向數(shù)據流 (UDF)。這(zhè)種模式對應用架構的影響如(rú)下(xià):
ViewModel 會存儲并公開(kāi)界面要(yào)使用的狀态。界面狀态是經過 ViewModel 轉換的應用數(shù)據。
界面會向 ViewModel 發送用戶事件通(tōng)知。
ViewModel 會處理用戶操作(zuò)并更新狀态。
更新後的狀态将反饋給界面以進行(xíng)呈現。
系統會對導緻狀态更改的所有事件重複上(shàng)述操作(zuò)。
對于導航目的地(dì)或屏幕,ViewModel 會使用存儲庫或用例類來獲取數(shù)據并将其轉換為(wèi)界面狀态,同時(shí)納入可(kě)能(néng)會導緻狀态更改的事件的影響。前面提到的案例研究包含一(yī)個報道(dào)列表,其中每篇報道(dào)都(dōu)有标題、說明(míng)、來源、作(zuò)者名稱、發布日期,以及是否添加了(le)書(shū)簽
用戶請(qǐng)求為(wèi)報道(dào)添加書(shū)簽就是一(yī)個可(kě)能(néng)會導緻狀态更改的事件示例。作(zuò)為(wèi)狀态提供方,ViewModel 的職責是定義所有必需的邏輯,以便填充界面狀态中的所有字段,并處理界面完全呈現所需的事件。
以下(xià)幾個部分更詳細地(dì)介紹了(le)導緻狀态變化的事件,以及如(rú)何使用 UDF 處理這(zhè)些事件。
邏輯類型
為(wèi)報道(dào)添加書(shū)簽就是一(yī)個業務邏輯示例,因為(wèi)這(zhè)能(néng)夠為(wèi)應用帶來價值。如(rú)需了(le)解詳情,請(qǐng)參閱數(shù)據層頁面。不過除此之外(wài),還有其他(tā)類型的重要(yào)邏輯需要(yào)定義:
業務邏輯決定着應用數(shù)據的産品要(yào)求的實現。如(rú)前面所述,一(yī)個例子是在案例研究應用中為(wèi)報道(dào)添加書(shū)簽。業務邏輯通(tōng)常位于網域層或數(shù)據層中,但(dàn)絕不能(néng)位于界面層中。
界面行(xíng)為(wèi)邏輯(即界面邏輯)決定着如(rú)何在屏幕上(shàng)顯示狀态變化。示例包括:使用 Android Resources 獲取要(yào)在屏幕上(shàng)顯示的正确文本、在用戶點擊某個按鈕時(shí)前往特定屏幕,或使用消息框或信息提示控件在屏幕上(shàng)向用戶顯示消息。
界面邏輯(尤其是在涉及 Context 等界面類型時(shí))應位于界面中,而非 ViewModel 中。如(rú)果界面變得越來越複雜(zá),并且您希望将界面邏輯委托給另一(yī)個類,以便有利于進行(xíng)測試和(hé)關注點分離,您可(kě)以創建一(yī)個簡單的類作(zuò)為(wèi)狀态容器。在界面中創建的簡單類可(kě)以采用 Android SDK 依賴項,因為(wèi)它們遵循界面的生命周期;ViewModel 對象具有更長(cháng)的生命周期。
如(rú)需詳細了(le)解狀态容器以及如(rú)何利用它們更好地(dì)構建界面,請(qǐng)參閱 Jetpack Compose 狀态指南。
為(wèi)何使用 UDF?
UDF 可(kě)為(wèi)狀态提供周期建模(如(rú)圖 4 所示)。它還可(kě)以将以下(xià)位置分離開(kāi)來:狀态變化來源位置、轉換位置以及最終使用位置。這(zhè)種分離可(kě)讓界面隻發揮其名稱所表明(míng)的作(zuò)用:通(tōng)過觀察狀态變化來顯示信息,并通(tōng)過将這(zhè)些變化傳遞給 ViewModel 來傳遞用戶 intent。
換句話說,UDF 有助于實現以下(xià)幾點:
數(shù)據一(yī)緻性。界面隻有一(yī)個可(kě)信來源。
可(kě)測試性。狀态來源是獨立的,因此可(kě)獨立于界面進行(xíng)測試。
可(kě)維護性。狀态的更改遵循明(míng)确定義的模式,即狀态更改是用戶事件及其數(shù)據拉取來源共同作(zuò)用的結果。
公開(kāi)界面狀态
定義界面狀态并确定如(rú)何管理相應狀态的提供後,下(xià)一(yī)步是将提供的狀态發送給界面。由于您使用 UDF 管理狀态的提供,因此您可(kě)以将提供的狀态視(shì)為(wèi)數(shù)據流,換句話說,随着時(shí)間(jiān)的推移,将提供狀态的多個版本。因此,您應在 LiveData 或 StateFlow 等可(kě)觀察數(shù)據容器中公開(kāi)界面狀态。這(zhè)樣做(zuò)是為(wèi)了(le)使界面可(kě)以對狀态的任何變化做(zuò)出反應,而無需直接從(cóng) ViewModel 手動拉取數(shù)據。這(zhè)些類型還有一(yī)個好處是,始終緩存界面狀态的最新版本,這(zhè)對于在配置發生變化後快(kuài)速恢複狀态非常有用。
如(rú)需關于将 LiveData 用作(zuò)可(kě)觀察數(shù)據容器的介紹,請(qǐng)參閱此 Codelab。如(rú)需關于 Kotlin 數(shù)據流的類似介紹,請(qǐng)參閱 Android 上(shàng)的 Kotlin 數(shù)據流。
如(rú)果向界面公開(kāi)的數(shù)據相當簡單,通(tōng)常值得将數(shù)據封裝在界面狀态類型中,因為(wèi)它能(néng)傳達狀态容器的發出與其關聯的屏幕或界面元素之間(jiān)的關系。此外(wài),随着界面元素變得越來越複雜(zá),添加界面狀态的定義來容納呈現界面元素所需的額外(wài)信息始終會更加容易。
創建 UiState 流的一(yī)種常用方法是,将後備可(kě)變數(shù)據流作(zuò)為(wèi)來自(zì) ViewModel 的不可(kě)變數(shù)據流進行(xíng)公開(kāi),例如(rú)将 MutableStateFlow
這(zhè)樣一(yī)來,ViewModel 便可(kě)以公開(kāi)在內(nèi)部更改狀态的方法,以便發布供界面使用的更新。以需要(yào)執行(xíng)異步操作(zuò)的情況為(wèi)例,可(kě)以使用 viewModelScope 啓動協程,并且可(kě)以在操作(zuò)完成時(shí)更新可(kě)變狀态。
在上(shàng)面的示例中,NewsViewModel 類會嘗試獲取特定類别的報道(dào),然後在界面狀态中反映嘗試結果(成功或失敗),其中界面可(kě)以對其做(zuò)出适當反應。如(rú)需詳細了(le)解錯誤處理,請(qǐng)參閱在屏幕上(shàng)顯示錯誤部分。
注意:單向數(shù)據流有多種常用的實現,上(shàng)面示例中顯示的模式(通(tōng)過 ViewModel 上(shàng)的函數(shù)更改狀态)便是其中的一(yī)種。
其他(tā)注意事項
除了(le)前面的指南之外(wài),公開(kāi)界面狀态時(shí)還要(yào)考慮以下(xià)事項:
界面狀态對象應處理彼此相關的狀态。 這(zhè)樣可(kě)以減少(shǎo)不一(yī)緻的情況,并讓代碼更易于理解。如(rú)果您在兩個不同的數(shù)據流中分别公開(kāi)新聞報道(dào)列表和(hé)書(shū)簽數(shù)量,可(kě)能(néng)會發現其中一(yī)個已更新,但(dàn)另一(yī)個沒有更新。當您使用單個數(shù)據流時(shí),這(zhè)兩個元素都(dōu)會保持最新狀态。此外(wài),某些業務邏輯可(kě)能(néng)需要(yào)組合使用數(shù)據源。例如(rú),可(kě)能(néng)隻有在用戶已登錄并且是付費(fèi)新聞服務訂閱者時(shí),您才需要(yào)顯示書(shū)簽按鈕。
在此聲明(míng)中,書(shū)簽按鈕的可(kě)見(jiàn)性是兩個其他(tā)屬性的派生屬性。随着業務邏輯變得越來越複雜(zá),擁有單個 UiState 類,并且其中的所有屬性都(dōu)是立即可(kě)用的,變得越來越重要(yào)。
界面狀态:單個數(shù)據流還是多個數(shù)據流?是選擇在單個數(shù)據流中還是在多個數(shù)據流中公開(kāi)界面狀态,關鍵指導原則是前面提到的要(yào)點:發出的內(nèi)容之間(jiān)的關系。在單個數(shù)據流中進行(xíng)公開(kāi)的最大優勢是便捷性和(hé)數(shù)據一(yī)緻性:狀态的使用方随時(shí)都(dōu)能(néng)立即獲取最新信息。不過,在有些情況下(xià),可(kě)能(néng)适合使用來自(zì) ViewModel 的單獨的狀态流:
不相關的數(shù)據類型:呈現界面所需的某些狀态可(kě)能(néng)是完全相互獨立的。在此類情況下(xià),将這(zhè)些不同的狀态捆綁在一(yī)起的代價可(kě)能(néng)會超過其優勢,尤其是當其中某個狀态的更新頻(pín)率高于其他(tā)狀态的更新頻(pín)率時(shí)。
UiState diffing:UiState 對象中的字段越多,數(shù)據流就越有可(kě)能(néng)因為(wèi)其中一(yī)個字段被更新而發出。由于視(shì)圖沒有 diffing 機制來了(le)解連續發出的數(shù)據流是否相同,因此每次發出都(dōu)會導緻視(shì)圖更新。這(zhè)意味着,可(kě)能(néng)必須要(yào)對 LiveData 使用 Flow API 或 distinctUntilChanged() 等方法來緩解這(zhè)個問(wèn)題。
使用界面狀态
如(rú)需在界面中使用 UiState 對象流,您可(kě)以對所使用的可(kě)觀察數(shù)據類型使用終端運算符。例如(rú),對于 LiveData,您可(kě)以使用 observe() 方法;對于 Kotlin 數(shù)據流,您可(kě)以使用 collect() 方法或其變體。
在界面中使用可(kě)觀察數(shù)據容器時(shí),請(qǐng)務必考慮界面的生命周期。這(zhè)非常重要(yào),因為(wèi)當未向用戶顯示視(shì)圖時(shí),界面不應觀察界面狀态。如(rú)需詳細了(le)解此主題,請(qǐng)參閱這(zhè)篇博文。使用 LiveData 時(shí),LifecycleOwner 會隐式處理生命周期問(wèn)題。使用數(shù)據流時(shí),最好通(tōng)過适當的協程作(zuò)用域和(hé) repeatOnLifecycle API 來處理這(zhè)一(yī)任務
線程處理和(hé)并發
在 ViewModel 中執行(xíng)的所有工(gōng)作(zuò)都(dōu)應具有主線程安全性(即從(cóng)主線程調用是安全的)。這(zhè)是因為(wèi)數(shù)據層和(hé)網域層負責将工(gōng)作(zuò)移至其他(tā)線程。
如(rú)果 ViewModel 執行(xíng)長(cháng)時(shí)間(jiān)運行(xíng)的操作(zuò),則還要(yào)負責将相應邏輯移至後台線程。Kotlin 協程是管理并發操作(zuò)的絕佳方式,Jetpack 架構組件則為(wèi)其提供內(nèi)置支持。如(rú)需詳細了(le)解如(rú)何在 Android 應用中使用協程,請(qǐng)參閱 Android 上(shàng)的 Kotlin 協程。
導航
應用導航的變化通(tōng)常是由類似于事件的發出操作(zuò)驅動的。例如(rú),在 SignInViewModel 類執行(xíng)登錄後,UiState 可(kě)能(néng)會有一(yī)個 isSignedIn 字段被設為(wèi) true。此類觸發器的使用方式應與上(shàng)面使用界面狀态部分介紹的方式相同,不過使用實現應遵從(cóng)導航組件。
Paging
Paging 庫通(tōng)過一(yī)個稱為(wèi) PagingData 的類型在界面中使用。由于 PagingData 表示并包含可(kě)以随時(shí)間(jiān)變化的內(nèi)容(換句話說,它不是不可(kě)變類型),因此它不應以不可(kě)變界面狀态表示。相反,您應在單獨的流中獨立地(dì)從(cóng) ViewModel 中公開(kāi)它。如(rú)需具體示例,請(qǐng)參閱 Android Paging Codelab。
動畫(huà)
為(wèi)了(le)提供流暢的頂級導航過渡,您可(kě)能(néng)需要(yào)等待第二個屏幕加載數(shù)據,然後再啓動動畫(huà)。Android 視(shì)圖框架提供了(le)一(yī)些鈎子,以便通(tōng)過 postponeEnterTransition() 和(hé) startPostponedEnterTransition() API 延遲 fragment 目的地(dì)之間(jiān)的過渡。這(zhè)些 API 提供了(le)一(yī)種方法來确保做(zuò)到以下(xià)一(yī)點:在界面通(tōng)過動畫(huà)過渡到第二個屏幕之前,第二個屏幕上(shàng)的界面元素(通(tōng)常是從(cóng)網絡獲取的圖片)已做(zuò)好顯示準備。如(rú)需了(le)解更多詳情和(hé)實現細節,請(qǐng)參閱 Android Motion 示例
網站建設開(kāi)發|APP設計開(kāi)發|小程序建設開(kāi)發