關(guān)于組件化,相信很多人耳熟能詳,網(wǎng)上的組件化框架也如雨后春筍,最近做了一些 組件化調(diào)研,開(kāi)始著手探索適合自己項(xiàng)目的一條組件化之路,在此分享一下,歡迎指正交流。
組件化之核心技術(shù)點(diǎn)
在閱讀了大部分組件化相關(guān)的文章和框架之后,大致能總結(jié)出以下幾點(diǎn):
1、組件的路由的注冊(cè)和中央路由的采集
2、組件間的通信(跨進(jìn)程)和打包之后的模塊間通信(同進(jìn)程),以及其兼容(跨/同進(jìn)程)
3、組件向外提供服務(wù),以及組建間的服務(wù)的同異步互調(diào),以及其兼容(跨/同進(jìn)程)
我們的目標(biāo)以及達(dá)到的成果:
1、最小代價(jià)組件化,最簡(jiǎn)單的配置、最靈活的切換
2、組件路由自動(dòng)化注冊(cè),中央路由自動(dòng)化采集
3、服務(wù)自動(dòng)注冊(cè),兼容同異步,兼容跨/同進(jìn)程
4、OkBus實(shí)現(xiàn)的通信機(jī)制,兼容同異步,兼容跨/同進(jìn)程,與傳統(tǒng)使用方式完全 一樣,無(wú)感知無(wú)差別
5、跨組件調(diào)用時(shí)自動(dòng)喚醒,單個(gè)組件調(diào)試時(shí)無(wú)需手動(dòng)打開(kāi)目標(biāo)組件,即使目標(biāo)開(kāi)啟后被殺掉進(jìn)程,同樣可以喚醒加通信一步到位
6、代碼量少的可憐,實(shí)在一句多余的代碼都不想寫(xiě)的懶人體驗(yàn)
一、組建自動(dòng)注冊(cè)和中央路由自動(dòng)收集APT+SPI
第一步,路由Map自動(dòng)化注冊(cè)交給(APT)annotationProcessor
具體細(xì)節(jié)無(wú)需多言,注解標(biāo)識(shí)目標(biāo)url+注解處理器集中處理生成代碼,借助javapoet和auto-service,實(shí)現(xiàn)自動(dòng)注冊(cè)路由到組件Map。
注意、這一步,生成的代碼類(lèi)都會(huì)被標(biāo)注上@AutoService,成為路由注冊(cè)服務(wù)的提供者
第二步,中央路由采用SPI自動(dòng)收集
Java提供的SPI全名就是Service Provider Interface,下面是一段官方的解釋,,其實(shí)就是為某個(gè)接口尋找服務(wù)的機(jī)制,有點(diǎn)類(lèi)似IOC的思想,將裝配的控制權(quán)移交給ServiceLoader。
SPI在平時(shí)我們用到的會(huì)比較少,但是在A(yíng)ndroid模塊開(kāi)發(fā)中就會(huì)比較有用,不同的模塊可以基于接口編程,每個(gè)模塊有不同的實(shí)現(xiàn)service provider,然后通過(guò)SPI機(jī)制自動(dòng)注冊(cè)到一個(gè)配置文件中,就可以實(shí)現(xiàn)在程序運(yùn)行時(shí)掃描加載同一接口的不同service provider。這樣模塊之間不會(huì)基于實(shí)現(xiàn)類(lèi)硬編碼,可插拔。
注上@AutoService的接口實(shí)現(xiàn)類(lèi),會(huì)在META-INF下自動(dòng)生成接口的服務(wù)實(shí)現(xiàn)列表

在最終打包的Application自動(dòng)采集子組件的路由器:
ServiceLoader<IRouterRulesCreator> loader = ServiceLoader.load(IRouterRulesCreator.class);
for (IRouterRulesCreator rules : loader) Router.addRouterRule(rules);
在獨(dú)立運(yùn)行的組件Application也是如此。
第三步,Messenger擴(kuò)展OkBus實(shí)現(xiàn)跨/同進(jìn)程的無(wú)差別操作
在未組件化之前,使用OkBus的APP架構(gòu)圖為:

在同一進(jìn)程中,使用OKBus在不同模塊間傳遞Message數(shù)據(jù)。
組件化之后的架構(gòu)圖為:

特點(diǎn):
1、組件化和非組件化對(duì)OkBus來(lái)說(shuō)使用方式完全一樣,無(wú)感知無(wú)差別,舊代碼基本不用改
2、Messenger 相對(duì)于ContentProvider、Socket、AIDL。操作最簡(jiǎn)單,它是對(duì)AIDL的Message傳遞做了封裝,Message可以作為任何序列數(shù)據(jù)的載體
3、只有一個(gè)服務(wù)器,再多組件整體架構(gòu)也不冗亂
4、自動(dòng)判斷單組件運(yùn)行和多組件打包狀態(tài),
5、模塊自動(dòng)化注冊(cè),數(shù)據(jù)自動(dòng)經(jīng)過(guò)服務(wù)器轉(zhuǎn)發(fā),可以到達(dá)APP內(nèi)的組件的任何一環(huán)
實(shí)現(xiàn)原理:
服務(wù)器保存客戶(hù)端注冊(cè)的信使,收到消息時(shí),遍歷轉(zhuǎn)發(fā)
//1.根據(jù)模塊ID保存所有的客戶(hù)端信使
private ConcurrentHashMap<Integer, Messenger> mClientMessengers = new ConcurrentHashMap<>();
//2.收到消息時(shí)轉(zhuǎn)發(fā)給其他模塊的處理器,來(lái)源模塊除外
Enumeration keys = mClientMessengers.keys();
while (keys.hasMoreElements()) {
int moduleId = (int) keys.nextElement();
Messenger mMessenger = mClientMessengers.get(moduleId);
if (moduleId != msg.arg1) {//不是目標(biāo)來(lái)源模塊,進(jìn)行分發(fā)
Message _msg = Message.obtain(msg);
try {
mMessenger.send(_msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
第四步,OkBus實(shí)現(xiàn)同異步服務(wù)互調(diào)
服務(wù)互調(diào)就是OkBus的兩個(gè)消息,觸發(fā)服務(wù)一個(gè)消息,返回結(jié)果一個(gè)消息
異步互調(diào)就是兩個(gè)消息,對(duì)應(yīng)兩個(gè)回調(diào)
/**
* 服務(wù)規(guī)則:
* 1、服務(wù)的請(qǐng)求ID必須是負(fù)值(正值表示事件)
* 2、服務(wù)的請(qǐng)求ID必須是奇數(shù),偶數(shù)表示該服務(wù)的返回事件,
* 即: requestID-1 = returnID
* 例如 -0xa001表示服務(wù)請(qǐng)求 -0xa002表示-0xa001的服務(wù)返回
*/
/**
* 注冊(cè)服務(wù)
*
* @param serviceId 服務(wù)id
* @param callback 服務(wù)調(diào)用的回調(diào)
* @param <T> 服務(wù)返回的數(shù)據(jù)范型
*/
public <T> void registerService(final int serviceId, final CallBack<T> callback) {
okBus.unRegister(serviceId);//服務(wù)提供者只能有一個(gè)
okBus.register(serviceId, new Event() {
@Override
public void call(Message msg) {
//TODO 優(yōu)化到子線(xiàn)程
OkBus.getInstance().onEvent(serviceId - 1, callback.onCall(msg));
}
});
}
/**
* 異步調(diào)用服務(wù)
*
* @param serviceId 服務(wù)id
* @param callback 回調(diào)
*/
public void fetchService(final int serviceId, final Event callback) {
if (serviceId > 0 || serviceId % 2 == 0) {
assert false : "請(qǐng)求ID必須是負(fù)奇值!";
return;
}
//1、先注冊(cè)回調(diào)
okBus.register(serviceId - 1, new Event() {
@Override
public void call(Message msg) {
callback.call(msg);
okBus.unRegister(serviceId - 1);//服務(wù)是單次調(diào)用,觸發(fā)后即取消注冊(cè)
}
}, Bus.BG);
//2、通知目標(biāo)模塊
okBus.onEvent(serviceId);
}
兩個(gè)即時(shí)的消息一來(lái)一回,就完成了服務(wù)的互調(diào),
服務(wù)因?yàn)槭菍?shí)時(shí)調(diào)用,因此調(diào)用完之后立馬注銷(xiāo)回調(diào)即可。
同步調(diào)用則是在異步調(diào)用的基礎(chǔ)上加了鎖:
/**
* 同步調(diào)用服務(wù)
*
* @param serviceId 服務(wù)ID
* @param timeout 超時(shí)時(shí)間
* @return
*/
public synchronized <T> T fetchService(final int serviceId, int timeout) {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<T> resultRef = new AtomicReference<>();
service.execute(new Runnable() {
@Override
public void run() {
fetchService(serviceId, new Event() {
@Override
public void call(Message msg) {
try {
resultRef.set((T) msg.obj);
} catch (Exception e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
});
}
});
try {
latch.await(timeout, TimeUnit.SECONDS); //最多等待timeout秒
} catch (Exception e) { //等待中斷
e.printStackTrace();
}
return resultRef.get();
}
由于主線(xiàn)程加鎖來(lái)實(shí)現(xiàn)的同步,所以要根據(jù)不同組件的ANR觸發(fā)上限傳入timeout
同時(shí),所有數(shù)據(jù)接收的處理必須放到子線(xiàn)程,否則就是死鎖:
private class WorkThread extends Thread {
Handler mHandler;
@Override
public void run() {
Looper.prepare();
mHandler = new ServiceHandler();
mMessenger = new Messenger(mHandler);
Looper.loop();
}
public void quit() {
mHandler.getLooper().quit();
}
}
注意:子線(xiàn)程Handler需要自己Looper.prepare
第五步,組件和服務(wù)的自動(dòng)化注冊(cè)
原理也是SPI,1、聲明一個(gè)組件:
@AutoService(IModule.class)
public class Module extends BaseModule {
@Override
public void afterConnected() {
}
@Override
public int getModuleIdId() {
return Constants.MODULE_B;
}
}
2、SPI自動(dòng)注冊(cè)
//自動(dòng)注冊(cè)組件服務(wù)
ServiceLoader<IModule> modules = ServiceLoader.load(IModule.class);
for (IModule module : modules) module.init();
第六步,自動(dòng)喚醒,按需加載
單個(gè)組件調(diào)試時(shí),自動(dòng)喚醒服務(wù)器,需要調(diào)用某個(gè)組件服務(wù)時(shí),自動(dòng)喚醒目標(biāo)組件,服務(wù)器和目標(biāo)組件打不打開(kāi),有沒(méi)有被殺死,都能正常喚醒繼續(xù)通信
實(shí)現(xiàn)方案:
1、任意組件打開(kāi)時(shí),自動(dòng)喚醒服務(wù)器
BaseAppModuleApp里面:
Intent intent = new Intent(MessengerService.class.getCanonicalName());// 5.0+ need explicit intent
intent.setPackage(Constants.SERVICE_PACKAGE_NAME); // the package name of Remote Service
boolean mIsBound = bindService(intent, mBaseModule.mConnection, BIND_AUTO_CREATE);
2、調(diào)用目標(biāo)組件的服務(wù)時(shí),自動(dòng)喚醒目標(biāo)組件
Intent ait = new Intent(NoticeService.class.getCanonicalName()); //喚醒目標(biāo)進(jìn)程的服務(wù)Action名
ait.setPackage(Constants.MODULE_PACKAGE_PRE + module_name); //喚醒目標(biāo)進(jìn)程的包名
BaseAppModuleApp.getBaseApplication().bindService(ait, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service != null) {
LogUtils.logOnUI(Constants.TAG, "已經(jīng)自動(dòng)喚醒" + module_name);
Messenger moduleNameMessenger = new Messenger(service);
Message _msg = Message.obtain();
Bundle _data = new Bundle();
_data.putBoolean(Constants.NOTICE_MSG, true);
_msg.setData(_data);
_msg.replyTo = okBus.mServiceMessenger;//把服務(wù)器的信使給目標(biāo)組件的信使,讓他倆自己聯(lián)系,這里僅僅是通知
try {
moduleNameMessenger.send(_msg);
} catch (Exception e) {
e.printStackTrace();
}
//喚醒成功,繼續(xù)發(fā)送異步請(qǐng)求,通知目標(biāo)模塊
okBus.onEvent(serviceId);
} else {
LogUtils.logOnUI(Constants.TAG, module_name + "進(jìn)程,本來(lái)就是醒的");
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtils.logOnUI(Constants.TAG, "自動(dòng)喚醒目標(biāo)進(jìn)程失敗 module_name:" + module_name);
}
}, BIND_AUTO_CREATE);
}
其它
1、主app殼的處理:
開(kāi)發(fā)時(shí)徹底隔離:在開(kāi)發(fā)階段,沒(méi)有引入任何業(yè)務(wù)邏輯組件,所有組件都是看不到的,這樣就從根本上杜絕了引用實(shí)現(xiàn)類(lèi)的問(wèn)題;
打包時(shí)引入依賴(lài):打包階段,才真正的引入業(yè)務(wù)邏輯模塊
dependencies {
if (isDebug.toBoolean()) {//調(diào)試階段,只保證基本邏輯
implementation project(":lib")
} else {//打包階段,才真正的引入業(yè)務(wù)邏輯模塊
implementation project(":module_a")
implementation project(":module_b")
implementation project(":module_service")
}
}
開(kāi)發(fā)階段,模塊不穩(wěn)定,直接引用,避免各種麻煩。
稍微穩(wěn)定之后,可以直接使用一個(gè)編譯好的aar包,減少編譯工作量提升編譯速度。
對(duì)于完全穩(wěn)定,基本不會(huì)改的模塊,直接引用倉(cāng)庫(kù)上的內(nèi)容,在gradle中聲明依賴(lài)就行了。
2、組件只有當(dāng)做單獨(dú)APP運(yùn)行時(shí)才有自己的application
sourceSets {
main {
if (isDebug.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'//這里面才有application
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
3、全局組件開(kāi)關(guān) gradle.properties設(shè)置isDebug,gradle自動(dòng)切換
if (isDebug.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}