Andromeda:首個適用於多進程架構的組件通訊框架

引言

其實Android的組件化由來已久,並且已經有了一些不錯的方案,特別是在頁面跳轉這方面,好比阿里的ARouter, 天貓的統跳協議, Airbnb的DeepLinkDispatch, 藉助註解來完成頁面的註冊,從而很巧妙地實現了路由跳轉。java

可是,儘管像ARouter等方案其實也支持接口的路由,然而使人遺憾的是隻支持單進程的接口路由git

而目前愛奇藝App中,因爲複雜的業務場景,致使既有單進程的通訊需求,也有跨進程的通訊需求,而且還要支持跨進程通訊中的Callback調用,以及全局的事件總線github

那能不能設計一個方案,作到知足以上需求呢?服務器

這就是Andromeda的誕生背景,在肯定了以上需求以後,分析論證了不少方案,最終選擇了目前的這個方案,在知足要求的同時,還作到了整個進程間通訊的阻塞式調用,從而避免了很是ugly的異步鏈接代碼<!--more-->。微信

Andromeda的功能

Andromeda目前已經開源,開源地址爲開源地址爲https://gitee.com/bettar/Andromeda.restful

因爲頁面跳轉已經有完整而成熟的方案,因此Andromeda就再也不作頁面路由的功能了。目前Andromeda主要包含如下功能:數據結構

  • 本地服務路由,註冊本地服務是registerLocalService(Class, Object), 獲取本地服務是getLocalService(Class);架構

  • 遠程服務路由,註冊遠程服務是registerRemoteService(Class, Object), 獲取遠程服務是getRemoteService(Class);app

  • 全局(含全部進程)事件總線, 訂閱事件爲subscribe(String, EventListener), 發佈事件爲publish(Event);框架

  • 遠程方法回調,若是某個業務接口須要遠程回調,能夠在定義aidl接口時使用IPCCallback;

注: 這裏的服務不是Android中四大組件的Service,而是指提供的接口與實現。爲了表示區分,後面的服務均是這個含義,而Service則是指Android中的組件。

這裏爲何須要區分本地服務和遠程服務呢?

最重要的一個緣由是本地服務的參數和返回值類型不受限制,而遠程服務則受binder通訊的限制。

能夠說,Andromeda的出現爲組件化完成了最後一塊拼圖。

Andromeda和其餘組件間通訊方案的對好比下:

  易用性 IPC性能 支持IPC 支持跨進程事件總線 支持IPC Callback
Andromeda Yes Yes Yes
DDComponentForAndroid 較差 -- No No No
ModularizationArchitecture 較差 Yes No No

接口依賴仍是協議依賴

這個討論頗有意思,由於有人以爲使用Event或ModuleBean來做爲組件間通訊載體的話,就不用每一個業務模塊定義本身的接口了,調用方式也很統一。

可是這樣作的缺陷也很明顯:第一,雖然不用定義接口了,可是爲了適應各自的業務需求,若是使用Event的話,須要定義許多Event; 若是使用ModuleBean的話,須要爲每一個ModuleBean定義許多字段,甚至於即便是讓另外一方調用一個空方法,也須要建立一個ModuleBean對象,這樣的消耗是很大的; 並且隨着業務增多,這個模塊對應的ModuleBean中須要定義的字段會愈來愈多,消耗會愈來愈大。

第二,代碼可讀性較差。定義Event/ModuleBean的方式不如接口調用那麼直觀,不利於項目的維護;

第三,正如微信Android模塊化架構重構實踐(上)中說到的那樣,"咱們理解的協議通訊,是指跨平臺/序列化的通訊方式,相似終端和服務器間的通訊或restful這種。如今這種形式在終端內很常見了。協議通訊具有一種很強力解耦能力,但也有不可忽視的代價。不管什麼形式的通訊,全部的協議定義須要讓通信兩方都能獲知。一般爲了方便會在某個公共區域存放全部協議的定義,這狀況和Event引起的問題有點像。另外,協議若是變化了,兩端怎麼同步就變得有點複雜,至少要配合一些框架來實現。在一個應用內,這樣會不會有點複雜?用起來好像也不那麼方便?更況且它究竟解決多少問題呢"。

顯然,協議通訊用做組件間通訊的話過重了,從而致使它應對業務變化時不夠靈活。

