Android 應用架構指南

語言: CN / TW / HK

參考連結:應用架構指南  |  Android 開發者  |  Android Developers

Android App 使用者體驗

我們常見的 Android App 一般是由四大元件組成的,其中最常見的是 Activity 和 Service 等。一個 App 可能包含多個元件,而且移動裝置資源有限,系統可能會隨時終止某些 App ,鑑於這種情況,App 被銷燬是不受開發者控制的,那麼 App 的資料和狀態就不應該在記憶體中進行儲存,而且, 不同的元件之間不應該有依賴關係。

基於上述的一些客觀情況,Android 為我們提供了一套開發的基本規則和從系統 API 層面的規避風險的支援。但作為開發者,也要注意避免這些情況的出現。

常見的架構原則

隨著 Android 應用大小不斷增加,App 的架構應該具有支援擴充套件、提升穩定性和方便測試等特性。 應用架構主要是為了劃分各個部分之間的界限以及每個部分應該承擔的職責。 官方為我們指定了一些設計原則讓我們更好的去搭建和設計應用架構。

原則一:分離關注點

分離關注點(Separation of concerns,SoC),是將計算機程式分隔為不同部分的設計原則。每一部分會有自己需要關注的職責。這是最重要的原則。

一種常見的錯誤是在一個 Activity 或 Fragment 中編寫所有的程式碼。這些基於介面的類應該僅處理 UI 和作業系統互動的邏輯

這些類應該儘可能的保持精簡,這樣可以避免許多與元件生命週期相關的問題,並且能夠提升這些類的可測試性。

原則二:通過資料模型驅動介面

第二個重要的原則是,應該通過資料的變化來驅動 UI 的變化。資料模型就是 Model ,代表應用的資料,它們應獨立於應用中的 UI 元素和其他元件,並且不受生命週期影響。但 Model 仍會在 App 銷燬時,從記憶體中移除。

持久化資料是理想之選,原因如下: * 如果 Android 作業系統銷燬應用以釋放資源,使用者不會丟失資料。 * 當網路連線不穩定或不可用時,應用可以繼續工作。

通過 Model 來驅動 UI ,另一個好處就是更方便進行測試。我們可以通過修改資料來測試 UI 的變化。

推薦的應用架構

基於上面的架構原則,App 至少應該分為兩個層級: - 介面層(UI):在螢幕上顯示應用資料。 - 資料層(Model):包含應用的業務邏輯並公開應用資料。

可以增加一箇中間的 Domain 層,來簡化和重複使用介面層與資料層之間的互動。

mad-arch-overview.png

UI 層

UI 層的作用是在螢幕上顯示應用資料。無論是因為使用者互動(例如按下按鈕)還是外部輸入(例如網路響應)導致資料發生變化時,UI 都應更新以反映相應的變化。

UI 層可以進一步拆分成兩個部分: * 在螢幕上呈現資料的 UI 元素。您可以使用 View 或 Jetpack Compose 函式構建這些元素。 * 用於儲存資料、向 UI 提供資料以及處理邏輯的狀態容器(如 ViewModel )。

mad-arch-overview-ui.png

介面的狀態

UI 層高可以拆分成 UI 元素和 UI 狀態兩部分。

mad-arch-ui-elements-state.png

UI 元素就是 UI 中具體的 View ;UI 狀態是相對於應用而言的,可以理解為 UI 的資料邏輯抽象。當資料邏輯抽象發生變化時,就會導致呈現的 UI 的變化。

一個簡單的類比是,通過 ViewModel 控制 UI 狀態。

UI 的狀態應該是定義為不可變的,這樣能夠使 UI 專注於發揮單一的作用。所以,不應該在介面中直接修改 UI 狀態,除非 UI 是狀態資料的唯一來源。

這個意思就是,相當於一個 Switch 控制元件,當你操作 UI 時,會使它的 一個 flag 在 true / false 之間進行切換。這就是一個 UI 是狀態資料的唯一來源。

違反這個原則會導致同一條資訊有多個可信來源,從而導致資料不一致等輕微的 bug。

單向資料流管理 UI 狀態

