應用程式架構指南

應用程式架構是優質 Android 應用程式的基礎。定義完善的架構可讓您建立可擴充及維護的應用程式,因應不斷擴大的 Android 裝置生態系統,包括手機、平板電腦、折疊式裝置、ChromeOS 裝置、車輛螢幕和 XR。

應用程式組合

一般 Android 應用程式是由多個應用程式元件組成,例如服務內容供應者廣播接收器。您必須在應用程式資訊清單中宣告這些元件。

應用程式的使用者介面也是元件。過去,UI 是使用多個活動建構而成。不過,現代應用程式採用單一活動架構。單一 Activity 可做為畫面或 Jetpack Compose 目的地的容器。

多種板型規格

應用程式可支援多種板型規格,包括手機、平板電腦、折疊式裝置和 ChromeOS 裝置等。請勿假設應用程式一律會固定在直向或橫向模式。設定變更 (例如裝置旋轉,或折疊及展開折疊式裝置) 會強制應用程式重組 UI,進而影響應用程式狀態。

資源限制

行動裝置 (包括大螢幕裝置) 的資源有限,因此作業系統隨時可能停止應用程式程序,將資源提供給其他程序。

變數啟動條件

在資源受限的環境中,應用程式的元件可能會個別啟動,且啟動順序不一定,此外,作業系統或使用者可以隨時刪除這些元件。因此,請勿在應用程式元件中儲存任何應用程式資料或狀態。讓應用程式元件自給自足,彼此獨立。

常見架構原則

如果無法使用應用程式元件儲存應用程式資料和狀態,應該如何設計應用程式?

隨著 Android 應用程式的規模不斷擴充,請務必定義架構,讓應用程式能夠調度資源。設計良好的應用程式架構會定義應用程式各部分之間的關聯,以及每個部分應負的責任。

關注點分離

設計應用程式架構時,請務必遵循下列特定原則。

最重要的原則是區隔注意事項:將應用程式區隔為方法、類別、檔案、套件、模組和層,並明確定義責任和界線。

常見的做法是在 Activity 中寫入所有程式碼。

Activity 的主要角色是代管應用程式的 UI。Android 作業系統會控管這些項目的生命週期,並經常因應使用者動作 (例如螢幕旋轉) 或系統事件 (例如記憶體不足) 刪除及重新建立這些項目。

因此不適合保存應用程式資料或狀態。如果您將資料儲存在 Activity 中,系統會在重新建立元件時遺失該資料。為確保資料持續存在並提供穩定的使用者體驗,請勿將狀態委託給這些 UI 元件。

自動調整式版面配置

建構應用程式時,請確保能妥善處理設定變更,例如裝置螢幕方向變更或應用程式視窗大小變更。導入自動調整式標準版面配置,在各種板型規格上提供最佳使用者體驗。

透過資料模型使用雲端硬碟使用者介面

另一個重要原則是,您應將使用者介面從資料模型 (建議使用永久模型) 驅動。資料模型代表應用程式的資料。兩者與應用程式中的 UI 元素和其他元件無關。這表示,並未綁定 UI 和應用程式元件的生命週期,但當作業系統從記憶體中移除應用程式的處理程序時,仍會銷毀。

永久模型非常適合下列原因:

  • 如果 Android 作業系統 刪除您的應用程式以釋出資源,使用者並不會遺失資料。

  • 網路連線不穩定或無法使用時,您的應用程式仍會繼續運作。

以資料模型類別為基礎建構應用程式架構,讓應用程式更穩定且可測試。

單一可靠資料來源

在應用程式中定義新的資料型別時,請為其指派單一可靠資料來源 (SSOT)。SSOT 是該資料的擁有者,只有 SSOT 可以修改或變更資料。為此,SSOT 會使用不可變動的型別公開資料;如要修改資料,SSOT 會公開函式或接收其他型別可呼叫的事件。

這個模式有多項優點:

  • 集中管理特定類型資料的所有變更
  • 保護資料,防止其他類型竄改
  • 更容易追蹤資料變更,方便找出錯誤

在離線優先應用程式中,應用程式資料的可靠來源通常是資料庫。在其他情況下,事實來源可以是 ViewModel

單向資料流程

單一可靠資料來源原則通常會與單向資料流 (UDF) 模式搭配使用。在 UDF 中,狀態只會單向流動,通常是從父項元件流向子項元件。反向修改資料流程的事件。

在 Android 中,狀態或資料通常會從階層中範圍較大的型別,流向範圍較小的型別。事件通常會從範圍較小的型別觸發,直到抵達對應資料型別的 SSOT 為止。舉例來說,應用程式資料通常會從資料來源流向 UI。使用者事件 (例如按下按鈕) 會從 UI 傳送至 SSOT,應用程式資料會在 SSOT 中修改,並以不可變更的型別公開。