因此最終決定採用"接口+數據結構"的方式進行組件間通訊,對於須要暴露的業務接口和數據結構,放到一個公共的module中。

跨進程路由方案的實現

本地服務的路由就不說了,一個Map就能夠搞定。

比較麻煩的是遠程服務,要解決如下難題:

  • 讓任意兩個組件都可以很方便地通訊,即一個組件註冊了本身的遠程服務,任意一個組件都能輕易調用到

  • 讓遠程服務的註冊和使用像本地服務同樣簡單,即要實現阻塞調用

  • 不能下降通訊的效率

封裝bindService

這裏最容易想到的就是對傳統的Android IPC通訊方式進行封裝,即在bindService()的基礎上進行封裝,好比ModularizationArchitecture這個開源庫中的WideRouter就是這樣作的,構架圖以下:

這個方案有兩個明顯的缺陷:

  • 每次IPC都須要通過WideRouter,而後再轉發到對應的進程,這樣就致使了原本一次IPC能夠解決的問題,須要兩次IPC解決,而IPC自己就是比較耗時的

  • 因爲bindService是異步的,實際上根本作不到真正的阻塞調用

  • WideConnectService須要存活到最後,這樣的話就要求WideConnectService須要在存活週期最長的那個進程中,而如今沒法動態配置WideConnectService所在的進程,致使在使用時不方便

考慮到這幾個方面,這個方案pass掉。

Hermes

這是以前一個餓了麼同事寫的開源框架,它最大的特點就是不須要寫AIDL接口,能夠直接像調用本地接口同樣調用遠程接口。

而它的原理則是利用動態代理+反射的方式來替換AIDL生成的靜態代理,可是它在跨進程這方面本質上採用的仍然是bindService()的方式,以下:

其中Hermes.connect()本質上仍是bindService()的方式,那一樣存在上面的那些問題。另外,Hermes目前還不能很方便地配置進程,以及還不支持in, out, inout等IPC修飾符。

不過,儘管有以上缺點,Hermes仍然是一個優秀的開源框架,至少它提供了一種讓IPC通訊和本地通訊同樣簡單的思路。

最終方案

再回過頭來思考前面的方案,其實要調用遠程服務,無非就是要獲取到通訊用的IBinder,而前面那兩個方案最大的問題就是把遠程服務IBinder的獲取和Service綁定在了一塊兒,那是否是必定要綁定在一塊兒呢? 有沒有可能不經過Service來獲取IBinder呢?

實際上是能夠的,咱們只須要有一個binder的管理器便可。

核心流程

最終採用了註冊-使用的方式,總體架構以下圖:

這個架構的核心就是Dispatcher和RemoteTransfer, Dispatcher負責管理全部進程的業務binder以及各進程中RemoteTransfer的binder; 而RemoteTransfer負責管理它所在進程全部Module的服務binder.

詳細分析以下。

每一個進程有一個RemoteTransfer,它負責管理這個進程中全部Module的遠程服務,包含遠程服務的註冊、註銷以及獲取,RemoteTransfer提供的遠程服務接口爲:

interface IRemoteTransfer {
    oneway void registerDispatcher(IBinder dispatcherBinder);
   
    oneway void unregisterRemoteService(String serviceCanonicalName);
​
    oneway void notify(in Event event);
}

這個接口是給binder管理者Dispatcher使用的,其中registerDispatcher()是Dispatcher將本身的binder反向註冊到RemoteTransfer中,以後RemoteTransfer就可使用Dispatcher的代理進行服務的註冊和註銷了。

在進程初始化時,RemoteTransfer將本身的信息(其實就是自身的binder)發送給與Dispatcher同進程的DispatcherService, DispatcherService收到以後通知Dispatcher, Dispatcher就經過RemoteTransfer的binder將本身反射註冊過去,這樣RemoteTransfer就獲取到了Dispatcher的代理。

這個過程用流程圖表示以下:

這個註冊過程通常發生在子進程初始化的時候,可是其實即便在子進程初始化時沒有註冊也沒關係,實際上是能夠推遲到須要將本身的遠程服務提供出去,或者須要獲取其餘進程的Module的服務時再作這件事也能夠,具體緣由在下一小節會分析。

遠程服務註冊的流程以下所示:

 

Dispatcher則持有全部進程的RemoteTransfer的代理binder, 以及全部提供服務的業務binder, Dispatcher提供的遠程服務接口是IDispatcher,其定義以下:

