轉(zhuǎn)載自:https://mp.weixin.qq.com/s/7SfRnt_jJLU0nrHPeuwomg
引言
其實Android的組件化由來已久常侣,而且已經(jīng)有了一些不錯的方案庭瑰,特別是在頁面跳轉(zhuǎn)這方面堪置,比如阿里的ARouter, 天貓的統(tǒng)跳協(xié)議, Airbnb的DeepLinkDispatch, 借助注解來完成頁面的注冊蔗彤,從而很巧妙地實現(xiàn)了路由跳轉(zhuǎn)剖毯。
但是洲鸠,盡管像ARouter等方案其實也支持接口的路由堂淡,然而令人遺憾的是只支持單進程的接口路由。
Andromeda的功能
- 本地服務路由坛怪,注冊本地服務是registerLocalService(Class, Object), 獲取本地服務是getLocalService(Class);
- 遠程服務路由淤齐,注冊遠程服務是registerRemoteService(Class, Object), 獲取遠程服務是getRemoteService(Class);
- 全局(含所有進程)事件總線, 訂閱事件為subscribe(String, EventListener), 發(fā)布事件為publish(Event);
- 遠程方法回調(diào),如果某個業(yè)務接口需要遠程回調(diào)袜匿,可以在定義aidl接口時使用IPCCallback;
注: 這里的服務不是Android中四大組件的Service,而是指提供的接口與實現(xiàn)更啄。為了表示區(qū)分,后面的服務均是這個含義居灯,而Service則是指Android中的組件祭务。
為什么需要區(qū)分本地服務和遠程服務
最重要的一個原因是本地服務的參數(shù)和返回值類型不受限制,而遠程服務則受binder通信的限制怪嫌。Andromeda的出現(xiàn)為組件化完成了最后一塊拼圖义锥。
Andromeda和其他組件間通信方案的對比如下:
易用性 | IPC性能 | 支持IPC | 支持跨進程事件總線 | 支持IPC Callback | |
---|---|---|---|---|---|
Andromeda | 好 | 高 | yes | yes | yes |
DDComponentForAndroid | 較差 | - | no | no | no |
ModularizationArchitecture | 較差 | 低 | yes | no | no |
接口依賴還是協(xié)議依賴
有人覺得使用Event或ModuleBean來作為組件間通信載體的話,就不用每個業(yè)務模塊定義自己的接口了岩灭,調(diào)用方式也很統(tǒng)一???
但是也有如下缺點:
- 雖然不用定義接口了拌倍,但是為了適應各自的業(yè)務需求,如果使用Event的話噪径,需要定義許多Event; 如果使用ModuleBean的話柱恤,需要為每個ModuleBean定義許多字段,甚至于即使是讓另一方調(diào)用一個空方法找爱,也需要創(chuàng)建一個ModuleBean對象梗顺,這樣的消耗是很大的; 而且隨著業(yè)務增多,這個模塊對應的ModuleBean中需要定義的字段會越來越多车摄,消耗會越來越大
- 代碼可讀性較差寺谤。定義Event/ModuleBean的方式不如接口調(diào)用那么直觀仑鸥,不利于項目的維護
- 我們理解的協(xié)議通信,是指跨平臺/序列化的通信方式变屁,類似終端和服務器間的通信或restful這種⊙劭。現(xiàn)在這種形式在終端內(nèi)很常見了。協(xié)議通信具備一種很強力解耦能力敞贡,但是協(xié)議如果變化了泵琳,兩端怎么同步就變得有點復雜,至少要配合一些框架來實現(xiàn)誊役。在一個應用內(nèi)获列,這樣會不會有點復雜?協(xié)議通信用作組件間通信的話太重了蛔垢,從而導致它應對業(yè)務變化時不夠靈活击孩。
所以最終決定采用接口+數(shù)據(jù)結(jié)構(gòu)的方式進行組件間通信,對于需要暴露的業(yè)務接口和數(shù)據(jù)結(jié)構(gòu)鹏漆,放到一個公共的module中巩梢。
跨進程路由方案的實現(xiàn)
本地服務的路由就不說了,一個Map就可以搞定艺玲。
遠程服務括蝠,需要解決一下難題:
- 讓任意兩個組件都能夠很方便地通信,即一個組件注冊了自己的遠程服務饭聚,任意一個組件都能輕易調(diào)用到
- 讓遠程服務的注冊和使用像本地服務一樣簡單忌警,即要實現(xiàn)阻塞調(diào)用
- 不能降低通信的效率
最終方案:
建立一個一個binder的管理器,如下圖:
詳細分析如下:
這個架構(gòu)的核心就是Dispatcher和RemoteTransfer秒梳, Dispatcher負責管理所有進程的業(yè)務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)發(fā)送給與Dispatcher同進程的DispatcherService, DispatcherService收到之后通知Dispatcher, Dispatcher就通過RemoteTransfer的binder將自己反射注冊過去,這樣RemoteTransfer就獲取到了Dispatcher的代理探越。流程圖如下:
調(diào)用流程簡圖.png
這個注冊過程一般發(fā)生在子進程初始化的時候狡赐,但是其實即使在子進程初始化時沒有注冊也不要緊,其實是可以推遲到需要將自己的遠程服務提供出去扶关,或者需要獲取其他進程的Module的服務時再做這件事也可以阴汇。
遠程服務注冊流程簡圖:
Dispatcher則持有所有進程的RemoteTransfer的代理binder, 以及所有提供服務的業(yè)務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);}
同步獲取binder的問題
設想這樣一個場景:在Dispatcher反向注冊之前数冬,就有一個Module想要調(diào)用另外一個進程中的某個服務(這個服務已經(jīng)注冊到Dispatcher中), 那么此時如何同步獲取呢节槐?
其實是有辦法的搀庶,那就是通過ContentProvider!
利用ContentProviderClient直接獲取IBinder,其調(diào)用方式如下:
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);
}
}
可以在調(diào)用結(jié)果的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的重任别渔,所以不能讓它輕易狗帶附迷。對于絕大多數(shù)App,主進程是存活時間最長的進程哎媚,將Dispatcher置于主進程就可以了喇伯。但是,有些App中存活時間最長的不一定是主進程拨与,比如有的音樂App, 將主進程殺掉之后稻据,播放進程仍然存活,此時顯然將Dispatcher置于播放進程是一個更好的選擇截珍。
為了讓使用Andromeda這個方案的開發(fā)者能夠根據(jù)自己的需求進行配置攀甚,提供了DispatcherExtension這個Extension, 開發(fā)者在apply plugin: ‘org.qiyi.svg.plugin'之后,可在gradle中進行配置:
dispatcher{
process ":downloader"}
}
當然岗喉,如果主進程就是存活時間最長的進程的話秋度,則不需要做任何配置,只需要apply plugin: 'org.qiyi.svg.plugin'即可钱床。