這個模式可更妥善地維護資料一致性、較不易發生錯誤、更容易偵錯,並提供單一事實來源模式的所有優點。

如要進一步瞭解 UDF,請參閱「Jetpack Compose 的單向資料流程」。

考量一般架構原則,設計每個應用程式時,至少應包含兩個層:

  • UI 層:在畫面上顯示應用程式資料
  • 資料層:包含應用程式的商業邏輯,並揭露應用程式資料

您可以新增名為網域層的額外層,藉此簡化和重複使用使用者介面與資料層之間的互動。

在一般應用程式架構中,使用者介面層會從資料層或選用的網域層取得應用程式資料 (位於使用者介面層和資料層之間)。
圖 1. 典型應用程式架構圖。

現代應用程式架構

現代化 Android 應用程式架構會使用下列技術 (包括其他技術):

  • 適應性分層架構
  • 應用程式所有層級的單向資料流程 (UDF)
  • 使用者介面層,其中包含狀態容器,可管理複雜的 UI
  • 協同程式和資料流
  • 依附元件插入最佳做法

詳情請參閱「Android 架構建議」。

UI 層

UI 層 (或呈現層) 的作用是在畫面上顯示應用程式資料。只要資料因使用者互動 (例如按下按鈕) 或外部輸入 (例如網路回應) 而改變,使用者介面就會更新以反映變更。

UI 層包含兩種建構函式:

  • 在畫面上顯示資料的 UI 元素。您可以使用 Jetpack Compose 函式建構這些元素,支援自動調整式版面配置。
  • 狀態持有者 (例如 ViewModel) 保存資料、將其公開至使用者介面並處理邏輯。狀態持有物件的存續時間應與提供狀態的 UI 元素相同。舉例來說,畫面的 ViewModel 應保留在記憶體中,直到畫面從應用程式的導覽返回堆疊中移除為止。詳情請參閱「狀態生命週期」。
在一般架構中,UI 層的 UI 元素是取決於狀態持有者,而後者則取決於資料層或選用網域層的類別。
圖 2. UI 層在應用程式架構中的角色。

對於自動調整式 UI,狀態容器 (例如 ViewModel 物件) 會公開 UI 狀態,以配合不同的視窗大小類別。您可以透過 currentWindowAdaptiveInfo() 推導出這個 UI 狀態。NavigationSuiteScaffold 等元件隨後就能使用這項資訊,根據可用螢幕空間,自動在不同導覽模式 (例如 NavigationBarNavigationRailNavigationDrawer) 之間切換。

詳情請參閱「UI 層」和「Compose UI 架構」。

如要進一步瞭解自動調整式應用程式和導覽功能,請參閱「建構自動調整式應用程式」和「建構自動調整式導覽功能」。

資料層

應用程式的資料層包含商業邏輯。商業邏輯可為應用程式提供價值,且會根據決定應用程式建立、儲存及變更資料的規則組成。

資料層是由存放區組成,每個存放區可包含零到多個資料來源。針對應用程式中處理的各種資料類型,建立存放區類別。舉例來說,您可以為電影相關資料建立 MoviesRepository 類別,或是為款項相關資料建立 PaymentsRepository 類別。

在一般架構中,資料層存放區可提供資料給應用程式其餘部分,而且依附於資料來源。
圖 3. 資料層在應用程式架構中的角色。

存放區類別負責以下工作:

  • 向應用程式的其餘部分公開資料
  • 集中處理資料的變更
  • 解決多個資料來源之間的衝突
  • 從應用程式的其餘部分抽象化資料來源
  • 涵蓋商業邏輯

每個資料來源類別都應只負責處理一個資料來源 (可以是檔案、網路來源或本機資料庫)。資料來源類別是應用程式與系統之間處理資料運算的橋樑。

詳情請參閱資料層頁面

網域層

網域層是 UI 層和資料層之間的選用層。

網域層負責封裝複雜的商業邏輯,或是多個 ViewModel 重複使用的簡易商業邏輯。網域層為選用性質,因為並非所有應用程式都有上述要求。建議只在需要時才使用,例如處理複雜度或可重複使用。

如果包含在內,選用的網域層就可提供 UI 層的依附元件,並依附於資料層。
圖 4. 網域層在應用程式架構中的角色。

網域層中的類別通常稱為「用途」或「互通者」。每個用途都負責單一功能。舉例來說,如果多個檢視區塊模型仰賴時區以在螢幕上顯示適當的訊息,則應用程式可能擁有 GetTimeZoneUseCase 類別。

詳情請參閱網域層頁面

管理元件之間的依附元件