interface IDispatcher {
​
   BinderBean getTargetBinder(String serviceCanonicalName);
   
   IBinder fetchTargetBinder(String uri);
​
   void registerRemoteTransfer(int pid,IBinder remoteTransferBinder);
​
   void registerRemoteService(String serviceCanonicalName,String processName,IBinder binder);
​
   void unregisterRemoteService(String serviceCanonicalName);
​
   void publish(in Event event);
​
}

Dispatcher提供的服務是由RemoteTransfer來調用的,各個方法的命名都很相信你們都能看懂,就不贅述了。

同步獲取binder的問題

前面的方案中有一個問題咱們尚未提到,那就是同步獲取服務binder的問題。

設想這樣一個場景:在Dispatcher反向註冊以前,就有一個Module想要調用另一個進程中的某個服務(這個服務已經註冊到Dispatcher中), 那麼此時如何同步獲取呢?

這個問題的核心其實在於,如何同步獲取IDispatcher的binder?

實際上是有辦法的,那就是經過ContentProvider!

有兩種經過ContentProvider直接獲取IBinder的方式,比較容易想到的是利用ContentProviderClient, 其調用方式以下:

public static Bundle call(Context context, Uri uri, String method, String arg, Bundle extras) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            return context.getContentResolver().call(uri, method, arg, extras);
        }
        ContentProviderClient client = tryGetContentProviderClient(context, uri);
        Bundle result = null;
        if (null == client) {
            Logger.i("Attention!ContentProviderClient is null");
        }
        try {
            result = client.call(method, arg, extras);
        } catch (RemoteException ex) {
            ex.printStackTrace();
        } finally {
            releaseQuietly(client);
        }
        return result;
    }
​
    private static ContentProviderClient tryGetContentProviderClient(Context context, Uri uri) {
        int retry = 0;
        ContentProviderClient client = null;
        while (retry <= RETRY_COUNT) {
            SystemClock.sleep(100);
            retry++;
            client = getContentProviderClient(context, uri);
            if (client != null) {
                return client;
            }
            //SystemClock.sleep(100);
        }
        return client;
    }
​
    private static ContentProviderClient getContentProviderClient(Context context, Uri uri) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            return context.getContentResolver().acquireUnstableContentProviderClient(uri);
        }
        return context.getContentResolver().acquireContentProviderClient(uri);
    }

能夠在調用結果的Bundle中攜帶IBinder便可,可是這個方案的問題在於ContentProviderClient兼容性較差,在有些手機上第一次運行時會crash,這樣顯然沒法接受。

另一種方式則是藉助ContentResolver的query()方法,將binder放在Cursor中,以下:

DispatcherCursor的定義以下,其中,generateCursor()方法用於將binder放入Cursor中,而stripBinder()方法則用於將binder從Cursor中取出。

public class DispatcherCursor extends MatrixCursor {
​
    public static final String KEY_BINDER_WRAPPER = "KeyBinderWrapper";
​
    private static Map<String, DispatcherCursor> cursorMap = new ConcurrentHashMap<>();
​
    public static final String[] DEFAULT_COLUMNS = {"col"};
​
    private Bundle binderExtras = new Bundle();
​
    public DispatcherCursor(String[] columnNames, IBinder binder) {
        super(columnNames);
        binderExtras.putParcelable(KEY_BINDER_WRAPPER, new BinderWrapper(binder));
    }
​
    @Override
    public Bundle getExtras() {
        return binderExtras;
    }
​
    public static DispatcherCursor generateCursor(IBinder binder) {
        try {
            DispatcherCursor cursor;
            cursor = cursorMap.get(binder.getInterfaceDescriptor());
            if (cursor != null) {
                return cursor;
            }
            cursor = new DispatcherCursor(DEFAULT_COLUMNS, binder);
            cursorMap.put(binder.getInterfaceDescriptor(), cursor);
            return cursor;
        } catch (RemoteException ex) {
            return null;
        }
    }
​
    public static IBinder stripBinder(Cursor cursor) {
        if (null == cursor) {
            return null;
        }
        Bundle bundle = cursor.getExtras();
        bundle.setClassLoader(BinderWrapper.class.getClassLoader());
        BinderWrapper binderWrapper = bundle.getParcelable(KEY_BINDER_WRAPPER);
        return null != binderWrapper ? binderWrapper.getBinder() : null;
    }
​
}
​