UI 是 UI 狀態的呈現, UI 狀態是資料邏輯,在客戶端中,一般資料都是動態變化的,不管是使用者操作還是網路請求,都會導致 Model 層的資料更新,進而影響到 UI 狀態的資料發生變化。

而單項資料流(一種架構模式),有助於強制實施這種健康的職責分離。

我們在上面提到過 ViewModel 元件是一個很好的狀態容器。 通過 ViewModel 元件, Model 層和 UI 層進行互動的邏輯是:

mad-arch-ui-udf.png

狀態向下流動、事件向上流動的這種模式稱為單向資料流 (UDF)。這種模式對應用架構的影響如下: * ViewModel 會儲存並公開 UI 要使用的狀態。UI 狀態是經過 ViewModel 轉換的應用資料。 * UI 元素會向 ViewModel 傳送使用者事件輸入的訊息。 * ViewModel 會處理使用者操作並更新狀態。 * 更新後的狀態將反饋給 UI 元素以進行呈現。 * 系統會對導致 UI 狀態更改的所有事件重複上述操作。

一個簡單的例子是,一個文章閱讀 App ,使用者為這篇文章新增到收藏:

mad-arch-ui-udf-in-action.png

邏輯型別

上面的例子中,有一些邏輯需要進行定義或者說是為其劃分層級: - 業務邏輯:業務邏輯決定了如何處理狀態的變化,通常位於中間層或 Model 層中,但絕不能位於 UI 層中。 - 介面行為邏輯/介面邏輯:會導致 UI 呈現內容的變化,這一層邏輯應該位於 UI 層中(尤其是涉及 Context 等介面常有的型別時),而非 ViewModel 中。 如果介面變得越來越複雜,並且您希望將介面邏輯委託給另一個類,以便有利於進行測試和關注點分離,您可以建立一個簡單的類作為狀態容器。

為什麼要使用單項資料流?

mad-arch-ui-udf.png

單項資料流建立了上面這張圖的模型,種分離可讓介面只發揮其名稱所表明的作用:通過觀察 UI 狀態變化來顯示資訊,並通過將這些變化傳遞給 ViewModel 來傳遞使用者的輸入。 換句話說,單項資料流有助於實現以下幾點: - 資料一致性:介面只有一個可信來源。 - 可測試性:狀態來源是獨立的,因此可獨立於介面進行測試。 - 可維護性:狀態的更改遵循明確定義的模式,即狀態更改是使用者事件及其資料拉取來源共同作用的結果。

而 UI 狀態這一層級,可以以 ViewModel 作為容器,以 LiveData 作為狀態的資料類傳遞工具(通過觀察者模式,可以讓 View 感知到狀態資料的變化)。

資料層

Model 層應該包含業務邏輯。業務邏輯決定應用的價值,它包含決定應用如何建立、儲存和更改資料的規則。

資料層可以由多個數據管理的 Repositories 組成,每個 Repositories 可以包含零到多個數據源。

mad-arch-overview-data.png

Repositories 類負責以下任務: * 提供資料。 * 對資料進行集中管理。 * 解決多個數據源之間的衝突。 * 包含業務邏輯。

每個 Repositories 類應僅負責處理一個數據源,該資料來源可以是檔案、網路來源或本地資料庫。

需要注意的是,Model 層的資料物件會儲存在記憶體中,當 App 被系統銷燬時,需要注意做資料的儲存處理。

另一方面是資料物件應該是被強引用的,不會因為 GC 而造成資料的丟失。

Domain 層(可選層)

是位於介面與資料層之間的可選層。負責封裝複雜的業務邏輯,或者由多個 ViewModel 重複使用的簡單業務邏輯。此層是可選的,因為並非所有應用都有這類需求。請僅在需要時使用該層,例如處理複雜邏輯或支援可重用性。

總結

儘管官方的推薦的這套架構,並沒有明確指明是 MVVM ,但是從思想上還是和 MVVM 十分相似的。更準確的說,他更多的是 Presentation Model 模式的思路。 PM 的思想是將 View 抽象出一層 View 的抽象,然後通過 View 的抽象去與 Model 進行互動。 而在這個推薦的架構中,對 View 的抽象就是 UI State ,UI 元素則是指 View 。 通過 UI 狀態去與 Model 進行互動也對應了 PM 中 , Presentation Model 的角色。