Android 徹底組件化方案實踐原2017.07.26mobilehub
作者 | 格竹子 微信公眾號 | mobilehub 1 模塊化,、組件化與插件化 項目發(fā)展到一定程度,隨著人員的增多,,代碼越來越臃腫,,這時候就必須進行模塊化的拆分,。在我看來,模塊化是一種指導(dǎo)理念,,其核心思想就是分而治之,、降低耦合。而在Android工程中如何實施,,目前有兩種途徑,,也是兩大流派,一個是組件化,,一個是插件化,。 提起組件化和插件化的區(qū)別,有一個很形象的圖: 組件化和插件化對比 上面的圖看上去比較清晰,,其實容易導(dǎo)致一些誤解,,有下面幾個小問題,圖中可能說的不太清楚:
本文主要集中講的是組件化的實現(xiàn)思路,對于插件化的技術(shù)細節(jié)不做討論,,我們只是從上面的問答中總結(jié)出一個結(jié)論:組件化和插件化的最大區(qū)別(應(yīng)該也是唯一區(qū)別)就是組件化在運行時不具備動態(tài)添加和修改組件的功能,,但是插件化是可以的。 暫且拋棄對插件化“道德”上的批判,,我認為對于一個Android開發(fā)者來講,,插件化的確是一個福音,這將使我們具備極大的靈活性,。但是苦于目前還沒有一個完全合適,、完美兼容的插件化方案(RePlugin的饑餓營銷做的很好,但還沒看到療效),,特別是對于已經(jīng)有幾十萬代碼量的一個成熟產(chǎn)品來講,,套用任何一個插件化方案都是很危險的工作。所以我們決定先從組件化做起,本著做一個最徹底的組件化方案的思路去進行代碼的重構(gòu),,下面是最近的思考結(jié)果,,歡迎大家提出建議和意見。 2 如何實現(xiàn)組件化 要實現(xiàn)組件化,,不論采用什么樣的技術(shù)路徑,,需要考慮的問題主要包括下面幾個:
代碼解耦 把龐大的代碼進行拆分,Androidstudio能夠提供很好的支持,,使用IDE中的multiple module這個功能,,我們很容易把代碼進行初步的拆分。在這里我們對兩種module進行區(qū)分,
為了方便,,我們統(tǒng)一把library稱之為依賴庫,,而把Component稱之為組件,我們所講的組件化也主要是針對Component這種類型,。而負責拼裝這些組件以形成一個完成app的module,,一般我們稱之為主項目、主module或者Host,,方便起見我們也統(tǒng)一稱為主項目,。 經(jīng)過簡單的思考,我們可能就可以把代碼拆分成下面的結(jié)構(gòu): 組件化簡單拆分 這種拆分都是比較容易做到的,,從圖上看,,讀書、分享等都已經(jīng)拆分組件,,并共同依賴于公共的依賴庫(簡單起見只畫了一個),,然后這些組件都被主項目所引用。讀書,、分享等組件之間沒有直接的聯(lián)系,,我們可以認為已經(jīng)做到了組件之間的解耦。但是這個圖有幾個問題需要指出:
這些問題我們后面一個個來解決,,首先我們先看代碼解耦要做到什么效果,像上面的直接引用并使用其中的類肯定是不行的了,。所以我們認為代碼解耦的首要目標就是組件之間的完全隔離,,我們不僅不能直接使用其他組件中的類,最好能根本不了解其中的實現(xiàn)細節(jié),。只有這種程度的解耦才是我們需要的。 組件的單獨調(diào)試 其實單獨調(diào)試比較簡單,,只需要把apply plugin: 'com.android.library'切換成apply plugin: 'com.android.application'就可以,,但是我們還需要修改一下AndroidManifest文件,因為一個單獨調(diào)試需要有一個入口的actiivity,。 我們可以設(shè)置一個變量isRunAlone,,標記當前是否需要單獨調(diào)試,根據(jù)isRunAlone的取值,,使用不同的gradle插件和AndroidManifest文件,,甚至可以添加Application等Java文件,以便可以做一下初始化的操作,。 為了避免不同組件之間資源名重復(fù),,在每個組件的build.gradle中增加resourcePrefix "xxx_",從而固定每個組件的資源前綴,。下面是讀書組件的build.gradle的示例:
通過這些額外的代碼,,我們給組件搭建了一個測試Host,從而讓組件的代碼運行在其中,,所以我們可以再優(yōu)化一下我們上面的框架圖,。 支持單獨調(diào)試的組件化 組件的數(shù)據(jù)傳輸 上面我們講到,主項目和組件,、組件與組件之間不能直接使用類的相互引用來進行數(shù)據(jù)交互,。那么如何做到這個隔離呢?在這里我們采用接口+實現(xiàn)的結(jié)構(gòu),。每個組件聲明自己提供的服務(wù)Service,,這些Service都是一些抽象類或者接口,組件負責將這些Service實現(xiàn)并注冊到一個統(tǒng)一的路由Router中去。如果要使用某個組件的功能,,只需要向Router請求這個Service的實現(xiàn),,具體的實現(xiàn)細節(jié)我們?nèi)徊魂P(guān)心,只要能返回我們需要的結(jié)果就可以了,。這與Binder的C/S架構(gòu)很相像,。 因為我們組件之間的數(shù)據(jù)傳遞都是基于接口編程的,接口和實現(xiàn)是完全分離的,,所以組件之間就可以做到解耦,,我們可以對組件進行替換、刪除等動態(tài)管理,。這里面有幾個小問題需要明確:
下面就是加上數(shù)據(jù)傳輸功能之后的架構(gòu)圖: 組件之間的數(shù)據(jù)傳輸 組件之間的UI跳轉(zhuǎn) 可以說UI的跳轉(zhuǎn)也是組件提供的一種特殊的服務(wù),,可以歸屬到上面的數(shù)據(jù)傳遞中去,。不過一般UI的跳轉(zhuǎn)我們會單獨處理,一般通過短鏈的方式來跳轉(zhuǎn)到具體的Activity,。每個組件可以注冊自己所能處理的短鏈的schme和host,,并定義傳輸數(shù)據(jù)的格式。然后注冊到統(tǒng)一的UIRouter中,,UIRouter通過schme和host的匹配關(guān)系負責分發(fā)路由,。 UI跳轉(zhuǎn)部分的具體實現(xiàn)是通過在每個Activity上添加注解,然后通過apt形成具體的邏輯代碼,。這個也是目前Android中UI路由的主流實現(xiàn)方式,。 組件的生命周期 由于我們要動態(tài)的管理組件,,所以給每個組件添加幾個生命周期狀態(tài):加載、卸載和降維,。為此我們給每個組件增加一個ApplicationLike類,,里面定義了onCreate和onStop兩個生命周期函數(shù)。
一個小的細節(jié)是,主項目負責加載組件,,由于主項目和組件之間是隔離的,,那么主項目如何調(diào)用組件ApplicationLike的生命周期方法呢,目前我們采用的是基于編譯期字節(jié)碼插入的方式,,掃描所有的ApplicationLike類(其有一個共同的父類),,然后通過javassisit在主項目的onCreate中插入調(diào)用ApplicationLike.onCreate的代碼。 我們再優(yōu)化一下組件化的架構(gòu)圖: 組件的生命周期 集成調(diào)試 每個組件單獨調(diào)試通過并不意味著集成在一起沒有問題,,因此在開發(fā)后期我們需要把幾個組件機集成到一個app里面去驗證,。由于我們上面的機制保證了組件之間的隔離,所以我們可以任意選擇幾個組件參與集成,。這種按需索取的加載機制可以保證在集成調(diào)試中有很大的靈活性,,并且可以加大的加快編譯速度。 我們的做法是這樣的,,每個組件開發(fā)完成之后,,發(fā)布一個relaese的aar到一個公共倉庫,,一般是本地的maven庫。然后主項目通過參數(shù)配置要集成的組件就可以了,。所以我們再稍微改動一下組件與主項目之間的連接線,,形成的最終組件化架構(gòu)圖如下: 最終結(jié)構(gòu)圖 代碼隔離 此時在回顧我們在剛開始拆分組件化是提出的三個問題,應(yīng)該說都找到了解決方式,,但是還有一個隱患沒有解決,,那就是我們可以使用compile project(xxx:reader.aar)來引入組件嗎?雖然我們在數(shù)據(jù)傳輸章節(jié)使用了接口+實現(xiàn)的架構(gòu),,組件之間必須針對接口編程,,但是一旦我們引入了reader.aar,那我們就完全可以直接使用到其中的實現(xiàn)類啊,,這樣我們針對接口編程的規(guī)范就成了一紙空文,。千里之堤毀于蟻穴,只要有代碼(不論是有意還是無意)是這么做了,,我們前面的工作就白費了,。 我們希望只在assembleDebug或者assembleRelease的時候把aar引入進來,而在開發(fā)階段,,所有組件都是看不到的,,這樣就從根本上杜絕了引用實現(xiàn)類的問題。我們把這個問題交給gradle來解決,,我們創(chuàng)建一個gradle插件,,然后每個組件都apply這個插件,插件的配置代碼也比較簡單:
3 組件化的拆分步驟和動態(tài)需求 拆分原則 組件化的拆分是個龐大的工程,,特別是從幾十萬行代碼的大工程拆分出去,所要考慮的事情千頭萬緒,。為此我覺得可以分成三步:
組件化的動態(tài)需求 最開始我們講到,,理想的代碼組織形式是插件化的方式,屆時就具備了完備的運行時動態(tài)化,。在向插件化遷徙的過程中,,我們可以通過下面的集中方式來實現(xiàn)編譯速度的提升和動態(tài)更新。
4 總結(jié) 本文是筆者在設(shè)計“得到App”的組件化中總結(jié)一些想法,,在設(shè)計之初參考了目前已有的組件化和插件化方案,站在巨人的肩膀上又加了一點自己的想法,,主要是組件化生命周期以及完全的代碼隔離方面,。特別是最后的代碼隔離,不僅要有規(guī)范上的約束(針對接口編程),,更要有機制保證開發(fā)者不犯錯,,我覺得只有做到這一點才能認為是一個徹底的組件化方案。 |
|