其中BinderWrapper是binder的包裝類,其定義以下:

public class BinderWrapper implements Parcelable {
​
    private final IBinder binder;
​
    public BinderWrapper(IBinder binder) {
        this.binder = binder;
    }
​
    public BinderWrapper(Parcel in) {
        this.binder = in.readStrongBinder();
    }
​
    public IBinder getBinder() {
        return binder;
    }
​
    @Override
    public int describeContents() {
        return 0;
    }
​
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeStrongBinder(binder);
    }
​
    public static final Creator<BinderWrapper> CREATOR = new Creator<BinderWrapper>() {
        @Override
        public BinderWrapper createFromParcel(Parcel source) {
            return new BinderWrapper(source);
        }
​
        @Override
        public BinderWrapper[] newArray(int size) {
            return new BinderWrapper[size];
        }
    };
}

再回到咱們的問題,其實只須要設置一個與Dispatcher在同一個進程的ContentProvider,那麼這個問題就解決了。

Dispatcher的進程設置

因爲Dispatcher承擔着管理各進程的binder的重任,因此不能讓它輕易狗帶。

對於絕大多數App,主進程是存活時間最長的進程,將Dispatcher置於主進程就能夠了。

可是,有些App中存活時間最長的不必定是主進程,好比有的音樂App, 將主進程殺掉以後,播放進程仍然存活,此時顯然將Dispatcher置於播放進程是一個更好的選擇。

爲了讓使用Andromeda這個方案的開發者可以根據本身的需求進行配置,提供了DispatcherExtension這個Extension, 開發者在apply plugin: 'org.qiyi.svg.plugin'以後,可在gradle中進行配置:

dispatcher{
    process ":downloader"
}

固然,若是主進程就是存活時間最長的進程的話,則不須要作任何配置,只須要apply plugin: 'org.qiyi.svg.plugin'便可。

 

提高服務提供方的進程優先級

其實原本Andromeda做爲一個提供通訊的框架,我並不想作任何提供進程優先級有關的事情,可是根據一些以往的統計數據,爲了儘量地避免在通訊過程當中出現binderDied問題,至少在通訊過程當中須要讓服務提供方的進程優先級與client端的進程優先級接近,以減小服務提供方進程被殺的機率。

實際上bindService()就作了提高進程優先級的事情。在個人博客bindService過程解析中就分析過,bindService()實質上是作了如下事情:

  • 獲取服務提供方的binder

  • client端經過bind操做,讓Service所在進程的優先級提升

整個過程以下所示

因此在這裏就須要與Activity/Fragment聯繫起來了,在一個Activity/Fragment中首次使用某個遠程服務時,會進行bind操做,以提高服務提供方的進程優先級。

而在Activity/Fragment的onDestroy()回調中,再進行unbind()操做,將鏈接釋放。

這裏有一個問題,就是雖然bind操做對用戶不可見,可是怎麼知道bind哪一個Service呢?

其實很簡單,在編譯時,會爲每一個進程都插樁一個StubService, 而且在StubServiceMatcher這個類中,插入進程名與StubService的對應關係(編譯時經過javassist插入代碼),這樣根據進程名就能夠獲取對應的StubService.

而IDispatcher的getRemoteService()方法中獲取的BinderBean就包含有進程名信息。

生命週期管理

上一節提到了在Activity/Fragment的onDestroy()中須要調用unbind()操做釋放鏈接,若是這個unbind()讓開發者來調用,就太麻煩了。

因此這裏就要想辦法在Activity/Fragment回調onDestroy()時咱們可以監聽到,而後自動給它unbind()掉,那麼如何能作到這一點呢?

其實能夠借鑑Glide的方式,即利用Fragment/Activity的FragmentManager建立一個監聽用的Fragment, 這樣當Fragment/Activity回調onDestroy()時,這個監聽用的Fragment也會收到回調,在這個回調中進行unbind操做便可。

回調監聽的原理以下圖所示:

當時其實有考慮過是否藉助Google推出的Arch componentss來處理生命週期問題,可是考慮到還有的團隊沒有接入這一套,加上arch components的方案其實也變過屢次,因此就暫時採用了這種方案,後面會視狀況決定是否藉助arch components的方案來進行生命週期管理 。

IPCCallback

爲何須要IPCCallback呢?

對於耗時操做,咱們直接在client端的work線程調用是否能夠?

雖然能夠,可是server端可能仍然須要把耗時操做放在本身的work線程中執行,執行完畢以後再回調結果,因此這種狀況下client端的work線程就有點多餘。

因此爲了使用方便,就須要一個IPCCallback, 在server端處理耗時操做以後再回調。

對於須要回調的AIDL接口,其定義以下:

interface IBuyApple {
        int buyAppleInShop(int userId);
        void buyAppleOnNet(int userId,IPCCallback callback);
    }

而client端的調用以下:

IBinder buyAppleBinder = Andromeda.getRemoteService(IBuyApple.class);
        if (null == buyAppleBinder) {
            return;
        }
        IBuyApple buyApple = IBuyApple.Stub.asInterface(buyAppleBinder);
        if (null != buyApple) {
            try {
                buyApple.buyAppleOnNet(10, new IPCCallback.Stub() {
                    @Override
                    public void onSuccess(Bundle result) throws RemoteException {
                       ...
                    }
​
                    @Override
                    public void onFail(String reason) throws RemoteException {
                       ...
                    }
                });
​
            } catch (RemoteException ex) {
                ex.printStackTrace();
            }
        }
    

可是考慮到回調是在Binder線程中,而絕大部分狀況下調用者但願回調在主線程,因此lib封裝了一個BaseCallback給接入方使用,以下:

IBinder buyAppleBinder = Andromeda.getRemoteService(IBuyApple.class);
        if (null == buyAppleBinder) {
            return;
        }
        IBuyApple buyApple = IBuyApple.Stub.asInterface(buyAppleBinder);
        if (null != buyApple) {
            try {
                buyApple.buyAppleOnNet(10, new BaseCallback() {
                    @Override
                    public void onSucceed(Bundle result) {
                       ...
                    }
​
                    @Override
                    public void onFailed(String reason) {
                        ...
                    }
                });
​
            } catch (RemoteException ex) {
                ex.printStackTrace();
            }
        }

開發者可根據本身需求進行選擇。

事件總線

因爲Dispatcher有了各進程的RemoteTransfer的binder, 因此在此基礎上實現一個事件總線就易如反掌了。

簡單地說,事件訂閱時由各RemoteTransfer記錄各自進程中訂閱的事件信息; 有事件發佈時,由發佈者通知Dispatcher, 而後Dispatcher再通知各進程,各進程的RemoteTransfer再通知到各事件訂閱者。

事件

Andromeda中Event的定義以下:

   public class Event implements Parcelable {
    
        private String name;
    
        private Bundle data;
        
        ...
    }

即 事件=名稱+數據,通訊時將須要傳遞的數據存放在Bundle中。其中名稱要求在整個項目中惟一,不然可能出錯。 因爲要跨進程傳輸,因此全部數據只能放在Bundle中進行包裝。

事件訂閱

事件訂閱很簡單,首先須要有一個實現了EventListener接口的對象。 而後就能夠訂閱本身感興趣的事件了,以下:

   Andromeda.subscribe(EventConstants.APPLE_EVENT,MainActivity.this);

其中MainActivity實現了EventListener接口,此處表示訂閱了名稱爲EventConstnts.APPLE_EVENT的事件。

事件發佈

事件發佈很簡單,調用publish方法便可,以下:

   Bundle bundle = new Bundle();
    bundle.putString("Result", "gave u five apples!");
    Andromeda.publish(new Event(EventConstants.APPLE_EVENT, bundle));

InterStellar

在寫Andromeda這個框架的過程當中,有兩件事引發了個人注意,第一件事是因爲業務binder太多致使SWT異常(即Android Watchdog Timeout).

第二件事是跟同事交流的過程當中,思考過能不能不寫AIDL接口, 讓遠程服務真正地像本地服務同樣簡單。

因此就有了InterStellar, 能夠簡單地將其理解爲Hermes的增強版本,不過實現方式並不同,並且InterStellar支持IPC修飾符in, out, inout和oneway.

藉助InterStellar, 能夠像定義本地接口同樣定義遠程接口,以下:

public interface IAppleService {
​
       int getApple(int money);
​
       float getAppleCalories(int appleNum);
​
       String getAppleDetails(int appleNum,  String manifacture,  String tailerName, String userName,  int userId);
​
       @oneway
       void oneWayTest(Apple apple);
​
       String outTest1(@out Apple apple);
​
       String outTest2(@out int[] appleNum);
​
       String outTest3(@out int[] array1, @out String[] array2);
​
       String outTest4(@out Apple[] apples);
​
       String inoutTest1(@inout Apple apple);
​
       String inoutTest2(@inout Apple[] apples);
​
   }

而接口的實現也跟本地服務的實現徹底同樣,以下:

public class AppleService implements IAppleService {
​
    @Override
    public int getApple(int money) {
        return money / 2;
    }
​
    @Override
    public float getAppleCalories(int appleNum) {
        return appleNum * 5;
    }
​
    @Override
    public String getAppleDetails(int appleNum, String manifacture, String tailerName, String userName, int userId) {
        manifacture = "IKEA";
        tailerName = "muji";
        userId = 1024;
        if ("Tom".equals(userName)) {
            return manifacture + "-->" + tailerName;
        } else {
            return tailerName + "-->" + manifacture;
        }
    }
​
    @Override
    public synchronized void oneWayTest(Apple apple) {
        if(apple==null){
            Logger.d("Man can not eat null apple!");
        }else{
            Logger.d("Start to eat big apple that weighs "+apple.getWeight());
            try{
                wait(3000);
                //Thread.sleep(3000);
            }catch(InterruptedException ex){
                ex.printStackTrace();
            }
            Logger.d("End of eating apple!");
        }
    }
​
    @Override
    public String outTest1(Apple apple) {
        if (apple == null) {
            apple = new Apple(3.2f, "Shanghai");
        }
        apple.setWeight(apple.getWeight() * 2);
        apple.setFrom("Beijing");
        return "Have a nice day!";
    }
​
    @Override
    public String outTest2(int[] appleNum) {
        if (null == appleNum) {
            return "";
        }
        for (int i = 0; i < appleNum.length; ++i) {
            appleNum[i] = i + 1;
        }
        return "Have a nice day 02!";
    }
​
    @Override
    public String outTest3(int[] array1, String[] array2) {
        for (int i = 0; i < array1.length; ++i) {
            array1[i] = i + 2;
        }
        for (int i = 0; i < array2.length; ++i) {
            array2[i] = "Hello world" + (i + 1);
        }
​
        return "outTest3";
    }
​
    @Override
    public String outTest4(Apple[] apples) {
        for (int i = 0; i < apples.length; ++i) {
            apples[i] = new Apple(i + 2f, "Shanghai");
        }
​
        return "outTest4";
    }
​
    @Override
    public String inoutTest1(Apple apple) {
        Logger.d("AppleService-->inoutTest1,apple:" + apple.toString());
        apple.setWeight(3.14159f);
        apple.setFrom("Germany");
        return "inoutTest1";
    }
​
    @Override
    public String inoutTest2(Apple[] apples) {
        Logger.d("AppleService-->inoutTest2,apples[0]:" + apples[0].toString());
        for (int i = 0; i < apples.length; ++i) {
            apples[i].setWeight(i * 1.5f);
            apples[i].setFrom("Germany" + i);
        }
        return "inoutTest2";
    }
}

可見整個過程徹底不涉及到AIDL.

那它是如何實現的呢?

答案就藏在Transfer中。本質上AIDL編譯以後生成的Proxy實際上是提供了接口的靜態代理,那麼咱們其實能夠改爲動態代理來實現,將服務方法名和參數傳遞到服務提供方,而後調用相應的方法,最後將結果回傳便可

InterStellar的分層架構以下:

關於InterStellar的實現詳情,能夠到InterStellar github中查看。

總結

在Andromeda以前,多是因爲業務場景不夠複雜的緣由,絕大多數通訊框架都要麼沒有涉及IPC問題,要麼解決方案不優雅,而Andromeda的意義在於同時融合了本地通訊和遠程通訊,只有作到這樣,我以爲纔算完整地解決了組件通訊的問題。

其實跨進程通訊都是在binder的基礎上進行封裝,Andromeda的創新之處在於將binder與Service進行剝離,從而使服務的使用更加靈活。

最後,Andromeda目前已經開源,開源地址爲https://gitee.com/bettar/Andromeda,歡迎你們star和fork,有任何問題也歡迎你們提issue.

相關文章
相關標籤/搜索