常見的模組化模式

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

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

高內聚和低耦合原則

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

  • 低耦合表示模組應盡可能各自獨立,因而在變更其中一個模組時,對其他模組產生的影響能夠減少至零或最小。模組不應知道其他模組的內部作業
  • 高內聚代表多個模組應構成程式碼集合,以執行系統的功能。這些模組應具有明確定義的責任,並保持在特定領域知識的範圍內。以 ebook 應用程式為例。將預訂和付款相關程式碼放在同一模組中可能不太合適,因為這些程式碼分屬兩個不同的功能領域。

模組類型

整理模組的方式主要取決於應用程式架構。以下列出遵循我們建議的應用程式架構,可引入應用程式的部分常見模組類型。

資料模組

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

  1. 封裝特定領域的所有資料和商業邏輯:每個資料模組都必須負責處理代表特定領域的資料,可處理多種類型的資料,只要其具有相關性即可。
  2. 將存放區公開為外部 API:資料模組的共用 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。後者會向模組使用者公開轉換依附元件。由於 implementation 可以減少需要重建的模組數量,因此能夠縮短建構時間。

首選使用 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 類型。