當前參與的專案歷史也很久遠,第一行程式碼據說是寫於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;
@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——包含網路元件、圖片載入元件、各種工具等功能元件
- 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 {
@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高階工程師
喜歡可關注: