Android模組化開發實踐

vivo網際網路技術發表於2021-08-24

一、前言

隨著業務的快速發展,現在的網際網路App越來越大,為了提高團隊開發效率,模組化開發已經成為主流的開發模式。正好最近完成了vivo官網App業務模組化改造的工作,所以本文就對模組化開發模式進行一次全面的介紹,並總結模組化改造經驗,幫助兄弟專案避坑。

二、什麼是模組化開發

首先我們搞清兩個概念,Android客戶端開發目前有兩種模式:單工程開發模式模組化開發模式

  • 單工程開發模式:早期業務少、開發人員也少,一個App對應一個程式碼工程,所有的程式碼都集中在這一個工程的一個module裡。

  • 模組化開發模式:簡單來說,就是將一個App根據業務功能劃分成多個獨立的程式碼模組,整個App是由這些獨立模組整合而成。

在講什麼是模組化開發前,我們先定義清楚兩個概念:元件和模組。

  • 元件:指的是單一的功能元件,比如登入元件、分享元件;

  • 模組:廣義上來說是指功能相對獨立、邊界比較清晰的業務、功能等,本文如果單獨出現模組這個詞一般是該含義。狹義上是指一個業務模組,對應產品業務,比如商城模組、社群模組。

模組和元件的本質思想是一樣的,都是為了業務解耦和程式碼重用,元件相對模組粒度更細。在劃分的時候,模組是業務導向,劃分一個個獨立的業務模組,元件是功能導向,劃分一個個獨立的功能元件。

模組化開發模式又分為兩種具體的開發模式:單工程多module模式多工程模式

單工程多module模式

所有程式碼位於一個工程中,模組以AndroidStudio的module形式存在,由一個App module和多個模組module組成。如圖:

多工程模式

每個模組程式碼位於一個工程中,整個專案由一個主模組工程和多個子模組工程組成。其中主模組工程只有一個App module,用於整合子模組,進行整體除錯、編包。子模組工程由一個App module和一個Library module組成,App module中是除錯、測試程式碼,Library module中是業務、功能程式碼。如下圖:

下面我們來對比一下單工程多module模式和多工程模式的優缺點:

通過上面的對比,我們可以看出來,多工程模式在程式碼管理、開發除錯、業務並行等方面有明顯優勢,非常適合像vivo官網這種業務線多、工程大、開發人員多的App,所以vivo官網目前就採用的此模式。本文在講解模組化開發時,一般也是指多工程模式。

單工程多module模式,更適合開發人員少、業務並行程度低的專案。但是多工程模式也有兩個缺點:程式碼倉較多、開發時需要開啟多個工程,針對這兩個缺點,我們也有解決方案。

程式碼倉較多的問題

要求我們在拆分模組時粒度不能太細,當一個模組膨脹到一定程度時再進行拆分,在模組化帶來的效率提升與程式碼倉管理成本增加間保持平衡。

要開啟多個工程開發的問題

我們基於Gradle外掛開發了程式碼管理工具,可以方便的切換通過程式碼依賴子模組或者maven依賴子模組,實際開發體驗跟單工程多module模式一樣,如下圖;

模組化開發的流程也很簡單:

  • 版本前期,每個模組由特定的開發人員負責,各子模組分別獨立開發、除錯;

  • 子模組開發完成後,整合到主模組工程進行整體除錯;

  • 整合除錯成功後,進入測試。

三、模組化開發

3.1 我們為什麼要做模組化開發呢?

這裡我們說說單一工程開發模式的一些痛點。

團隊協作效率低

  • 專案早期業務少、開發人員也少,隨著業務發展、團隊擴張,由於程式碼都在同一個工程中,雖然各個人開發的功能不同,但是經常會修改同一處的程式碼,這時就需要相關開發人員溝通協調以滿足各自需求,增加溝通成本;

  • 提交程式碼時,程式碼衝突也要溝通如何合併(否則可能引起問題),增加合程式碼成本;

  • 無法進行並行版本開發,或者勉強進行並行開發,代價是各個程式碼分支差異大,合併程式碼困難。

程式碼維護成本高

  • 單一工程模式由於程式碼都在一起,程式碼耦合嚴重,業務與業務之間、業務與公共元件都存在很多耦合程式碼,可以說是你中有我、我中有你,任何修改都可能牽一髮而動全身,隨著版本的迭代,維護成本會越來越高。

開發除錯效率低

  • 任何一次的修改,即使是改一個字元,都需要編譯整個工程程式碼,隨著程式碼越來越多,編譯也越來越慢,非常影響開發效率。

3.2 如何解決問題

說完單一工程開發模式的痛點,下面我們看看模組化開發模式怎麼來解決這些問題的。

提高團隊協作效率

  • 模組化開發模式下,根據業務、功能將程式碼拆分成獨立模組,程式碼位於不同的程式碼倉,版本並行開發時,各個業務線只在各自的模組程式碼倉中進行開發,互不干擾,對自己修改的程式碼負責;

  • 測試人員只需要重點測試修改過的功能模組,無需全部迴歸測試;

  • 要求產品層面要有明確的業務劃分,並行開發的版本必須是不同業務模組。

降低程式碼維護成本

  • 模組化開發對業務模組會劃分比較明確的邊界,模組間程式碼是相互獨立的,對一個業務模組的修改不會影響其他模組;

  • 當然,這對開發人員也提出了要求,模組程式碼需要做到高內聚。

提高編譯速度

  • 開發階段,只需要在自己的一個程式碼倉中開發、除錯,無需整合完整App,編譯程式碼量極少;

  • 整合除錯階段,開發的程式碼倉以程式碼方式依賴,其他不涉及修改的程式碼倉以aar方式依賴,整體的編譯程式碼量也比較少。

當然模組化開發也不是說全都是好處,也存在一些缺點,比如:

1)業務單一、開發人員少的App不要模組化開發,那樣反而會帶來更多的維護成本;

2)模組化開發會帶來更多的重複程式碼;

3)拆分的模組越多,需要維護的程式碼倉越多,維護成本也會升高,需要在拆分粒度上把握平衡。

總結一下,模組化開發就像我們管理書籍一樣,一開始只有幾本書時,堆書桌上就可以了。隨著書越來越多,有幾十上百本時,我們需要一個書櫥,按照類別放在不同的格子裡。對比App迭代過程,起步時,業務少,單一工程模式效率最高,隨著業務發展,我們要根據業務拆分不同的模組。

所有這些目的都是為了方便管理、高效查詢。

四、模組化架構設計

模組化架構設計的思路,我們總結為縱向和橫向兩個維度。縱向上根據與業務的緊密程度進行分層,橫向上根據業務或者功能的邊界拆分模組。

下圖是目前我們App的整體架構。

4.1 縱向分層

先看縱向分層,根據業務耦合度從上到下依次是業務層、元件層、基礎框架層。

  • 業務層:位於架構最上層,根據業務模組劃分(比如商城、社群等),與產品業務相對應;

  • 元件層:App的一些基礎功能(比如登入、自升級)和業務公用的元件(比如分享、地址管理),提供一定的複用能力;

  • 基礎框架層:完全與業務無關、通用的基礎元件(比如網路請求、圖片載入),提供完全的複用能力。

框架層級從上往下,業務相關性越來越低,程式碼穩定性越來越高,程式碼入倉要求越來越嚴格(可以考慮程式碼許可權收緊,越底層的程式碼,入倉要求越高)。

4.2 橫向分模組

  • 在每一層上根據一定的粒度和邊界,拆分獨立模組。比如業務層,根據產品業務進行拆分。元件層則根據功能進行拆分。

  • 大模組可以獨立一個程式碼倉(比如商城、社群),小模組則多個模組組成一個程式碼倉(比如上圖中虛線中的就是多個模組位於一個倉)。

  • 模組要高內聚低耦合,儘量減少與其他模組的依賴。

物件導向設計原則強調組合優於繼承,平行模組對應組合關係,上下層模組對應繼承關係,組合的優點是封裝性好,達到高內聚效果。所以在考慮框架的層級問題上,我們更偏向前者,也就是拆分的模組儘量平行,減少層級。

層級多的問題在於,下層程式碼倉的修改會影響更多的上層程式碼倉,並且層級越多,並行開發、並行編譯的程度越低。

模組依賴規則:

  • 只有上層程式碼倉才能依賴下層程式碼倉,不能反向依賴,否則可能會出現迴圈依賴的問題;

  • 同一層的程式碼倉不能相互依賴,保證模組間徹底解耦。

五、模組化開發需要解決哪些問題

5.1 業務模組如何獨立開發、除錯?

方式一:每個工程有一個App module和一個Library module,利用App module中的程式碼除錯Library module中的業務功能程式碼。

方式二:利用程式碼管理工具整合到主工程中除錯,開發中的程式碼倉以程式碼方式依賴,其他模組以aar方式依賴。

5.2 平行模組間如何實現頁面跳轉,包括Activity跳轉、Fragment獲取?

根據模組依賴原則,平行模組間禁止相互依賴。隱式Intent雖然能解決該問題,但是需要通過Manifest集中管理,協作開發比較麻煩,所以我們選擇了路由框架Arouter,Activity跳轉和Fragment獲取都能完美支援。另外Arouter的攔截器功能也很強大,比如處理跳轉過程中的登入功能。

5.3 平行模組間如何相互呼叫方法?

Arouter服務參考——https://github.com/alibaba/ARouter。

5.4 平行模組間如何傳遞資料、驅動事件?

Arouter服務、EventBus都可以做到,視具體情況定。

六、老專案如何實施模組化改造

老專案實施模組化改造非常需要耐心和細心,是一個循序漸進的過程。

先看一下我們專案的模組化進化史,從單一工程逐步進化成紡錘形的多工程模組化模式。下圖是進化的四個階段,從最初的單個App工程到現在的4層多倉結構。

注:此圖中每個方塊表示一個程式碼倉,上層程式碼倉依賴下層程式碼倉。

早期專案都是採用單一工程模式的,隨著業務的發展、人員的擴張,必然會面臨將老專案進行模組化改造的過程。但是在模組化改造過程中,我們會面臨很多問題,比如:

  • 程式碼邏輯複雜,缺乏文件、註釋,不敢輕易修改,害怕引起功能異常;

  • 程式碼耦合嚴重,你中有我我中有你,牽一髮動全身,拆分重構難度大;

  • 業務版本迭代與模組化改造並行,程式碼衝突頻繁,影響專案進度;

相信做模組化的人都會遇到這些問題,但是模組化改造勢在必行,我們不可能暫停業務迭代,把人力都投入到模組化中來,一來業務方不可能同意,二來投入太多人反而會帶來更多程式碼衝突。

所以需要一個可行的改造思路,我們總結為先自頂向下劃分,再自底向上拆分

自頂向下

  • 從整體到細節逐層劃分模組,先劃分業務線,業務線再劃分業務模組,業務模組中再劃分功能元件,最終形成一個樹狀圖。

自底向上

  • 當我們把模組劃分明確、依賴關係梳理清楚後,我們就需要自底向上,從葉子模組開始進行拆分,當我們把葉子模組都拆分完成後,枝幹模組就可以輕鬆拆分,最後完成主幹部分的拆分。

  • 另外整個模組化工作需要由專人統籌,整體規劃,完成主要的改造工作,但是有複雜的功能也可以提需求給各模組負責人,協助完成改造。

下面就講講我們在模組化改造路上打怪升級的一些經驗。總的來說就是循序漸進,各個擊破

6.1 業務模組梳理

這一步是自頂向下劃分模組,也就是確定子模組程式碼倉。一個老專案必然經過多年迭代,經過很多人開發,你不一定要對所有的程式碼都很熟悉,但是你必須要基本瞭解所有的業務功能,在此基礎上綜合產品和技術規劃進行初步的模組劃分。

此時的模組劃分可以粒度粗一點,比如根據業務線或者大的業務模組進行劃分,但是邊界要清晰。一個App一般會有多個業務線,每個業務線下又會有多個業務模組,這時,我們梳理業務不需要太細,保持2層即可,否則過度的拆分會大大增加實施的難度。

6.2 抽取公共元件

劃分完模組,但是如果直接按此來拆分業務模組,會有很大難度,並且會有很多重複程式碼,因為很多公共元件是每個業務模組都要依賴的(比如網路請求、圖片載入、分享、登入)。所以模組化拆分的第一步就是要抽取、下沉這些公共元件。

在這一步,我們在抽取公共元件時會遇到兩類公共元件,一類是完全業務無關的基礎框架元件(比如網路請求、圖片載入),一類是業務相關的公共業務元件(比如分享、登入)。

可以將這兩類公共元件分成兩層,便於後續的整體框架形成。比如我們的lib倉放的是基礎框架元件和core倉放的是業務公共元件。如下圖

6.3 業務模組拆分

抽取完公共元件後,我們要準備進行業務模組的拆分,這一步耗時最長,但也是效果最明顯的,因為拆完我們就可以多業務並行開發了。

確定要拆分的業務模組(比如下圖的商城業務),先把程式碼倉拉出來,新功能直接在新倉開發。

那老功能該怎麼拆分遷移呢?我們不可能一口吃成大胖子,想一次把一個大業務模組全部拆分出來,難度太大。這時我們就要對業務模組內部做進一步的梳理,找出所有的子功能模組(比如商城業務中的支付、選購、商詳等)。

按照功能模組的獨立程度,從易到難逐個拆分,比如支付的訂單功能比較獨立,那就先把訂單功能的程式碼拆分到新倉。

6.4 功能模組拆分

在拆分具體功能時,我們依然使用Top-Down的邏輯來實施,首先找到入口類(比如Activity),遷移到新的程式碼倉中,此時你會發現一眼望去全是報紅,就像拔草一樣帶出大量根鬚。依賴的佈局、資源、輔助類等等都找不到,我們按照從易到難的順序一個個解決,需要解決的依賴問題有以下幾類:

1)簡單的依賴,比如字串、圖片。

這類是最容易解決,直接把資源遷移過來即可。

2)較複雜的依賴,比如佈局檔案、drawable。

這類相對來說也比較容易解決,逐級遷移即可。比如佈局依賴各種drawable、字串、圖片,drawable又依賴其他的drawable等,自頂向下逐個遷移就能解決。

3)更復雜的依賴,類似A->B->C->D。

對於這類依賴有兩種解決方式,如果依賴的功能沒有業務特性或只是簡單封裝系統 API,那可以考慮直接copy一份;如果依賴的程式碼是多個功能模組公用的或者多個功能模組需要保持一致,可以考慮將該功能程式碼抽取下沉到下一層程式碼倉。

4)一時難以解決的依賴。

可以先暫時註釋掉,保證可以正常執行,後續理清邏輯再決定是進行解耦還是重構。斬斷依賴鏈非常重要,否則可能堅持不下去。

6.5 程式碼解耦

下面介紹一下常用的程式碼解耦方法:

公共程式碼抽取下沉

比如:基礎元件(eg.網路請求框架)、各模組需要保持功能一致的程式碼(eg.適配OS的動效);

簡單程式碼複製一份

比如簡單封裝系統api(eg.獲取packageName)、功能模組自用的自定義view(eg.提示彈窗);

三個工具

Arouter路由、Arouter服務、EventBus,能滿足各種解耦場景。

6.6 新老程式碼共存

老專案模組化是一個長期的過程,新老程式碼共存也是一個長期的過程。經過上面改造後,一個功能模組就可以獨立出來了,因為我們都是從老的App工程裡拆分出來的,所以App工程依賴新倉後就可以正常執行。當我們持續從老工程中拆分出獨立模組,最後老工程只需要保留一些入口功能,作為整合子模組的主工程。

七、總結

本文從模組化的概念模組化架構設計以及老專案如何實施模組化改造等幾個方面介紹移動應用客戶端模組化實踐。當然模組化工作遠不止這些,還包括模組aar管理、持續整合、測試、模組化程式碼管理、版本迭代流程等,本文就不一一贅述,希望這篇文章能給準備做模組化開發的專案提供幫助。

​作者:vivo網際網路客戶端團隊-Wang Zhenyu

相關文章