當前參與的項目歷史也好久遠,第一行代碼聽說是寫於2014年的某一天,那時Android用的ide仍是Eclipse、那時Android尚未很好的架構指導(mvp、mvvm)、那時Android最新的版本是5.0、那時Android的Material Design還沒流行……java
背景android
隨着業務和產品發展,目前參與的項目apk有2~10個Android開發人員(注:開發人員數回浮動,不是由於離職,而是是由於當前項目團隊在承接多個項目的並行開發)在進行迭代和維護。當前技術部移動團隊有30+開發人員,有多個不一樣的項目在並行開發,可是卻沒有架構組(底層碼農管不了組織的事,只能埋頭敲代碼),沒有架構組的最直接的問題是沒有一個組織來統一各個項目的技術選型和技術方案。git
今天帶來本身寫的一個組件化框架 XModulable:github
XModulable使用:編程
1. 添加依賴配置api
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. 實現組件bash
@XModule(name = "XX組件名")
public class XXModule implements IModule{
}複製代碼
3. 初始化sdk網絡
if (isDebug) {
XModulable.openDebug();
}
XModulable.init(this);複製代碼
4. 獲取組件
架構
組件獲取有兩種方式:依賴注入和手動查詢獲取。app
依賴注入:
public class TestActivity extends BaseActivity {
@InjectXModule(name = "xxx")
XXModule mXXModule;
@Override
protected 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——包含網路組件、圖片加載組件、各類工具等功能組件
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 {
@Autowired
LiveService liveService;
@Override
public LiveService getLiveService() {
return liveService;
}
}
@Route(path = PathConstants.PATH_SERVICE_LIVE)
public class LiveServiceImpl implements LiveService {
@Override
public void init(Context context) {
}
@Override
public Fragment createLiveEntranceFragment() {
return new LiveEntranceFragment();
}
@Override
public 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) {
// 當前組件獨立運行,須要設置applicationId
applicationId 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.modules
def isMainStandalone = modules.main.isStandalone
def isIMStandalone = modules.im.isStandalone
def 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高級工程師
喜歡可關注: