當前參與的項目歷史也很久遠,第一行代碼據說是寫于2014年的某一天,那時Android用的ide還是Eclipse、那時Android還沒有很好的架構指導(mvp、mvvm)、那時Android最新的版本是5.0、那時Android的Material Design還沒流行……
背景
隨著業務和產品發展,目前參與的項目apk有2~10個Android開發人員(注:開發人員數回浮動,不是因為離職,而是是因為當前項目團隊在承接多個項目的并行開發)在進行迭代和維護。當前技術部移動團隊有30+開發人員,有多個不同的項目在并行開發,但是卻沒有架構組(底層碼農管不了組織的事,只能埋頭敲代碼),沒有架構組的最直接的問題是沒有一個組織來統一各個項目的技術選型和技術方案。
今天帶來自己寫的一個組件化框架?XModulable:
XModulable使用:
1. 添加依賴配置
android {defaultConfig {...javaCompileOptions {annotationProcessorOptions {arguments = [ XModule : project.getName() ]}}}
}dependencies {// gradle3.0以上建議使用implementation(或者api) 'com.xpleemoon.xmodulable:XModulable-api:x.x.x'compile 'com.xpleemoon.xmodulable:XModulable-api:x.x.x'annotationProcessor 'com.xpleemoon.xmodulable:XModulable-compiler:x.x.x'...
}復制代碼
2. 實現組件
@XModule(name = "XX組件名")
public class XXModule implements IModule{}復制代碼
3. 初始化sdk
if (isDebug) {XModulable.openDebug();
}
XModulable.init(this);復制代碼
4. 獲取組件
組件獲取有兩種方式:依賴注入和手動查詢獲取。
依賴注入:
public class TestActivity extends BaseActivity {@InjectXModule(name = "xxx")XXModule mXXModule;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);XModulable.inject(this);}
}復制代碼
手動獲取:
XModulable.getInstance().getModule("XX組件名")復制代碼
5. 添加混淆規則
-keep class * implements com.xpleemoon.xmodulable.api.template.XModuleLoader
-keep class * implements com.xpleemoon.xmodulable.api.IModule
-keep class **$$XModulableInjector { *; }復制代碼
原理介紹:
組件化/模塊化
組件:基于可重用的目的,對功能進行封裝,一個功能就是一個組件,例如網絡、IO、圖片加載等等這些都是組件
模塊:基于業務獨立的目的,對一系列有內聚性的業務進行整理,將其與其他業務進行切割、拆分,從主工程或原所在位置抽離為一個相互獨立的部分
由于模塊是獨立、解耦、可重用的特性,在實施組件化/模塊化的過程中,我們需要解決三個主要問題:
1. 模塊通信——因為業務模塊是相互隔離的,它們完全不知道也無法感知其他業務模塊是否存在,所以需要一種盡最大可能的隔離、耦合度相對最低、代價相對最小的可行方案來實現通信
2. 模塊獨立運行——在后續迭代維護的過程中,各個業務線的人員能夠職責更加清晰
3. 模塊靈活組合運行——能夠適應產品需求,靈活拆分組合打包上線
NOTE:組件化/模塊化這一節將會以XModulable為例進行解釋它是如何進行組件化/模塊化:闡述和理解一個程序問題,最直接的方式是寫一個小的demo演示和show關鍵代碼。本文可能有些地方講的不夠詳細,強烈建議拉下XModulable運行看看。
解決拋出的三個問題之前,先過下[XModulable]的工程結構圖和架構圖,上圖中的module對應層級:
app殼層——依賴業務層,可靈活組合業務層模塊
業務層——im、live和main,面向common層實現業務層服務接口,向common注冊和查詢業務模塊
common層——依賴基礎組件層;承接業務層,暴露業務層服務接口,同時為業務層提供模塊路由服務
basic層——basicRes和basicLib
basicRes——包含通用資源和各UI組件
basicLib——包含網路組件、圖片加載組件、各種工具等功能組件
- XModulable只是一個小的demo而已,而圖中展示的是我對于每一層的完整構想,所以當去源碼的時候發現有些是缺失的:common缺失了AOP代碼、basciRes缺失了UI組件,basicLib缺失了幾乎所有的組件。
- XModulable-annoation、XModulable-api和XModulable -compiler屬于XModulable SDK。
- XModulable SDK主要用于業務模塊的注冊(sdk在執行初始化的時候,會自動進行注冊)和獲取(依賴注入和手動獲取)。這里對XModulable Sdk不做具體技術分析,對于依賴注入和注解的編譯期處理不了解或者感興趣的可移步我以前寫的編譯時(Compile time)處理,擼一個簡易版的ButterKnife
1. 模塊通信
模塊化的通信(UI跳轉和數據傳遞),需要抓住幾個基本點:隔離、解耦、代價小(易維護)、傳遞復雜數據(Fragment、View、File……)。實現獨立互不依賴模塊的通信,很容易能夠想到以下幾種方式:
Android傳統通信(比如aidl、廣播、自定義url……)
無法避免高度耦合、以及隨著項目擴張導致難以維護的問題
還有另外一關鍵個問題就是只能進行一些非常簡單的數據傳遞,像Fragment、View、File……這些數據(或者叫對象也行),完全無法通信傳遞,但是這些數據在實際的app中恰恰是組成一個app的關鍵節點。比如說app的主站中有一個MainActivity,它是一個ViewPager+TabLayout的結構,其中的每一個頁面都是來自于不同模塊的Fragment,這個時候我們的通信就完全無法滿足了。
第三方通信(比如EventBus、RxBus……)
容易陷入茫茫的event通知和接收中,增加調試和維護的成本
能夠傳遞一些復雜的數據,通過event事件來攜帶其它數據對象,但是代碼耦合性相應的會增加
第三方路由庫(比如ARouter、OkDeepLink、DeepLinkDispatch……)基本都能夠實現隔離、解耦、代價小(易維護)。至于數據傳遞的話默認只支持一些簡單數據,但是我們可以結合面向接口編程,公共層暴露接口,業務層面向公共層的接口去實現對應的接口方法(UI跳轉、數據讀寫……),最后當業務層使用的時候只需要通過路由到接口,就可以完成復雜數據的通信。以ARouter為例,可以在common層暴露業務模塊的服務接口(IProvider,ARouter提供的服務接口,只要實現了該接口的自定義服務,ARouter都能進行路由操作),然后交由對應的業務模塊去實現common層對應的服務接口,最后在業務模塊中使用ARouter進行路由其他業務模塊暴露的服務接口來實現。
從上面的分析來看,路由+面向接口編程是實現組件化/模塊化的不二之選,但是這里又有一個問題——假設哪天抽風想要更換路由庫或者可能某種特殊需求不同的業務模塊使用了不容的路由庫,那怎么辦呢?沒關系,我們這時候需要對路由庫做一層封裝,使業務模塊內的路由都相互隔離,也就是一個業務模塊內部的路由操作對其他業務模塊來說是一個黑箱操作。我的封裝思路是這樣的:加一個XModule(可以把它想象成一個容器)的概念,在common層暴露服務接口的同時暴露XModule(它的具體實現也是有對應的業務模塊決定的),每一業務模塊都對應一個XModule,用于承載common層暴露的服務接口,業務模塊之間的通信第一步必須先獲取XModule,然后再通過這個容器去拿到服務。
綜上所述,最終的組件化/模塊化采用的是封裝+路由+面向接口編程。以live業務模塊為例,從源碼的角度看下它們是實現這套思路的。在common層把live業務模塊想要暴露給其他業務模塊的服務LiveService進行了暴露,同時在common層暴露了一個LiveModule(live業務模塊的服務容器,承載了LiveService),l,live業務模塊面向common層對應的接口進行實現(LiveModuleImpl和LiveServiceImpl)。這樣的話,上層業務就可以通過XModulable SDK獲取到LiveModule,然后通過LiveModule承載的服務進行調用。
// common層live暴露的XModule(LiveModule)和服務接口(LiveService)
public abstract class LiveModule extends BaseModule {public abstract LiveService getLiveService();
}
public interface LiveService extends BaseService {Fragment createLiveEntranceFragment();void startLive();
}
// 業務模塊層——live針對common層暴露的實現LiveModuleImpl和LiveServiceImpl
@XModule(name = ModuleName.LIVE)
public class LiveModuleImpl extends LiveModule {@AutowiredLiveService liveService;@Overridepublic LiveService getLiveService() {return liveService;}
}
@Route(path = PathConstants.PATH_SERVICE_LIVE)
public class LiveServiceImpl implements LiveService {@Overridepublic void init(Context context) {}@Overridepublic Fragment createLiveEntranceFragment() {return new LiveEntranceFragment();}@Overridepublic void startLive() {ARouter.getInstance().build(PathConstants.PATH_VIEW_LIVE).navigation();}
}復制代碼
2. 模塊獨立運行
業務模塊在Android Studio中其實就是一個module,從gradle的角度來說,module不是以application plugin方式運行,就是以library plugin方式運行,所以為了業務模塊也能夠獨立運行,就需要控制gradle能夠在application plugin和library plugin兩種形式下切換,同時還要提供單獨運行時的源碼。
首先在項目的build.gradle中創建業務模塊配置,isStandAlone表示業務模塊是否獨立運行:
ext {applicationId = "com.xpleemoon.sample.modulable"// 通過更改isStandalone的值實現業務模塊是否獨立運行,以及app殼工程對組件的靈活依賴modules = [main: [isStandalone : false,applicationId: "${applicationId}.main",],im : [isStandalone : false,applicationId: "${applicationId}.im",],live: [isStandalone : true,applicationId: "${applicationId}.live"],]
}復制代碼
然后設置對應業務模塊的build.gradle:
def currentModule = rootProject.modules.live
// isStandalone的值決定了當前業務模塊是否獨立運行
if (currentModule.isStandalone) {apply plugin: 'com.android.application'
} else {apply plugin: 'com.android.library'
}android {
省略...defaultConfig {if (currentModule.isStandalone) {// 當前組件獨立運行,需要設置applicationIdapplicationId currentModule.applicationId}省略...def moduleName = project.getName()// 業務組件資源前綴,避免沖突resourcePrefix "${moduleName}_"javaCompileOptions {annotationProcessorOptions {arguments = [// ARouter處理器所需參數moduleName : moduleName,// XModulable處理器所需參數XModule: moduleName]}}}
省略...sourceSets {main {// 單獨運行所需要配置的源碼文件if (currentModule.isStandalone) {manifest.srcFile 'src/standalone/AndroidManifest.xml'java.srcDirs = ['src/main/java/', 'src/standalone/java/']res.srcDirs = ['src/main/res', 'src/standalone/res']}}}
}
省略...復制代碼
最后,在業務模塊中編寫build.gradle中sourceSets聲明單獨運行所需要的額外源碼文件,比如Application、SplashActivity和Manifest。
完成上面的過程后,就可以選擇對應的業務模塊live運行
3. 模塊靈活組合運行
模塊的靈活組合,其實也非常簡單,只需要更改業務模塊配置在項目build.gradle的isStandalone值,然后在app殼的build.gradle中通過業務模塊的isStandalone來決定是否依賴就行,關鍵代碼如下:
dependencies {
省略...def modules = rootProject.modulesdef isMainStandalone = modules.main.isStandalonedef isIMStandalone = modules.im.isStandalonedef isLiveStandalone = modules.live.isStandalone// 判斷業務組件是否獨立運行,實現業務組件的靈活依賴if (isMainStandalone && isIMStandalone && isLiveStandalone) {api project(':common')} else {if (!isMainStandalone) {implementation project(':main')}if (!isIMStandalone) {implementation project(':im')}if (!isLiveStandalone) {implementation project(':live')}}
}復制代碼
產品技術債
OK,現在已經把組件化/模塊化所面臨的問題消滅了,那就回過頭來整理現有產品的技術債:
代碼耦合、臃腫、混亂
模塊層級不合理
業務模塊相互依賴耦合
業務模塊拆分粒度不夠,某些模塊像個大雜燴
業務模塊無法單獨編譯運行,業務模塊之間無法靈活組合成apk
基礎組件無法快速提取,以供給其他工程使用
上述問題直接導致新來同事無法快速理清工程結構,以及無法快速進入開發。
若團隊后續擴張的話,勢必會按照業務功能模塊劃分獨立的業務小組,那么會導致人員組織架構和工程組織架構上打架
對癥下藥
(一)控制代碼質量
團隊內部人員需要有代碼質量意識,否則,容易出現有一波人在重構優化,而另一波人卻在寫bug、寫爛代碼,這樣就完全失去了重構的意義。所以,在進入重構之前務必要明確傳達控制代碼質量。
控制公共分支(master、develop和版本分支)權限,將公共分支的權限集中在少數人手里,可避免代碼沖突、分支不可控
非項目負責人只有develop權限——無法merge遠端倉庫的master、develop和版本分支
制定git flow和code review流程,提高團隊協作效率
項目負責人從master(或者develop分支,視自身的項目管理而定)遷出版本分支
開發人員從版本分支遷出個人的開發分支
開發人員在個人的開發分支上進行開發工作
開發人員在個人分支上開發完成后需要push到遠端,
開發人員在遠端(我們用的是gitlab)創建merge request(Source branch:個人分支,Target branch:版本分支),同時指派給項目負責人并@相關開發人人員
執行code review
review完成,由負責人進行遠端的分支合并
(二) 合理的模塊層級
首先來看下模塊層級架構圖:
在原有app的層級上,重新劃分模塊層級,這是很吃力的一件事情。因為一個項目經常是有多人并行的開發迭代的,當你已經切割或者規劃出模塊層級了,但是其它成員卻在反其道而行之,必然會導致實施過程中進行代碼合并時有茫茫多的沖突需要解決和返工,所以我們在這里還需要灌輸模塊層級思想和規劃。
劃分層級,從上到依次為:app殼層、業務層、common層、basic層,它們的職責如下
app殼層——直接依賴業務模塊
業務層——項目中各個獨立的業務功能的聚合,由多個業務模塊構成業務層
common層——承上啟下:承接上層業務,提供業務模塊路由服務;依賴底層basic,統一提供給上層使用
basic層——basicRes和basicLib
basicRes——包含通用資源和各UI組件
basicLib——包含網路組件、圖片加載組件、各種工具等功能組件
業務模塊提取通用代碼、組件、公共資源進行下沉
通用代碼下沉到common,可能涉及到BaseAplication、BaseActivity、廣播通知事件(也可能是EventBus相關事件,具體視自身而定)
ui組件和基礎資源下沉到basicRes
網路組件、圖片加載組件、各種工具等功能組件下沉到basicLib
大雜燴模塊拆分獨立。以主業務模塊為例,包含了推送、分享、更新、地圖、用戶中心、二手房、新房、租房……,如此臃腫的模塊不可能一次性拆分完成,所以必須制定一個計劃,有節奏的進行推進。我們的規劃是按照業務關聯性由低到高的原則依次拆分:
分享、更新下沉到basicLib
推送、地圖下沉到basicLib
用戶中心獨立成業務模塊
二手房、新房、租房獨立成業務模塊
業務模塊獨立運行;業務模塊之間靈活組合成apk
(三) 基礎組件內網maven依賴
基礎組件拆分完成后,如果直接進行module依賴的話,會導致重復編譯和無法靈活供給其它app使用的問題。所以我們需要將基礎組件上傳內網maven,然后通過maven進行依賴。
basicRes和basicLib作為基礎資源組件和基礎功能組件上傳到內網maven
對basicRes和basicLib根據組件細粒度拆分上傳內網maven,方便其他工程能夠根據實際需求靈活依賴
設定節奏和目標
制定重構節奏和流程如下,將規劃合理分配到各個版本中去,在保證產品迭代的同時,能夠穩步推進基于組件化/模塊化的重構探索實踐。
節奏 | 目標 | 執行范圍 |
第一期 | 控制代碼質量 | 1. 控制公共分支(master、develop和版本分支)權限;2. 制定git flow和code review流程 |
第二期 | 合理的模塊層級(現有層級分割下沉) | 1. 劃分層級;2. 業務模塊提取通用代碼、組件、公共資源進行下沉 |
第三期 | 合理的模塊層級(大雜燴模塊拆分獨立1) | 分享、更新下沉到basicLib |
第四期 | 合理的模塊層級(大雜燴模塊拆分獨立2) | 推送、地圖下沉到basicLib |
第五期 | 合理的模塊層級(大雜燴模塊拆分獨立3) | 用戶中心獨立成業務模塊 |
第六期 | 合理的模塊層級(大雜燴模塊拆分獨立4) | 二手房、新房、租房獨立成業務模塊 |
第七期 | 合理的模塊層級(業務模塊獨立運行和靈活組合) | 業務模塊獨立運行,業務模塊之間靈活組合成apk |
第八期 | 基礎組件內網maven依賴 | 1. basicRes和basicLib上傳到內網maven;2. 對basicRes和basicLib根據組件細粒度拆分上傳內網maven |
源碼:https://github.com/xpleemoon/XModulable
作者:xpleemoon。平安好房Android高級工程師
相關推薦
App組件化與業務拆分那些事
熱修復原理之熱修復框架對比和代碼修復
Android架構之路-三步實現MVP架構(上)
喜歡可關注: