Android工程模塊化平臺設計-講稿

這篇文章是我在 2018【攜程技術沙龍移動技術工程化】技術分享時所講內容的文字版本,修改刪減了演講時的冗餘言語。 發佈在【掘金專欄】,但願能給買不到票參加大會的朋友帶來幫助。java

https://kymjs.com/code/2018/04/22/01/

你們好,今天跟你們分享的主題是《Android工程模塊化平臺的設計》git

https://kymjs.com/code/2018/04/22/01/

首先自我介紹一下:我叫張濤,目前就任於餓了麼移動技術部。可能有些朋友認識我,我以前也會在我博客【開源實驗室】寫一些Android相關的技術點,若是對今天講的模塊化設計,你以爲有什麼問題或者能夠深刻探討的,也歡迎加我微信kymjs123詳聊。編程

https://kymjs.com/code/2018/04/22/01/

今天咱們講的主題是基於項目模塊化來講的,模塊化是什麼你們確定都是知道了的,這裏問一下你們,有多少人在此以前有作過模塊化的,舉個手我看一下;瞭解過據說過模塊化的呢?此次比較多。
咱們說,作模塊化其實跟項目重構很像,都是從這幾個點來作的,只是側重點不一樣。分別是:刪除、組織、降級、解耦。那麼這四點是什麼意思呢,那麼接下來跟你們分享一下我是如何理解這四大塊的:後端

https://kymjs.com/code/2018/04/22/01/

刪除:刪除沒必要要的文件,儘量減少工程體積。這裏有一組數據,是我統計咱們餓了麼的一款 APP 在模塊化先後一些文件的數量。
能夠看到,.java文件從1677個減小到了1543個。其實這不是重點,重點是下面的drawable,這裏drawable只包含圖片、和xml佈局,當通過模塊化重構後文件數從 693 減小到 538 個。圖片資源減小接近 200 個,apk 的大小也會隨之下降。微信

https://kymjs.com/code/2018/04/22/01/

而組織呢,指的是:按照有意義的標準將代碼分組。這其實也是java的包所存在的目的之一。
可是隨着項目的不斷迭代,需求很緊的狀況下是很難有時間去真正規範的將類分組的。看到圖中,咱們以前的結構很亂,就是由於項目快速迭代和人員更替的過程當中,難免會有這樣的現象。因此這也是模塊化重構時所做的一件大事。架構

https://kymjs.com/code/2018/04/22/01/

接下來就是咱們常常說的內聚和耦合了,降級。咱們以前有一個類叫:Navigator,它是負責幾乎全部Activity直接跳轉的。就是咱們會把全部的startActivity()的跳轉放到這個類裏面去寫。以前少的時候還好,結果等我看到這個類的時候,這個類已經有 200 多個方法了,全是Activity跳轉的方法,其中還有重複的,就是很早以前有人寫了一個跳到某個界面,結果以後來了我的,他不知道又寫一個。app

而咱們在作模塊化重構時的作法就是,首先觀察本身的項目,這是重構很重要的一步,就是要結合自身。把這個類拆分紅了三大部分,咱們有兩塊業務是會頻繁跳轉的但這兩個業務跳轉的頁面又都是在自身的模塊內,分別是用戶模塊和商戶模塊。所以咱們將這兩個模塊中分別創建兩個用於模塊本身內部的跳轉叫UserNavigatorShopNavigator,而模塊間的跳轉或一些小模塊內部的則使用Router去作,咱們本身定義了一個路由庫,其實實現跟如今開源的區別不大。模塊化

https://kymjs.com/code/2018/04/22/01/

最後解耦,也是今天的重點,如何優雅移除模塊間的耦合。 到目前爲止,咱們已經可以作到讓全部不包含業務狀態接口的模塊的增刪,不須要改動任何一行代碼。 具體到一個示例就是這樣:工具

https://kymjs.com/code/2018/04/22/01/

或者,也能夠是這樣:組件化

https://kymjs.com/code/2018/04/22/01/

這兩個段代碼的區別就是一個是手動管理Debug的狀態,另外一個是交給Gradle的編譯任務去控制,原理上是同樣的。
而這麼作是如何實現的呢,其本質就是:一個模塊就是一個功能,你想要讓你的 apk 具有這個功能,就添加這個模塊一塊兒編譯就能夠了。這纔是咱們說的真正的組件化,模塊之間零耦合,增減模塊零改動。
例如圖中:debug這個模塊,確定不會用在正式的生產環境;而相反的tinker這個模塊,熱補丁確定也不會用於調試階段。因此我在開發時就能夠不使用這個模塊相關的代碼。
另外再舉個使用的例子:我有一個訂單模塊,訂單模塊須要播放鈴聲,好比你們在飯店常常聽到「您有新的餓了麼訂單,請及時處理」。但我在開發訂單模塊的時候,若是我已經肯定鈴聲播放是沒有問題的,那我能夠選擇開發階段不打鈴聲的包,直到發佈到線上了再去加上鈴聲的包。那我沒有添加這個鈴聲模塊的時候,我就默認不具有播放鈴聲的功能,但徹底不影響其餘的訂單模塊的業務功能,而這個鈴聲模塊的增刪,是不須要修改任何代碼的。
聽到這裏相信你們都很好奇這是怎麼實現的。接下來就跟你們講講內部的原理。

https://kymjs.com/code/2018/04/22/01/

全部的核心功能都來自咱們本身寫的一個庫:IronBank。取《自冰與火之歌》中的【鐵金庫】,叫鐵金庫不容拖欠。
鐵金庫的內部實現,實際上是使用了 APT 註解處理器,去在編譯時解析註解生成一個類,讓這個類去生成跨模塊的對象。鐵金庫使用了與後端 SOA 設計思路相似的方式:將模塊之間的主動依賴倒置,變爲功能的提供與使用。
那什麼是 SOA 的設計思路呢,咱們看到一張我畫的漫畫圖:SOA 它是一種面向服務的架構模型。

https://kymjs.com/code/2018/04/22/01/

例如圖上左邊有一個對外提供媒體功能的服務提供者,他告知IronBank我提供媒體服務:「嘿,老鐵,我這有個媒體服務,你那邊有誰要用的時候能夠用個人。」
到了另外一邊,若是此刻有模塊說是,我須要媒體服務:「老鐵,你那有沒有媒體服務,我這邊須要播一個鈴聲啊!」。
「有的,給你。」
IronBank就會將以前服務提供者提供給他的媒體對象交給服務使用者。

https://kymjs.com/code/2018/04/22/01/

接下來咱們來看具體到代碼上是如何使用的:首先是做爲服務使用方,也就是上一張圖右半部分。咱們看到傳統的作法是首先聲明一個接口類型,而後new出接口的實現類給他賦值。
而使用了IronBank的時候,你是不須要關心接口的實現類究竟是誰的。這就是IronBank惟一的用處,隱藏實現類,作到完全的面相接口編程。

https://kymjs.com/code/2018/04/22/01/

以前說過,IronBank將模塊之間依賴倒置,由以前的服務提供方被動的接受調用方調用變爲,服務方主動提供服務給調用方。
那做爲服務提供方須要作些什麼事呢,很是簡單,你只須要給你的對象提供public static方法,並加上一個@Creator註解,告訴IronBank這是一個建立器方法就能夠了,其餘任何事情,都不須要考慮。

https://kymjs.com/code/2018/04/22/01/

前面講的IronBank適用的場景是無狀態的服務,而咱們作業務APP開發的時候更多的是有業務狀態的對象,比方說咱們一般長鏈與推送功能是等到用戶登陸了之後纔會去啓動,但具體到代碼上,推送模塊是根本不知道用戶何時登陸的,這就是一個業務狀態的問題。 而對此咱們引入了一個BizLifecycle的接口,他其實與Android上的Application對象功能相似。只不過他用來管理的是業務的生命週期,而不是應用的。
那麼在代碼邏輯上,每一個模塊若是關心你所須要的業務生命週期,只須要註冊一個Lifecycle就好了,同時註冊的過程也只須要一個註解,由編譯插件解決了。

https://kymjs.com/code/2018/04/22/01/

能夠看到,其實這樣的一種能力用事件通知也能夠作到,比方說廣播或者EventBus,可是咱們刻意屏蔽了這種方式,就是由於事件通知這種功能你是很難去追蹤的,你不知道一個消息發送了之後,他的接受者是在哪裏。相信你們也能狗想象獲得,一個應用若是廣播氾濫,處處都是事件接收事件發送會項目代碼會變得多麼嚇人。

https://kymjs.com/code/2018/04/22/01/

講到這裏,整個模塊化解耦的所有能力就跟你們介紹完了。接下來,咱們再從宏觀角度去看一下整個項目的結構,分爲三級,最上層是業務模塊,緊接着是一些可選的功能組件,最底層則是與項目無關的公共依賴。

https://kymjs.com/code/2018/04/22/01/