應用程式中的類別需要其他類別才能正常運作。您可以使用下列任一設計模式來收集特定類別的依附元件:

  • 依附元件植入 (DI):依附元件植入可讓類別定義其依附元件,而不必建構這些依附元件。在執行階段,另一個類別會負責提供這些依附元件。
  • 服務定位器:服務定位器模式提供註冊資料庫,可讓類別取得依附元件,而不是建構依附元件。

這些模式可讓您擴充程式碼,因為該格式提供了管理依附元件的明確模式,而不必複製程式碼或增加複雜度。這些模式還可讓您快速切換測試和實際工作環境實作。

一般最佳做法

程式設計是廣告素材欄位,建構 Android 應用程式也不例外。解決問題的方法有很多種;您可能會在多個 Activity 或片段之間傳輸資料、擷取遠端資料,並在本機儲存資料以供離線模式使用,或處理任何不常見的應用程式遇到的任何其他常見情況。

雖然下列建議做法並非必要,但在大多數情況下,這些程式碼能讓您更穩固、長期地測試及維護程式碼。

不要將資料儲存在應用程式元件中。

避免將應用程式的進入點 (例如活動、服務和廣播接收器) 指定為資料來源。讓進入點與其他元件協調,只擷取與該進入點相關的部分資料。每個應用程式元件都會相對短暫,具體而言取決於使用者如何與裝置互動,以及系統容量。

減少 Android 類別的依附元件。

讓應用程式元件成為僅仰賴 Android 架構 SDK API (例如 ContextToast) 的類別。排除應用程式中的其他類別,可幫助測試及減少應用程式中的組合

在應用程式中,定義模組之間的明確責任範圍。

請勿將程式碼從網路載入資料的代碼分散到程式碼集的多個類別或套件中。同樣地,請避免在同一類別定義多項不相關的工作,例如資料快取和資料繫結。遵循建議的應用程式架構

每個單元盡可能不要曝光。

請勿建立會公開內部實作詳細資料的捷徑。雖然短期內可能會爭取到一些時間,但隨著程式碼基礎的發展,您可能需要支付技術債務。

將重點放在應用程式的獨特核心,讓應用程式脫穎而出。

請勿藉由重複撰寫相同的樣板程式碼來做無謂的重複作業。而是將時間和精力投入於應用程式的獨特之處。並讓 Jetpack 程式庫和其他推薦的程式庫處理重複的樣板。

使用標準版面配置和應用程式設計模式。

Jetpack Compose 程式庫提供功能完善的 API,可建構自動調整式使用者介面。在應用程式中使用標準版面配置,針對多種板型規格和螢幕尺寸提供最佳使用者體驗。請查看應用程式設計模式圖庫,選取最適合您用途的版面配置。

在設定變更期間保留 UI 狀態。

設計自動調整式版面配置時,請在設定變更 (例如調整螢幕大小、折疊及變更螢幕方向) 期間保留 UI 狀態。您的架構應驗證使用者的目前狀態是否維持不變,以提供流暢的體驗。

設計可重複使用的可組合 UI 元件。

建構可重複使用和可組合的 UI 元件,支援自動調整式設計。 這樣一來,您就能組合及重新排列元件,以配合各種螢幕大小和姿勢,而無需大幅重構。

考慮讓應用程式的每個部分獨立進行測試。

明確定義的網路資料擷取 API 有助於測試在本機資料庫中保留該資料的模組。或者,假如您將這兩個函式的邏輯放在同一個位置,或是將網路程式碼分配給整個程式碼集,那麼這樣的測試將變得困難許多,甚至無法做到。

型別負責其並行政策。

如果某個型別執行長時間的封鎖作業,該型別就必須負責將運算作業移至正確的執行緒。型別會知道執行的運算種類,以及要執行運算的執行緒。型別應對主執行緒沒有威脅,也就是可安全地從主執行緒呼叫,不會阻斷主執行緒。

請盡可能保留相關且最新的資料。

這樣一來,即使裝置處於離線模式,也能享有應用程式的功能性。提醒您,並非所有使用者都能享有穩定快速的高速連線,即使他們使用這些功能,也不會在擁擠場所中收到不良訊號。

架構的優點

在應用程式中導入良好的架構,可為專案和工程團隊帶來許多好處:

  • 提升整體應用程式的維護性、品質和穩定性。
  • 讓應用程式縮放。更多人與團隊可以為同一個程式碼集貢獻心力,且程式碼衝突情況極少。
  • 有助於新手上路。架構可讓專案保持一致性,因此團隊新成員能快速上手,在更短的時間內提高效率。
  • 更容易測試。良好的架構會鼓勵使用較簡單的型別,這類型別通常更容易測試。
  • 透過定義明確的程序,有條不紊地調查錯誤。

雖然良好的架構需要預先投入時間,但也會直接影響使用者。工程團隊的工作效率提升後,應用程式會更加穩定,並提供更多功能,因此使用者也能受益。

範例

以下範例展現了良好的應用程式架構: