1. 程式人生 > >Android:關於專案元件化/模組化的設計

Android:關於專案元件化/模組化的設計

隨著技術越來越成熟,這兩年,元件化開發與外掛化開發的熱度一度高漲。對於元件化,有的人也喜歡稱之為模組化開發,我也比較喜歡稱之為模組化開發。使用模組化開發也已經有一段時間了,特此總結一下模組化開發的心得,防止以後忘記。

什麼是模組化開發

對於模組化開發的概念,有的人可能還不是很瞭解,通俗的來講就是:

  • 將專案中的具體功能模組,如登入、個人中心等,拆分成一個一個單獨的module
  • 將其中公用的功能抽離出來,形成一個個單獨的公用module,方便複用
  • 然後像引用一些library專案一樣,將這些功能module引入到一個統一的module中,組成一個app
  • 但是與這些library有區別的地方在於,這些被拆分成的module也可以在需要的時候單獨作為一個app執行

為什麼要使用模組化開發

  • 隨著專案越來越龐大,專案的體積也會越來越臃腫,每次更改一下程式碼,可能就要編譯很長時間,效率得不到保證
  • 一個專案可能有多個人員開發,大家在同一個專案中開發和修改程式碼,可能就會對其他人員的程式碼帶來影響和衝突

針對以上兩個問題,模組化開發,可以讓每個人手中的專案大小得到有效控制,每個人可以更加專注於自己的任務,而不用花太多精力去關心和check別人的程式碼

模組化開發的兩種模式

模組化開發目前有兩種模式 (圖是網上截的,太懶,不想畫圖 @

[email protected]

  • submodule: 即整個專案只有一個project,在project中構建多個功能模組module,每個人單獨負責一個module
    每個module都有自己的git倉庫,非常直觀。但是不好的地方就是,整個projectgit分支會很複雜,且團隊協作的時候,大家都是在projectapp模組中做測試,可能就會發生衝突。且因為所有的module在一個project中,每個人都可以修改他人f負責的module,不是很安全
    subnodule

  • multi-project: 即將每個功能模組都拆分成一個單獨的project,一個project

    中一般包含自己測試使用的app模組和功能模組。這樣,每個project都擁有自己單獨的git倉庫,在團隊協作時,每個人都有自己的app模組來做測試,不會影響到他人,也修改不了他人的module比較安全,但是專案初期時,對於程式碼規範,命名規範都必須規定好,否則最後合併成app時可能會有衝突。
    multi-project

以上兩種模式都是比較常用的模式,各有優點,submodule更直觀,開發更快速,multi-project更解耦,更安全。大家可以根據實際情況選擇。(因為個人比較習慣於multi-project,所以下面的有些講解可能偏向於multi-project

模組化開發的設計

模組化開發大致分為以下幾層:(網上一搜一堆,沒什麼大區別,主要是對每個層級的細分)

  • 殼工程
  • 業務元件層
  • 公共元件層
    在這裡插入圖片描述
    (圖是網上截的,太懶,不想畫圖 @[email protected],將就看吧,和講的大致也差不多)

殼工程

顧名思義,殼工程只是一個空的app module,本身不含任何功能頁面,不做任何業務邏輯處理,可隨時替換。主要作用就是將其他業務元件的依賴新增到自己身上,形成完整的app

業務元件層

  • 業務元件層由多個業務元件組成,每個業務元件代表專案的一個具體功能模組,例如登入、個人中心等。
  • 業務元件中的功能需要在多個地方使用時,可以將其在細化拆分為基礎業務元件商務業務元件
    例如大眾點評中,有兩個這樣的模組酒店高階酒店,這兩個模組中都用到了對於酒店搜尋功能,但是兩個模組又不完全相同,這時可以將酒店搜尋做成一個單獨的基礎業務元件,在兩個模組中都呼叫酒店搜尋元件,這兩個模組便屬於商務業務元件

公共元件層

對於 公共元件層,以前有同事喜歡直接做成一個公用的library,比如做成一個名為lib_commonmodule,所有的 公用util、網路訪問工具、第三方庫如分享、支付等 都放在裡面,然後被 業務元件 呼叫。在專案小的時候,這也無關緊要,但是如果專案越來越大,lib_common也可能會更著變得越來越臃腫,這就又回到了起點,最後不得不花費精力重新將lib_common拆分。所以個人覺得不能全都將 公用部分 集中在lib_common中,仍需劃分。

  • 基礎元件層: 存放穩定的複用程式碼,不需頻繁更改,與專案幾乎沒有什麼實際關聯,作為底層存在,例如 通用的 BaseActivity、BaseDialog、通用網路訪問封裝框架、工具類等,具體是放在一個元件中還是拆分為多個元件,根據實際狀況而定
  • 第三方庫元件層: 專案中難免會用到一些第三方庫,如分享、第三方登入、支付等,這些第三方庫可以獨立成一個元件或者根據複雜程度分成多個元件方便複用
  • 通用元件(Common): 針對專案實際情況,抽離出公用的程式碼例如工具類形成的元件。看起來可能與 基礎元件 的作用相似,但實際上 Common元件基礎元件 是有區別的,基礎元件 是穩定的,在個人看來,基礎元件 可以不僅應用於一個專案,可以在多個專案中使用而不需要做變動(隨著不斷完善,以後很可能就成為了自己公司的專案框架^_^。而 Common元件 是更加針對專案的實際情況的,大都會依賴於 基礎元件,並對裡面一些功能做二次封裝以適用於專案實際情況,不適合在其他專案中不做修改就使用,且會根據專案需求的改變,Common元件 也有可能做頻繁修改

業務資料互動時的處理

場景

開發中遇到業務資料互動是有大概率遇到的事,比如說專案中有兩個模組一個是景點,一個是美食,當你到達一個 景點 後,會給你 推薦美食,而 推薦美食 的資料肯定是從美食模組而來。

在上述場景中,一般常見的實現就是,將獲取推薦美食資料介面下沉到一個 公用元件 中,在 公用元件 中實現介面,然後美食景點模組都呼叫 公用元件 中的這個介面,獲取資料。看似沒什麼大問題,其實還存在一些隱患。假如,專案中這種互動的行為存在更多怎麼辦?都將這些資料介面下沉到 公用元件 ,在這裡面實現嗎?這樣但增加了 公用元件 的體積,而且是危險的。因為對於 公用元件 上文已經說過,很有可能會頻繁變動,而變動可能來自不同的業務元件,那麼可能會有多個不瞭解美食模組的開發人員參與到 公用元件 的修改,這意味著修改人員是有許可權修改這些互動資料的獲取的,這是危險的。或者還有另一種常見的實現,就是資料獲取,仍然放在美食模組,景點模組直接呼叫美食模組中的程式碼,但是這增加了兩者之間的耦合。

解決

在我看來,業務元件之間是相互獨立,沒有依賴的,每個成員專注於自己的業務,無需關注其他成員的工作,且同時對自己的業務擁有絕對的掌控權,而將業務中的核心即資料處理放在公用層,是個危險的行為。那麼該如何解決呢?下面以ARouter路由框架為例

  1. 利用路由框架在一個 公用元件 中註冊服務(甚至你也可以單獨建立一個 路由元件 做這件事),這裡的服務就是ARouterIProviderIProvider是個interface,本身不做具體實現,非常easy和安全
// 宣告介面,其他元件通過介面來呼叫服務
public interface FoodService extends IProvider {
    List<FoodBean> getFoodData(int locationCode);
}
  1. 依賴公用元件,在美食模組中實現FoodService
// 實現介面
@Route(path = "/service/food", name = "美食服務")
public class FoodServiceImpl implements FoodService {

    @Override
    public List<FoodBean> getFoodData(int locationCode) {
        List<FoodBean> list=new ArrayList();
        // 具體實現
        ...
        return list;
    }

    @Override
    public void init(Context context) {

    }
}
  1. 依賴公用元件, 在景點模組中獲取美食資料
// 申明服務
 @Autowired(name = "/service/food")
 FoodService service;
 
 // 呼叫服務
 List<FoodBean> foodList = service.getFoodData(101);

關於模組化時,元件模式的切換

在開發時,開發人員有時需要元件作為一個單獨的app執行來除錯,有時需要將元件整合到殼app中除錯,所以需要經常在app 和 library間切換。下面是為了應對這種情況,常見的處理方法

  1. projectgradle.properties中定義一個標記
# true: 作為一個 app 單獨執行
# false: 作為一個元件
isApp=false
  1. modulemain目錄下新建資料夾manifest,再在manifest資料夾中新建appmodule兩個資料夾,存放兩種模式下的AndroidManifest.xml
    manifest圖片
  2. 對於modulebuild.gradle的配置
// 因為在 gradle.properties 定義了,所以這邊可以直接獲取
def isApp = isApp.toBoolean()

if (isApp) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

...省略

// 作為一個元件時,是沒有 applicationId 的
if(isApp){
    applicationId "com.xxxxx.xxxxxx"
}

...省略

sourceSets {
    // 作為 app 和 元件時的 AndroidManifest.xml 是不同的
    main {
       if (isApp) {
            manifest.srcFile 'src/main/manifest/app/AndroidManifest.xml'
       } else {
           manifest.srcFile 'src/main/manifest/module/AndroidManifest.xml'
       }
    }
}

小提議

  • 無論是使用submodule還是multi-project,引入元件時,儘量將元件打包成aar,再引入,這樣可以編譯更快
  • 開發前,團隊先規定好程式碼規範與資源命名規範,因為在最終元件合併成app時,如果元件中有資源名一致的資源,會產生衝突

感謝以下文章

Android工程模組化平臺的設計(整理優化版)
Android元件化(業務拆分)
美團貓眼android模組化實戰-可能是最詳細的模組化實戰