Andromeda:適用於多程式架構的元件通訊框架

海海發表於2018-05-30

引言

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

但是,儘管像ARouter等方案其實也支援介面的路由,然而令人遺憾的是隻支援單程式的介面路由

而目前愛奇藝App中,由於複雜的業務場景,導致既有單程式的通訊需求,也有跨程式的通訊需求,並且還要支援跨程式通訊中的Callback呼叫,以及全域性的事件匯流排

那能不能設計一個方案,做到滿足以上需求呢?

這就是Andromeda的誕生背景,在確定了以上需求之後,分析論證了很多方案,最終選擇了目前的這個方案,在滿足要求的同時,還做到了整個程式間通訊的阻塞式呼叫,從而避免了非常ugly的非同步連線程式碼。

Andromeda的功能

Andromeda目前已經開源,開源地址為開源地址為github.com/iqiyi/Andro….

由於頁面跳轉已經有完整而成熟的方案,所以Andromeda就不再做頁面路由的功能了。目前Andromeda主要包含以下功能:

  • 本地服務路由,註冊本地服務是registerLocalService(Class, Object), 獲取本地服務是getLocalService(Class);
  • 遠端服務路由,註冊遠端服務是registerRemoteService(Class, Object), 獲取遠端服務是getRemoteService(Class);
  • 全域性(含所有程式)事件匯流排, 訂閱事件為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就是這樣做的,構架圖如下:

Module_arch

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

  • 每次IPC都需要經過WideRouter,然後再轉發到對應的程式,這樣就導致了本來一次IPC可以解決的問題,需要兩次IPC解決,而IPC本身就是比較耗時的
  • 由於bindService是非同步的,實際上根本做不到真正的阻塞呼叫
  • WideConnectService需要存活到最後,這樣的話就要求WideConnectService需要在存活週期最長的那個程式中,而現在無法動態配置WideConnectService所在的程式,導致在使用時不方便

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

Hermes

這是之前一個餓了麼同事寫的開源框架,它最大的特色就是不需要寫AIDL介面,可以直接像呼叫本地介面一樣呼叫遠端介面。

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

Hermes_connect

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

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

最終方案

再回過頭來思考前面的方案,其實要呼叫遠端服務,無非就是要獲取到通訊用的IBinder,而前面那兩個方案最大的問題就是把遠端服務IBinder的獲取和Service繫結在了一起,那是不是一定要繫結在一起呢? 有沒有可能不通過Service來獲取IBinder呢?

其實是可以的,我們只需要有一個binder的管理器即可。

核心流程

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

Andromeda_module_arch

這個架構的核心就是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的代理。

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

Andromeda_init_flow

這個註冊過程一般發生在子程式初始化的時候,但是其實即使在子程式初始化時沒有註冊也不要緊,其實是可以推遲到需要將自己的遠端服務提供出去,或者需要獲取其他程式的Module的服務時再做這件事也可以,具體原因在下一小節會分析。

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

Andromeda_register_flow

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所在程式的優先順序提高

整個過程如下所示

bindService_flow.png

所以在這裡就需要與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操作即可。

回撥監聽的原理如下圖所示:

ListenerFragment

當時其實有考慮過是否藉助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_arch

關於InterStellar的實現詳情,可以到InterStellar github中檢視。

總結

在Andromeda之前,可能是由於業務場景不夠複雜的原因,絕大多數通訊框架都要麼沒有涉及IPC問題,要麼解決方案不優雅,而Andromeda的意義在於同時融合了本地通訊和遠端通訊,只有做到這樣,我覺得才算完整地解決了元件通訊的問題。

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

最後,Andromeda目前已經開源,開源地址為開源地址為github.com/iqiyi/Andro….,歡迎大家star和fork,有任何問題也歡迎大家提issue.

相關文章