Android 官方專案是怎麼做模組化的?快來學習下

語言: CN / TW / HK

本篇文章將會以 Now in Android 專案為例,講解 Android 官方 App 模組化相關知識及策略。

姊妹篇文章見: 我從 Android 官方 App 中學到了什麼?

概述

模組化是將單一模組程式碼結構拆分為高內聚內耦合的多模組的一種編碼實踐。

模組化的好處

模組化有以下好處:

  • 可擴充套件性:在高耦合的單一程式碼庫中,牽一髮而動全身。模組化專案當採用關注點分離原則。這會賦予了貢獻者更多的自主權,同時也強制執行架構模式。
  • 支援並行工作:模組化有助於減少程式碼衝突,為大型團隊中的開發人員提供更高效的並行工作。
  • 所有權:一個模組可以有一個專門的 owner,負責維護程式碼和測試、修復錯誤和審查更改。
  • 封裝:獨立的程式碼更容易閱讀、理解、測試和維護。
  • 減少構建時間:利用 Gradle 的並行和增量構建可以減少構建時間。
  • 動態交付:模組化是 Play 功能交付 的一項要求,它允許有條件地交付應用程式的某些功能或按需下載。
  • 可重用性:模組化為程式碼共享和構建多個應用程式、跨不同平臺、從同一基礎提供了機會。

模組化的誤區

模組化也可能會被濫用,需要注意以下問題:

  • 太多模組:每個模組都有其成本,如 Gradle 配置的複雜性增加。這可能會導致 Gradle 同步及編譯時間的增加,併產生持續的維護成本。此外,與單模組相比,新增更多模組會增加專案 Gradle 設定的複雜性。這可以通過使用約定外掛來緩解,將可重用和可組合的構建配置提取到型別安全的 Kotlin 程式碼中。在 Now in Android 應用程式中,可以在 build-logic資料夾 中找到這些約定外掛。
  • 沒有足夠的模組:相反,如果你的模組很少、很大並且緊密耦合,最終會產生另外的大模組。這將失去模組化的一些好處。如果您的模組臃腫且沒有單一的、明確定義的職責,您應該考慮將其進一步拆分。
  • 太複雜了:模組化並沒有靈丹妙藥 -- 一種方案解決所有專案的模組化問題。事實上,模組化你的專案並不總是有意義的。這主要取決於程式碼庫的大小和相對複雜性。如果您的專案預計不會超過某個閾值,則可擴充套件性和構建時間收益將不適用。

模組化策略

需要注意的是沒有單一的模組化方案,可以確保其對所有專案都適用。但是,可以遵循一般準則,可以儘可能的享受其好處並規避其缺點。

這裡提到的模組,是指 Android 專案中的 module,通常會包含 Gradle 構建指令碼、原始碼、資源等,模組可以獨立構建和測試。如下:

一般來說,模組內的程式碼應該爭取做到低耦合、高內聚。

  • 低耦合:模組應儘可能相互獨立,以便對一個模組的更改對其他模組的影響為零或最小。他們不應該瞭解其他模組的內部工作原理。
  • 高內聚:一個模組應該包含一組充當系統的程式碼。它應該有明確的職責並保持在某些領域知識的範圍內。例如,Now in Android 專案中的core-network模組負責發出網路請求、處理來自遠端資料來源的響應以及向其他模組提供資料。

Now in Android 專案中的模組型別

注:模組依賴圖(如下)可以在模組化初期用於視覺化各個模組之間的依賴關係。

modularization-graph.png

Now in Android 專案中有以下幾種型別的模組:

  • app 模組: 包含繫結其餘程式碼庫的應用程式級和腳手架類,app例如和應用程式級受控導航。一個很好的例子是通過導航設定和底部導航欄設定。該模組依賴於所有模組和必需的模組。
  • feature- 模組: 功能特定的模組,其範圍可以處理應用程式中的單一職責。這些模組可以在需要時被任何應用程式重用,包括測試或其他風格的應用程式,同時仍然保持分離和隔離。如果一個類只有一個feature模組需要,它應該保留在該模組中。如果不是,則應將其提取到適當的core模組中。一個feature模組不應依賴於其他功能模組。他們只依賴於core他們需要的模組。
  • core-模組:包含輔助程式碼和特定依賴項的公共庫模組,需要在應用程式中的其他模組之間共享。這些模組可以依賴於其他核心模組,但它們不應依賴於功能模組或應用程式模組。
  • 其他模組 - 例如和模組syncbenchmark、 test以及 app-nia-catalog用於快速顯示我們的設計系統的目錄應用程式。

專案中的主要模組

基於以上模組化方案,Now in Android 應用程式包含以下模組:

| 模組名 | 職責 | 關鍵類及核心示例 | | ---------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | app | 將應用程式正常執行所需的所有內容整合在一起。這包括 UI 腳手架和導航。 | NiaApp, MainActivity 應用級控制導航通過 NiaNavHost, NiaTopLevelNavigation | | feature-1, feature-2 ... | 與特定功能或使用者相關的功能。通常包含從其他模組讀取資料的 UI 元件和 ViewModel。如:feature-author在 AuthorScreen 上顯示有關作者的資訊。feature-foryou它在“For You” tab 頁顯示使用者的新聞提要和首次執行期間的入職。 | AuthorScreen AuthorViewModel | | core-data | 儲存多個特性模組中的資料。 | TopicsRepository AuthorsRepository | | core-ui | 不同功能使用的 UI 元件、可組合項和資源,例如圖示。 | NiaIcons NewsResourceCardExpanded | | core-common | 模組之間共享的公共類。 | NiaDispatchers Result | | core-network | 發出網路請求並處理對應的結果。 | RetrofitNiANetworkApi | | core-testing | 測試依賴項、儲存庫和實用程式類。 | NiaTestRunner TestDispatcherRule | | core-datastore | 使用 DataStore 儲存持久資料。 | NiaPreferences UserPreferencesSerializer | | core-database | 使用 Room 的本地資料庫儲存。 | NiADatabase DatabaseMigrations Dao classes | | core-model | 整個應用程式中使用的模型類。 | Author Episode NewsResource | | core-navigation | 導航依賴項和共享導航類。 | NiaNavigationDestination |

Now in Android 的模組化

Now in Android 專案中的模組化方案是在綜合考慮專案的 Roadmap、即將開展的工作和新功能的情況下定義的。Now in Android 專案的目標是提供一個接近生產環境的大型 App 的模組化方案,並且要讓方案看起來並沒有過度模組化,希望是在兩者之間找到一種平衡。

這種方法與 Android 社群進行了討論,並根據他們的反饋進行了改進。這裡並沒有一個絕對的正確答案。歸根結底,模組化 App 有很多方法和方法,沒有唯一的靈丹妙藥。這就需要在模組化之前考慮清楚目標、要解決的問題已經對後續工作的影響,這些特定的情況會決定模組化的具體方案。可以繪製出模組依賴關係圖,以便幫助更好地分析和規劃。

這個專案就是一個示例,並不是一個需要固守不可改變固定結構,相反而是可以根據需求就行變化的。根據 Now in Android 這是我們發現最適合我們專案的一般準則,並提供了一個示例,可以在此基礎上進一步修改、擴充套件和構建。如果您的資料層很小,則可以將其儲存在單個模組中。但是一旦儲存庫和資料來源的數量開始增長,可能值得考慮將它們拆分為單獨的模組。

最後,官方對其他方式的模組化方案也是持開發態度,有更好的方案及建議也可以反饋出來。

總結

以上內容是根據 Modularization learning journey 翻譯整理而得。整體上是提供了一個示例,對一些初學者有一個可以參考學習的工程,對社群中模組化開發起到的積極的作用。說實話,這部分技術在國內並不是什麼新技術了。

下面講一個我個人對這個模組化方案的理解,以下是個人觀點,請批判性看待。

首先是好的點提供了通用的 Gradle 配置,簡化了各個模組的配置步驟,各種方式預計會在之後的一些專案中流行開來。

不足的點就是沒有明確模組化的整體策略,是應採取按照功能還是按照特性分,類似討論還有我們平時的類檔案是按照功能來分還是特性來分,如下是按照特性區分:

```

DO,建議方式

  • Project
  • feature1
  • ui
  • domain
  • data
  • feature2
  • ui
  • domain
  • data
  • feature3

```

按照功能區分的方式大致如下:

```

DO NOT,不建議方式

  • Project
  • ui
  • feature1
  • feature2
  • feature3
  • domain
  • feature1
  • feature2
  • feature3
  • data

```

我個人是傾向去按照特性的方式區分,而示例中看上去是偏後者,或者是一個混合體,比如有的模組是新增 feature 字首的,但是 core-model 模組又是在統一的一個模組中集中管理。個人建議的方式應該是將各個模組中各自使用的模型放到自己的模組中,否則專案在後續進行元件化時將會遇到頻繁發版的問題。當然,這種方式在模組化的階段並沒有什麼大問題。

模組化之後就是元件化,元件化之後就是殼工程,每個技術階段對應到團隊發展的階段,有機會的話後面可以展開聊聊。