應用程式架構是優質 Android 應用程式的基礎。定義完善的架構可協助您建立可擴充及維護的應用程式,因應不斷擴大的 Android 裝置生態系統,包括手機、平板電腦、折疊式裝置、ChromeOS 裝置、車輛螢幕和 XR。
應用程式組合
一般 Android 應用程式是由多個應用程式元件組成,例如服務、內容供應者和廣播接收器。您必須在應用程式資訊清單中宣告這些元件。
應用程式的使用者介面也是元件。過去,UI 是使用多個活動建構而成。不過,現代應用程式採用單一活動架構。單一 Activity 可做為容器,裝載以片段或 Jetpack Compose 目的地實作的畫面。
多種板型規格
應用程式可在多種板型規格上執行,包括手機、平板電腦、折疊式裝置、ChromeOS 裝置等。應用程式無法假設直向或橫向模式。設定變更 (例如旋轉裝置,或折疊及展開摺疊式裝置) 會強制應用程式重新組合 UI,進而影響應用程式資料和狀態。
資源限制
行動裝置 (包括大螢幕裝置) 的資源有限,因此作業系統隨時可能停止部分應用程式程序,以騰出空間使用新的裝置。
變數啟動條件
在資源受限的環境中,應用程式的元件可能會個別啟動,且啟動順序不一定,此外,作業系統或使用者可以隨時刪除這些元件。因此,請勿在應用程式元件中儲存任何應用程式資料或狀態。應用程式元件應獨立運作,彼此互不相干。
常見架構原則
如果無法使用應用程式元件儲存應用程式資料和狀態,應該如何設計應用程式?
隨著 Android 應用程式的規模不斷擴充,請務必定義架構,讓應用程式能夠調度資源。設計完善的應用程式架構會定義應用程式各部分之間的關聯,以及每個部分應負的責任。
關注點分離
設計應用程式架構時,請務必遵循下列特定原則。
最重要的原則是關注點分離。常見的做法是在 Activity 或 Fragment 中寫入所有程式碼。
Activity 或 Fragment 的主要角色是代管應用程式的 UI。Android OS 會控管這些項目的生命週期,並經常因應使用者動作 (例如螢幕旋轉) 或系統事件 (例如記憶體不足) 刪除及重新建立這些項目。
由於這類資料的暫時性,因此不適合用於保存應用程式資料或狀態。如果您將資料儲存在 Activity 或 Fragment 中,重新建立元件時,該資料就會遺失。為確保資料持續存在並提供穩定的使用者體驗,請勿將狀態委託給這些 UI 元件。
自動調整式版面配置
應用程式應妥善處理設定變更,例如裝置螢幕方向變更或應用程式視窗大小變更。導入自動調整式標準版面配置,在各種板型規格上提供最佳使用者體驗。
透過資料模型使用雲端硬碟使用者介面
另一個重要原則是,您應將使用者介面從資料模型 (建議使用永久模型) 驅動。資料模型代表應用程式的資料。兩者與應用程式中的 UI 元素和其他元件無關。這表示,並未綁定 UI 和應用程式元件的生命週期,但當作業系統從記憶體中移除應用程式的處理程序時,仍會銷毀。
永久模型非常適合下列原因:
如果 Android OS 刪除您的應用程式以釋出資源,使用者並不會遺失資料。
網路連線不穩定或無法使用時,您的應用程式仍會繼續運作。
以資料模型類別為基礎建構應用程式架構,讓應用程式更穩定且可測試。
單一可靠資料來源
在應用程式中定義新的資料型別時,您應為其指派單一可靠資料來源 (SSOT)。SSOT 是該資料的擁有者,只有 SSOT 可以修改或變更資料。為此,SSOT 會使用不可變動的型別公開資料;如要修改資料,SSOT 會公開函式或接收其他型別可呼叫的事件。
這個模式有多項優點:
- 集中管理特定類型資料的所有變更
- 保護資料,避免遭到其他類型竄改
- 讓資料變更更容易追蹤,因此更容易發現錯誤
在離線優先應用程式中,應用程式資料的可靠資料來源通常是資料庫。在其他情況下,事實來源可以是 ViewModel。
單向資料流程
單一可靠資料來源原則通常會與單向資料流 (UDF) 模式搭配使用。在 UDF 中,狀態只會單向流動,通常是從父項元件流向子項元件。反向修改資料流的事件。
在 Android 中,狀態或資料通常會從階層中範圍較大的型別,流向範圍較小的型別。事件通常會從範圍較小的型別觸發,直到抵達對應資料型別的 SSOT 為止。舉例來說,應用程式資料通常會從資料來源流向 UI。使用者事件 (例如按下按鈕) 會從 UI 傳送至 SSOT,應用程式資料會在 SSOT 中修改,並以不可變更的型別公開。
這種模式可更妥善地維護資料一致性、較不易發生錯誤、更容易偵錯,並提供 SSOT 模式的所有優點。
建議的應用程式架構
考量一般架構原則,每個應用程式至少應包含兩個層:
- UI 層:在畫面上顯示應用程式資料
- 資料層:包含應用程式的商業邏輯,並揭露應用程式資料
您可以新增名為網域層的額外層,藉此簡化和重複使用使用者介面與資料層之間的互動。
現代應用程式架構
現代化 Android 應用程式架構會使用下列技術 (包括其他技術):
- 適應性分層架構
- 應用程式所有層級的單向資料流 (UDF)
- 使用者介面層,其中包含狀態容器,可管理複雜的 UI
- 協同程式和資料流
- 依附元件插入最佳做法
詳情請參閱「Android 架構建議」。
UI 層
UI 層 (或呈現層) 的作用是在畫面上顯示應用程式資料。只要資料因使用者互動 (例如按下按鈕) 或外部輸入 (例如網路回應) 而改變,UI 都應更新以反映變更。
UI 層包含兩種建構函式:
- 在畫面上顯示資料的 UI 元素。您可以使用 Jetpack Compose 函式建構這些元素,支援自動調整式版面配置。
- 狀態持有者 (例如
ViewModel) 保存資料、將其公開至 UI 並處理邏輯
對於自動調整式 UI,狀態容器 (例如 ViewModel 物件) 會公開 UI 狀態,以配合不同的視窗大小類別。您可以透過 currentWindowAdaptiveInfo() 推導出這個 UI 狀態。NavigationSuiteScaffold 等元件隨後就能使用這項資訊,根據可用螢幕空間,自動在不同導覽模式 (例如 NavigationBar、NavigationRail 或 NavigationDrawer) 之間切換。
詳情請參閱「UI 層頁面」。
資料層
應用程式的資料層包含商業邏輯。商業邏輯可為應用程式提供價值,且會根據決定應用程式建立、儲存及變更資料的規則組成。
資料層是由存放區組成,每個存放區可包含零到多個資料來源。您應針對應用程式中處理的各種資料類型建立存放區類別。舉例來說,您可以為電影相關資料建立 MoviesRepository 類別,或是為款項相關資料建立 PaymentsRepository 類別。
存放區類別負責以下工作:
- 向應用程式的其餘部分公開資料
- 集中處理資料的變更
- 解決多個資料來源之間的衝突
- 抽象化應用程式其餘部分的資料來源
- 涵蓋商業邏輯
每個資料來源類別都應只負責處理一個資料來源 (可以是檔案、網路來源或本機資料庫)。資料來源類別是應用程式與系統之間處理資料運算的橋樑。
詳情請參閱資料層頁面。
網域層
網域層是 UI 層和資料層之間的選用層。
網域層負責封裝複雜的商業邏輯,或是多個 ViewModel 重複使用的簡易商業邏輯。網域層為選用性質,因為並非所有應用程式都有上述要求。建議只在需要時才使用,例如處理複雜度或可重複使用。
網域層中的類別通常稱為「用途」或「互通者」。每次使用時,都必須對單一功能負責。舉例來說,如果多個檢視區塊模型仰賴時區以在螢幕上顯示適當的訊息,則應用程式可能擁有 GetTimeZoneUseCase 類別。
詳情請參閱網域層頁面。
管理元件之間的依附元件
應用程式中的類別需要其他類別才能正常運作。您可以使用下列任一設計模式來收集特定類別的依附元件:
- 依附元件插入 (DI):依附元件插入可讓類別定義依附元件,而不必建構這些依附元件。在執行階段,另一個類別會負責提供這些依附元件。
- 服務定位器:服務定位器模式提供註冊資料庫,可讓類別取得依附元件,而不是建構依附元件。
這些模式可讓您擴充程式碼,因為該格式提供了管理依附元件的明確模式,而不必複製程式碼或增加複雜度。這些模式還可讓您快速切換測試和實際工作環境實作。
一般最佳做法
程式設計是廣告素材欄位,建構 Android 應用程式也不例外。解決問題的方法有很多種;您可能會在多個活動或片段之間傳輸資料、擷取遠端資料,並在本機儲存資料以供離線模式使用,或處理任何不常見的應用程式遇到的任何其他常見情況。
雖然下列建議做法並非必要,但在大多數情況下,這些程式碼能讓您更穩固、長期地測試及維護程式碼。
不要將資料儲存在應用程式元件中。
避免將應用程式的進入點 (例如活動、服務和廣播接收器) 指定為資料來源。進入點應只與其他元件協調,才能擷取與該進入點相關的部分資料。每個應用程式元件都會相對短暫,具體而言取決於使用者如何與裝置互動,以及系統容量。
減少 Android 類別的依附元件。
您的應用程式元件應成為僅仰賴 Android 架構 SDK API (例如 Context 或 Toast) 的類別。排除應用程式中的其他類別,可幫助測試及減少應用程式中的組合。
在應用程式中,定義模組之間的明確責任範圍。
請勿將程式碼從網路載入資料的代碼分散到程式碼集的多個類別或套件中。同樣地,請避免在同一類別定義多項不相關的工作,例如資料快取和資料繫結。遵循建議的應用程式架構可協助您完成這項作業。
每個單元盡可能不要曝光。
請勿建立會公開內部實作詳細資料的捷徑。雖然短期內可能會出現一些時間,但隨著程式碼基礎的發展,您可能需要支付技術債務。
將重點放在應用程式的獨特核心,讓應用程式脫穎而出。
請勿藉由重複撰寫相同的樣板程式碼來做無謂的重複作業。請將時間與精力投注在應用程式的獨特之處。並讓 Jetpack 程式庫和其他推薦的程式庫處理重複的樣板。
使用標準版面配置和應用程式設計模式。
Jetpack Compose 程式庫提供功能強大的 API,可建構適應性使用者介面。在應用程式中使用標準版面配置,針對多種板型規格和螢幕尺寸提供最佳使用者體驗。請參閱應用程式設計模式圖庫,選取最適合您用途的版面配置。
在設定變更期間保留 UI 狀態。
設計自動調整式版面配置時,請在設定變更 (例如調整螢幕大小、折疊及變更螢幕方向) 時保留 UI 狀態。您的架構應確認使用者目前的狀態維持不變,以提供流暢的體驗。
設計可重複使用且可組合的 UI 元件。
建構可重複使用和可組合的 UI 元件,支援自動調整式設計。 這樣一來,您就能組合及重新排列元件,以配合各種螢幕大小和姿勢,而無需大幅重構。
考慮讓應用程式的每個部分獨立進行測試。
明確定義的網路資料擷取 API 有助於測試在本機資料庫中保留該資料的模組。或者,假如您將這兩個函式的邏輯放在同一個位置,或是將網路程式碼分配給整個程式碼集,那麼這樣的測試將變得困難許多,甚至無法做到。
型別負責其並行政策。
如果某個型別執行長時間的封鎖作業,該型別就必須負責將運算作業移至正確的執行緒。型別會知道執行的運算種類,以及應執行運算的執行緒。型別應對主執行緒沒有威脅,也就是可安全地從主執行緒呼叫,且不會封鎖主執行緒。
請盡可能保留相關且最新的資料。
這樣一來,即使裝置處於離線模式,也能享有應用程式的功能性。提醒您,並非所有使用者都能享有穩定快速的高速連線,即使他們使用這些功能,也不會在擁擠場所中收到不良訊號。
架構的優點
在應用程式中導入良好的架構,可為專案和工程團隊帶來許多好處:
- 提升整體應用程式的維護性、品質和穩定性。
- 擴展能力:更多人與團隊可以為同一個程式碼集貢獻心力,且程式碼衝突情況極少。
- 有助於新手上路。架構可為專案帶來一致性,因此團隊新成員能快速上手,在較短的時間內提高效率。
- 更容易測試。良好的架構會鼓勵使用較簡單的型別,這類型別通常更容易測試。
- 透過定義完善的程序,有條不紊地調查錯誤。
投資架構也會直接影響使用者。工程團隊的工作效率提升後,應用程式的穩定性更高,功能也更豐富,不過,架構也需要預先投入時間。如要向貴機構其他成員說明投入時間的必要性,請參閱這些個案研究,瞭解其他公司分享的成功案例,以及應用程式架構良好帶來的優勢。
範例
以下範例展現了良好的應用程式架構: