其實Android的組件化由來已久,並且已經有了一些不錯的方案,特別是在頁面跳轉這方面,好比阿里的ARouter, 天貓的統跳協議, Airbnb的DeepLinkDispatch, 藉助註解來完成頁面的註冊,從而很巧妙地實現了路由跳轉。java
可是,儘管像ARouter等方案其實也支持接口的路由,然而使人遺憾的是隻支持單進程的接口路由。git
而目前愛奇藝App中,因爲複雜的業務場景,致使既有單進程的通訊需求,也有跨進程的通訊需求,而且還要支持跨進程通訊中的Callback調用,以及全局的事件總線。github
那能不能設計一個方案,作到知足以上需求呢?服務器
這就是Andromeda的誕生背景,在肯定了以上需求以後,分析論證了不少方案,最終選擇了目前的這個方案,在知足要求的同時,還作到了整個進程間通訊的阻塞式調用,從而避免了很是ugly的異步鏈接代碼<!--more-->。微信
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就能夠搞定。
比較麻煩的是遠程服務,要解決如下難題:
讓任意兩個組件都可以很方便地通訊,即一個組件註冊了本身的遠程服務,任意一個組件都能輕易調用到
讓遠程服務的註冊和使用像本地服務同樣簡單,即要實現阻塞調用
不能下降通訊的效率
這裏最容易想到的就是對傳統的Android IPC通訊方式進行封裝,即在bindService()的基礎上進行封裝,好比ModularizationArchitecture這個開源庫中的WideRouter就是這樣作的,構架圖以下:
這個方案有兩個明顯的缺陷:
每次IPC都須要通過WideRouter,而後再轉發到對應的進程,這樣就致使了原本一次IPC能夠解決的問題,須要兩次IPC解決,而IPC自己就是比較耗時的
因爲bindService是異步的,實際上根本作不到真正的阻塞調用
WideConnectService須要存活到最後,這樣的話就要求WideConnectService須要在存活週期最長的那個進程中,而如今沒法動態配置WideConnectService所在的進程,致使在使用時不方便
考慮到這幾個方面,這個方案pass掉。
這是以前一個餓了麼同事寫的開源框架,它最大的特點就是不須要寫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的問題。
設想這樣一個場景:在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承擔着管理各進程的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呢?
對於耗時操做,咱們直接在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));
在寫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.