SPI+I(xiàn)PC架構(gòu)下的組件化實(shí)現(xiàn)

關(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)列表


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)圖為:

未組件化之前OkBus的APP架構(gòu)圖

在同一進(jìn)程中,使用OKBus在不同模塊間傳遞Message數(shù)據(jù)。

組件化之后的架構(gòu)圖為:

Messenger擴(kuò)展OkBus實(shí)現(xiàn)跨進(jìn)程

特點(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'
}

QQ群:AndroidMVP 555343041

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容