最終,項目結構就是如圖中所示的這樣。但若是你真直接這麼作,你必定是會煩死的。
爲何?
第一:這麼多的模塊,直接用源碼依賴去編譯,編譯時間至少在10分鐘以上;
第二:模塊的隔離幾乎爲0,任何一我的依舊能夠修改任何一個模塊的代碼,而且很容易;
第三:在發版本之後,若是某一個模塊有BUG,再去修復,缺少一個版本的概念,尤爲是在跨團隊的時候,最終必定會出現版本分裂問題。

https://kymjs.com/code/2018/04/22/01/

解決辦法我想你們都知道,就是將模塊引用改成aar引用。aar引用最大的優點就在於模塊版本的管理與跨團隊的協做。
目前國內對Android領域的探索愈來愈深,應用規模也愈來愈大,爲了下降大型項目的複雜性和耦合度,同時也爲了適應模塊重用、多團隊並行開發測試等等需求,你必須有一套合適的模塊化平臺。

https://kymjs.com/code/2018/04/22/01/

這裏是咱們餓了麼目前使用的模塊化平臺,你們能夠從這張圖中感覺一下。
模塊化平臺,主要的功能是很明顯的,就是用於構建模塊,在這之上,還有隱含的功能,就是集中了構建模塊的權限,能夠更便於統一管理;
固然還有最重要的優點就在於模塊版本的管理,你能夠很清晰的知道當前主應用所接入的模塊的版本是哪一個,當前最新構建的SNAPSHOT是哪一個,以及每一個版本的更新日誌;
這樣作了之後,在跨團隊協做上的溝通就大大下降了,若是你已經接入或者即將接入的模塊是另外一個團隊開發的模塊組件,那你能夠直接關注它,它的全部版本變更日誌,最新版本全都一目瞭然;
而且能夠經過平臺簡化模塊的測試與模塊發佈的流程,好比提測的時候,若是是一次兼容版本的發佈,你只須要告訴測試提測分支,測試能夠本身根據如今線上應用的tag,同時引入當前提測的模塊替換老版本的模塊從新編譯,很容易就能控制變量。

https://kymjs.com/code/2018/04/22/01/

引入了平臺化之後,咱們再從工程結構的角度看一下:就目前咱們嘗試下來,這兩種結構是最合適Android工程模塊化的。一種是submodule,一種是multi-project。

https://kymjs.com/code/2018/04/22/01/

首先看submodule:這種結構是Android默認的多模塊結構,在一個工程下面有多個模塊。圖上每一個綠色的方塊都表明了一個git倉庫,而後咱們看到全部子模塊都包含在主工程模塊內。這種結構也是git默認支持的submodule結構,你只須要用最下面的這句git命令就能夠將他們關聯在一塊兒。
它的好處就是全部都是默認的,任何一我的理解起來都是很直觀。固然,他也有不適合的,就是協做開發的時候,全部人都在app module上測試本身的模塊,很容易互相影響,主工程的git分支也會很是繁雜。

https://kymjs.com/code/2018/04/22/01/

與之對應的,multi-project能很好的解決這個問題:全部模塊都是一個獨立的工程,他們在文件系統上是並列關係,每一個模塊所在的工程纔是一個git倉庫。
可是這種結構就對工程名會有必定的規範要求,主要緣由是在模塊聯調的時候。

https://kymjs.com/code/2018/04/22/01/

咱們看到這段代碼是寫在setting.gradle文件中的,他根據讀取本地的local.properties文件,來include一個模塊的源碼,方便在模塊聯調的時候能夠很容易的修改多模塊的代碼。
可是他就要求每一個模塊工程的文件夾名稱是以模塊名加上Project這樣來命名,好比order模塊所在的工程文件夾名就叫OrderProject
固然,你也能夠不遵照,只不過不遵照就得寫更多代碼,我這裏是直接用了循環,不遵照的話可能就須要把循環拆開手敲了。
以上兩種工程結構各有各的好處,沒有好壞,只有合不合適,咱們內部兩種結構也都有團隊在用。

https://kymjs.com/code/2018/04/22/01/

而後,這裏是模塊聯調的注意事項,就是若是你模塊是以源碼引入的,可能還有其餘模塊引用了一樣模塊的aar,就會形成衝突,你須要本身判斷一下,加個自定義方法也好,用編譯插件也能夠,都能作到讓源碼引用與aar引用互斥。

模塊化架構主要思路就是分而治之,在拆分的時候最重要的就是把依賴整理清楚,那些是業務模塊,哪些是可選的功能組件。最後爲了團隊方便以及更快的適應,還須要開發一些輔助工具,比方說我前面說的IronBank、BizLifecycle、初始化腳本等等,都是必不可少的。

https://kymjs.com/code/2018/04/22/01/

最後,今天的分享就到這裏,謝謝你們。

相關文章
相關標籤/搜索