問題場景:
筆者一直想使用組件化開發(fā)框架來進(jìn)行實(shí)現(xiàn)模塊解耦辉川,這一點(diǎn)在協(xié)作開發(fā)的時候很有用芦岂,比如ABC三人開發(fā)一款app李破,其中A需要用到BC的功能,B需要用到AC的功能壹将,C需要用到A功能嗤攻,那么問題來了,如果ABC同時開發(fā)各自模塊诽俯,那么誰都不想等待屯曹,怎么解決這個問題?
解決方法:
很多人會想到ABC定義業(yè)務(wù)接口惊畏,先提供出去恶耽,然后給需要的調(diào)用,這個方案是好的颜启,我們在提供接口的時候偷俭,需要定義各種模塊,下面我們開始編寫一個組件化開發(fā)架構(gòu)(目前筆者項(xiàng)目正在切換的架構(gòu)缰盏,筆者認(rèn)為不錯涌萤,打算模擬源碼實(shí)現(xiàn)開源,這樣的結(jié)構(gòu)夠我玩幾年了~)
下面我們就開始組件化開發(fā)吧:
首先創(chuàng)建一個app項(xiàng)目口猜,同時創(chuàng)建moudleA,moduleB,moduleC模塊负溪,app模塊依賴moudleA,moduleB,moduleC模塊,同時創(chuàng)建一個common作為base能力庫济炎,這個模塊用于我們后面編寫組件化核心代碼
項(xiàng)目結(jié)構(gòu)如下:
其中moudleA,moduleB,moduleC, common模塊作為lib存在(apply plugin: 'com.android.library'),圖解如下:
現(xiàn)在創(chuàng)建了ModuleA,B,C模塊川抡,但是目前了結(jié)構(gòu)無法滿足我們的要求,ABC之間需要定義接口
下面開始我們的編碼:
在moudleA里面定義外部接口A须尚,moudleB里面定義外部接口B崖堤,moudleC里面定義外部接口C,如果ModuleB需要用到A或者C的接口耐床,需要引用A,C項(xiàng)目
implementation project(path: ':moduleA')
implementation project(path: ':modulec')
那么B的結(jié)構(gòu)看上去如下:
這樣MoudleB就可以用到AC的接口密幔,但是有心的讀者可以發(fā)現(xiàn)一個問題,那這樣我還要區(qū)分moudleA撩轰,moudleB胯甩,moudleC干什么?他們互相依賴堪嫂,并且處于同一層那就相當(dāng)于合在了一個Lib里面偎箫,彼此相互依賴無法拆分,這樣的定義模塊就沒有意義了
筆者也認(rèn)為確實(shí)是這樣的溉苛,我們還需要進(jìn)行進(jìn)一步改造镜廉,爭取讓B只能訪問AC的接口,對于moduleA愚战,moduleC的具體實(shí)現(xiàn)不關(guān)心
實(shí)現(xiàn)方法:
我們將MoudleA與MoudleC再次拆分娇唯,分成ModuleA-API與ModuleA-IMPL,將ModuleA-API提供給B使用寂玲,MoudleC同樣的方法拆分
這里我們需要創(chuàng)建文件夾了塔插,
首先在我們的工程目錄下面創(chuàng)建moudleA,moduleB,moduleC文件夾,通知將之前的moudle分別嵌入對應(yīng)目錄拓哟,創(chuàng)建APImoudule與Implmoudle最后修改settings.gradle想许,看上去像這樣:
Project這樣:
Android顯示如下:
Setting配置如下:
include ':app', ':common',
":MoudleA:Api",":MoudleA:Impl",
":MoudleB:Api",":MoudleB:Impl",
":MoudleC:Api",":MoudleC:Impl"
rootProject.name='ComponentApp'
意思就是我們在每個模塊加入一個父目錄
結(jié)構(gòu)看上去如下:
其中Folder只是父目錄,不是工程断序,其他都是工程
這樣的話流纹,如果ModuleB需要使用A與C的接口,只需要依賴MoudleA/C – Api模塊就可以违诗,不需要依賴Impl的具體實(shí)現(xiàn)漱凝,而MoudleA/C-Impl代表具體實(shí)現(xiàn)其Api接口
那么結(jié)構(gòu)就變成這樣了:
模塊B需要用到moduleA,moduleC的接口,用到基礎(chǔ)庫Common诸迟,而真正具體實(shí)現(xiàn)ModuleA/C由協(xié)助者同步開發(fā)茸炒,這樣就不產(chǎn)生過度耦合,注意:圖中ModuleA/C-Impl只是具體實(shí)現(xiàn)阵苇,不會被其他模塊直接使用壁公,而我們最后將所有組件的Api與Impl將注冊到總組件Common中,這樣每個模塊Impl可以拿到Api接口的具體實(shí)現(xiàn)
下面我們開始編寫模擬代碼
1.創(chuàng)建ModuleABC的接口InterfaceABC
2.MoudleB-Impl項(xiàng)目添加ModuleA-Api與ModuleC-Api依賴绅项,用于調(diào)用其他模塊功能紊册,添加ModuleB-Api,用于具體實(shí)現(xiàn)自己外部提供的接口
implementation project(path: ':MoudleB:Api')
implementation project(path: ':MoudleC:Api')
implementation project(path: ':MoudleA:Api')
3.1 先實(shí)現(xiàn)自己模塊功能快耿,ModuleB-Api湿硝,我們定義InterfaceB,提供一個printModuleB接口方法:
/**
* 用于其他模塊調(diào)用
*/
public interface InterfaceB {
void printModuleB();
}
3.2 ModuleB-Impl定義Impl實(shí)現(xiàn)類
public class ModuleBImpl implements InterfaceB {
@Override
public void printModuleB() {
Log.i("MoudleImpl", "print ModuleB");
}
}
- 那么MoudleAC-Impl引用了ModuleB-Api润努,如何獲取其具體實(shí)現(xiàn)呢关斜?
下面就是重點(diǎn)了,我們需要將所有Api與Impl鏈接起來铺浇,并且注冊到一個組件存儲器里面痢畜,而外部只能拿到接口
4.1 步驟4需要定義在Common庫里面,并且所有Impl都有依賴Common庫鳍侣,用于獲取Api服務(wù)丁稀,定義一個存儲器HashMap<String,Object>存儲api名與impl實(shí)現(xiàn),并且提供注冊方法registerApiAndImpl倚聚,有了注冊线衫,還需要提供獲取接口方法。定義ComponentServiceStore類惑折,代碼看起來如下:
public class ComponentServiceStore implements IComponentService {
private Map<String, Object> apiStores = new HashMap<>();
@Override
public <T> void registerApiAndImpl(Class<T> api, Class<? extends T> impl) {
if (api.isInterface() && !impl.isInterface()) {
Object o = NewInstanceFactory.create(impl);
apiStores.put(api.getCanonicalName(), o);
} else {
throw new IllegalStateException("impl is not api subclass");
}
}
public void init(ApiService.IRegisterCallBack callBack) {
callBack.onInit(this);
}
<T> T getServiceImpl(Class<T> apiClazz) {
String canonicalName = apiClazz.getCanonicalName();
Object o = apiStores.containsKey(canonicalName) ? apiStores.get(canonicalName) : null;
return o != null ? (T) o : null;
}
public static class NewInstanceFactory {
@SuppressWarnings("ClassNewInstance")
@NonNull
public static <T> T create(@NonNull Class<T> modelClass) {
// noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
}
}
代碼就是一個簡單的map存取功能授账,這就是核心實(shí)現(xiàn)枯跑,對此我們還需要擴(kuò)展包裝一下,提供一個初始化方法:
public class ApiService {
private static ComponentServiceStore componentService;
public interface IRegisterCallBack {
void onInit(IComponentService componentService);
}
public static void initService(IRegisterCallBack callBack) {
// init componentService
if (componentService == null) {
componentService = new ComponentServiceStore();
}
componentService.init(callBack);
}
public static <T> T getServiceImpl(Class<T> apiClazz) {
if (componentService == null) {
throw new IllegalStateException("you must call ApiComponentService#initService first");
}
return componentService.getServiceImpl(apiClazz);
}
}
上面就是注冊組件化的核心代碼白热,我們可以在自定義Application#onCreate里面進(jìn)行初始化敛助,使用方法如下:(注意,App模塊目前需要將所有模塊都依賴進(jìn)去屋确,這里后續(xù)我們還需要改造纳击,如果app刪除某個模塊,最好我們只需要刪除gradle里面依賴不改動代碼就好)
public class XApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initService();
}
private void initService() {
ApiService.initService(new ApiService.IRegisterCallBack() {
@Override
public void onInit(IComponentService componentService) {
componentService.registerApiAndImpl(InterfaceA.class, ModuleAImpl.class);
componentService.registerApiAndImpl(InterfaceB.class, ModuleBImpl.class);
componentService.registerApiAndImpl(InterfaceC.class, ModuleCImpl.class);
}
});
}
}
所有組件都在App模塊里面注冊好了攻臀,那么我們?nèi)绻贛oudleB-Impl模塊使用其他某塊呢
1. MoudleB-Impl已經(jīng)依賴ModuleA-Api與ModuleC-Api
implementation project(path: ':MoudleC:Api')
implementation project(path: ':MoudleA:Api')
- 獲取其他模塊接口:
public class ModuleBImpl implements InterfaceB {
@Override
public void printModuleB() {
Log.i("MoudleImpl", "print ModuleB");
//這里我們調(diào)用一下模塊C的接口實(shí)現(xiàn)
new TestMocker().testModuleC();
}
}
public class TestMocker {
public void testModuleC() {
InterfaceC implC = ApiService.getServiceImpl(InterfaceC.class);
if (implC != null) {
implC.printModuleC();
}
}
}
我們在組件B的Impl實(shí)現(xiàn)里面去獲取C的接口并調(diào)用C的接口方法焕数,看看C-Impl是否實(shí)現(xiàn)了接口
好了,上面就是組件化解耦刨啸,筆者目前項(xiàng)目正在遷移的框架簡介(很有用哦~)
這里遺留一個小問題堡赔,有興趣的讀者可以研究一下:
前面我們提到的,app模塊如果注冊所有api與impl需要將模塊全部引入呜投,如果刪除某個模塊還需要手動修改報錯代碼加匈,這里我們是否可以能夠在動態(tài)添加刪除模塊的時候,不修改代碼仑荐,讓組件注冊的時候自動加載實(shí)例化雕拼,如果加載不到那就不加載呢?
注意
我們使用的組件化Impl實(shí)現(xiàn)都是存儲在ApiService里面的粘招,所以獲取出來的對象是同一個啥寇,但是筆者在項(xiàng)目里面使用的使用,在ModuleBImpl中存儲了一個變量(比如private String userId)洒扎,然后調(diào)用異步操作方法傳入修改了userId辑甜,后面callback回來的使用使用了該userId,但是心細(xì)的小伙伴可以發(fā)現(xiàn)袍冷,異步操作如果該方法調(diào)用多次就會出現(xiàn)回調(diào)獲取的userId與之前的不一致磷醋,所以這里建議Impl實(shí)現(xiàn)不能保存變量,可以使用新的實(shí)例來修改保存胡诗,不能存儲在這里類型單例里面
感謝閱讀邓线,有不當(dāng)之處或者建議,評論回復(fù)煌恢,筆者定努力完善骇陈。