常見的模組化模式

Stay organized with collections Save and categorize content based on your preferences.

沒有任何一種單一模組化策略可適用於所有專案。由於 Gradle 的靈活性很大,因此對於安排專案的方式幾乎沒有限制。本頁概述開發多模組 Android 應用程式時可使用的一些一般規則和常見模式。

高內聚和低耦合原則

模組程式碼集的特性之一,就是使用耦合內聚屬性。耦合功能衡量模組彼此的依賴程度。在此情況下,內聚會衡量單一模組的元素與功能的相關性。作為一般規則,您應盡力採用低耦合和高聚合:

  • 低耦合代表模組必須盡可能獨立,因此變更其中一個模組對其他模組不會產生影響或產生最小影響。模組不應知道其他模組的內部作業
  • 高內聚代表模組包含一系列程式碼,當做系統。他們應明確定義責任,並保持在特定網域的知識範圍內。以 ebook 應用程式為例。將預訂和付款相關程式碼放在同一模組中可能不太合適,因為它們是兩個不同的功能網域。

模組類型

整理模組的方式主要取決於應用程式架構。以下是在遵守建議的應用程式架構時,在應用程式中引入的一些常見模組類型。

資料模組

資料模組通常包含存放區、資料來源和模型類別。資料模組的三個主要作用如下:

  1. 封裝特定網域的所有資料和商業邏輯:每個資料模組都必須負責處理代表特定網域的資料。可處理多種類型的資料,只要其具有相關性。
  2. 將存放區顯示為外部 API:資料模組的共用 API 應為存放區,因為它們負責將資料提供給應用程式的其他部分。
  3. 隱藏外部的所有實作詳細資料和資料來源:只有來自相同模組的存放區才能存取資料來源。它們不會對外公開。如要強制執行,可以使用 Kotlin 的 privateinternal 瀏覽權限關鍵字。
圖 1:範例資料模組及其內容

特色模組

「功能」是應用程式功能的獨立部分,通常對應於一個螢幕或一系列密切相關的螢幕,例如註冊或結帳流程。如果您的應用程式有底部導覽列,則每個到達網頁可能都是一個功能。

圖 2:此應用程式的各個分頁標籤可定義為功能

功能與應用程式中的畫面或到達網頁相關聯。因此,它們可能有相關聯的 UI 和 ViewModel,可以處理邏輯和狀態。單一功能不必受限於單一檢視畫面或導覽到達網頁。功能模組取決於資料模組。

圖 3:功能模組及其內容範例

應用程式模組

應用程式模組是應用程式的進入點。它們依附功能模組,且通常提供根層級導覽。憑藉建構變數,單一應用程式模組可以編譯為多個不同的二進位檔。

圖 3:「試用版」和「完整版」變種版本模組依附元件圖表

如果您的應用程式指定多種裝置類型(例如汽車、穿戴式產品或電視),您可以考慮為每個類型定義一個應用程式模組。這有助於區隔平台專用的依附元件。

圖 4:Wear 應用程式依附元件圖表

常見模組

常見模組(又稱為核心模組)包含其他模組常用的程式碼。它們可以減少備援功能,而且不代表應用程式架構中的任何特定層。以下是常見模組的範例:

  • UI 模組:如果您在應用程式中使用自訂 UI 元素或精細品牌宣傳,應考慮將小工具集合封裝至模組,以便重複使用所有功能。這有助於讓使用者介面在不同功能中保持一致。舉例來說,如果您的主題設定為集中式,可以避免在品牌重塑時進行複雜的重構。
  • 分析模組:追蹤通常由業務需求決定,很少考慮軟體架構。分析追蹤器通常用於許多不相關的元件中。如果是這種情況,最好設定專屬的分析模組。
  • 網路模組:如果許多模組需要網路連線,您可以考慮用專屬模組來提供 HTTP 用戶端。在用戶端需要自訂設定時特別實用。
  • 公用程式模組:公用程式(又稱為輔助程式)通常是一小段可在應用程式中重複使用的程式碼。公用程式範例包括測試輔助程式、貨幣格式設定函式、電子郵件驗證工具或自訂運算子。

進行模組化通訊的模組

模組鮮少完全獨立存在,通常依附於其他模組並與之進行通訊。即使模組能搭配運作,並經常交換資訊,請務必保持低耦合狀態。有時候,在架構受限的情況下,在兩個模組之間直接通訊並不適合。也可能無法實現,例如帶有循環依附元件。

圖 5:由於循環依附元件的關係,模組無法直接雙向通訊。必須使用調節模組協調兩個其他獨立模組之間的資料流

如要解決這個問題,您可以在兩個其他模組之間加入第三個模組調節。調節模組可以監聽兩個模組的訊息,並視需要轉送訊息。在我們的範例應用程式中,即使事件起源於一個單獨螢幕,且屬於不同功能的一部分,結帳畫面必須知道要購買哪一本書。在這種情況下,調解器是擁有導覽圖的模組(通常是應用程式模組)。在此範例中,我們使用導航元件將資料從首頁功能傳送至結帳功能。

navController.navigate("checkout/$bookId")

結帳到達網頁會收到書籍 ID 做為引數,用來擷取書籍相關資訊。您可以使用已儲存的狀態控點來擷取到達網頁功能的 ViewModel 內的導覽引數。

class CheckoutViewModel(savedStateHandle: SavedStateHandle, …) : ViewModel() {

   val uiState: StateFlow<CheckoutUiState> =
      savedStateHandle.getStateFlow<String>("bookId", "").map { bookId ->
          // produce UI state calling bookRepository.getBook(bookId)
      }
      …
}

請勿傳送物件做為導覽引數。請改用簡單的 ID,該功能可用於從資料層存取及載入所需資源。如此一來,您可以保持低耦合狀態,而且不會違反單一可靠來源的原則。

在以下範例中,兩個功能模組取決於相同的資料模組。因此,您可以盡量減少調解模組轉發所需的資料量,並使模組之間保持低耦合狀態。模組應交換原始 ID 並從共用資料模組載入資源,而不是傳遞物件。

圖 6:兩個依賴於共用資料模組的功能模組

一般最佳做法

如前文所述,開發多模組應用程式並沒有一種正確方法。就像許多軟體架構一樣,應用程式模組化的方法也有很多。然而,下列一般建議可協助您讓程式碼更易讀、可維護且可測試。

保持設定一致

每個模組都會引入設定負擔。如果模組數量達到特定閾值,那麼管理一致的設定就會成為難題。舉例來說,模組必須使用相同版本的依附元件,這點很重要。如果您需要更新大量模組,而只是為了觸碰傳輸依附元件版本,則不僅要付出心力,還要面臨潛在的錯誤。為解決這個問題,您可以使用其中一項 Gradle 工具集中管理設定:

  • 版本目錄是 Gradle 在同步處理時產生的依附元件類型清單。這裡是您宣告所有依附元件的主要地點,並可供專案中的所有模組使用。
  • 使用慣例外掛程式在模組之間共用建構邏輯。

盡可能減少曝露

模組的公開介面應盡可能精簡,且只顯示常用項目。請勿將任何實作詳細資料外洩。請盡可能縮小範圍。使用 Kotlin 的 privateinternal 瀏覽權限範圍,將宣告設為不公開。在模組中宣告依附元件時,首選 implementation,而不是 api。後者會向模組使用者公開轉換依附元件。使用實作可以減少建構時間,因為這樣可以減少需要重建的模組數量。

首選使用 Kotlin 和 Java 模組

Android Studio 支援的基本模組類型有三種:

  • 應用程式模組是應用程式的進入點。其中包含原始碼、資源、資產和 AndroidManifest.xml。應用程式模組的輸出內容是 Android App Bundle (AAB) 或 Android 應用程式套件 (APK)。
  • 程式庫模組與應用程式模組的內容相同。其他 Android 模組會使用這些模組做為依附元件。程式庫模組的輸出內容為 Android Archive (AAR),與應用程式模組的結構相同,但系統會將其編譯為 Android Archive (AAR) 檔案,以供其他模組用作依附元件。程式庫模組可讓您封裝並重複使用多個應用程式模組中的相同邏輯和資源。
  • Kotlin 和 Java 程式庫不包含任何 Android 資源、資產或資訊清單檔案。

由於 Android 模組會產生負擔,因此最好盡可能使用 Kotlin 或 Java 類型。