愛奇藝開源的組件化跨進程通信解决方案,不服不行

mb612e2d70ec53f 2021-09-18 06:24:55 阅读数:860

通信 解决方案 解决 方案 不服

圖中藍色的節點錶示注册服務的當前進程,也就是Server進程,紅色節點錶示Dispatcher進程。整個過程重點在第三步,我們再重點分析一下:


#?RemoteTransfer
private?void?initDispatchProxyLocked()?{
????if?(null?==?dispatcherProxy)?{
????????//從contentprovider取Binder
????????IBinder?dispatcherBinder?=?getIBinderFromProvider();
????????if?(null?!=?dispatcherBinder)?{
????????????//取出後asInterface創建遠程代理對象
????????????dispatcherProxy?=?IDispatcher.Stub.asInterface(dispatcherBinder);
????????????registerCurrentTransfer();
????????}
????}
????...
}
private?void?registerCurrentTransfer()?{
????//向Dispatcher注册自己這個進程的RemoteTransfer?Binder
????dispatcherProxy.registerRemoteTransfer(android.os.Process.myPid(),?this.asBinder());
????...
}
private?IBinder?getIBinderFromProvider()?{
????Cursor?cursor?=?null;
????try?{
????????//通過contentprovider拿到cursor
????????cursor?=?context.getContentResolver().query(getDispatcherProviderUri(),?DispatcherProvider.PROJECTION_MAIN,
????????????????null,?null,?null);
????????if?(cursor?==?null)?{
????????????return?null;
????????}
????????return?DispatcherCursor.stripBinder(cursor);
????}?finally?{
????????IOUtils.closeQuietly(cursor);
????}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.

我們來看這個DispatcherProvider


public?class?DispatcherProvider?extends?ContentProvider?{
????...
[email protected]
????public?Cursor?query(Uri?uri,?String[]?projection,?String?selection,?String[]?selectionArgs,?String?sortOrder)?{
????????//將Binder封裝到cursor中返回
????????return?DispatcherCursor.generateCursor(Dispatcher.getInstance().asBinder());
????}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

接下來我們看服務的獲取,同樣的先看時序圖 ↓愛奇藝開源的組件化跨進程通信解决方案,不服不行_移動開發

1. Andromeda入口通過getRemoteService獲取遠程服務。2-4. 與提昇進程優先級有關,我們暫且不討論。5. 向RemoteTransfer請求獲取遠程服務的包裝bean。6-7. RemoteTransfer請求RemoteServiceTransfer幫忙先從本進程的緩存中查找目標Binder,如果找到直接返回。7.2. 如果沒有命中緩存調用getAndSaveIBinder方法,通過方法名可知,獲取後會將Binder緩存起來,這就是6-7步讀取的緩存。8. RemoteServiceTransfer通過DispatcherProxy發起IPC通信,請求遠程服務Binder。9-10. Dispatcher請ServiceDispatcher幫忙查找進程中的服務注册錶。11. 回到客戶端進程將Binder緩存。12. 將Binder返回給調用方。同樣圖中藍色的節點錶示獲取服務的進程,也就是Client進程,紅色節點錶示Dispatcher進程。至此,遠程服務的注册與獲取流程分析結束。

進程優先級上面提到在獲取遠程服務時,框架做了提昇進程優先級的事情。通常情况下使用遠程服務的端(簡稱Client端)處於前臺進程,而Server端進程已經注册完畢,往往處於後臺。為了提昇Server端的穩定性,最好能將Server端的進程優先級與Client保持接近,否則容易出現被LMK(Low Memory Killer)回收的情况。那如何提昇Server端進程的優先級呢?這裏的做法是用前臺的UI組件(Activity/Fragment/View)bind一個Server端預先插樁好的Service。整套流程最終通過AMS的updateOomAdjLocked方法實現。愛奇藝開源的組件化跨進程通信解决方案,不服不行_Android_02

回到Andromeda實現,這個預先插樁的Service如下:


public?class?CommuStubService?extends?Service?{
????public?CommuStubService()?{}
[email protected]
????public?IBinder?onBind(Intent?intent)?{
????????return?new?ICommuStub.Stub()?{
[email protected]
????????????public?void?commu(Bundle?args)?throws?RemoteException?{
????????????????//do?nothing?now
????????????}
????????};
????}
[email protected]
????public?int?onStartCommand(Intent?intent,?int?flags,?int?startId)?{
????????//這樣可以使Service所在進程的保活效果好一點
????????return?Service.START_STICKY;
????}
????public?static?class?CommuStubService0?extends?CommuStubService?{}
????public?static?class?CommuStubService1?extends?CommuStubService?{}
????public?static?class?CommuStubService2?extends?CommuStubService?{}
????...
????public?static?class?CommuStubService14?extends?CommuStubService?{}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.

複制代碼可見框架預置了15個Service供進程使用,也就是最多支持15個進程,這絕大數場景下足够了;另外維護了一個進程名和Service名稱的映射錶,否則怎麼知道應該bind那個Service,這個映射錶也是在編譯階段插樁完成的。 這個service的bind過程發生在上一章節獲取遠程服務時,流程如下圖:愛奇藝開源的組件化跨進程通信解决方案,不服不行_移動開發_03

圖中模塊根據所在進程分為三部分:

  1. 藍色錶示Client進程,發起獲取遠程服務請求。

  2. 淺灰色錶示Server進程,它事先將服務注册到Dispatcher中。

  3. 紫色錶示Dispatcher進程,內部緩存了各個進程的服務的Binder對象。

我們重點關注的是藍色模塊ConnectionManager部分,實際上當Client向Dispatcher請求遠程服務之後,會立即通過ConnectionManager綁定這個遠程服務所在進程的插樁的StubService,如此一來就提昇了Server所在進程的優先級。至此bind操作已經完成了,那何時unbind呢?顯然是當UI組件銷毀時,因為此時已不在前臺,需要降低進程優先級。如此一來就需要監聽UI組件的生命周期,在onDestroy時進行unbind操作。這就是圖中RemoteManager做的事情,它內部維護了前臺組件的生命周期。Andromeda提供了幾種with方法,用於獲取對應RemoteManager:


public?static?IRemoteManager?with(android.app.Fragment?fragment)?{return?getRetriever().get(fragment);}
public?static?IRemoteManager?with(Fragment?fragment)?{return?getRetriever().get(fragment);}
public?static?IRemoteManager?with(FragmentActivity?fragmentActivity)?{return?getRetriever().get(fragmentActivity);}
public?static?IRemoteManager?with(Activity?activity)?{return?getRetriever().get(activity);}
public?static?IRemoteManager?with(Context?context)?{return?getRetriever().get(context);}
public?static?IRemoteManager?with(View?view)?{return?getRetriever().get(view);} </pre>

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

複制代碼這是借鑒Glide的做法,這些方法最終被轉換為兩類:

  1. 具備生命周期的UI組件,最終是Activity或Fragment。

  2. ApplicationContext。

對於第一種情况,框架會為當前Activity或Fragment添加一個不可見的RemoteManagerFragment以監聽生命周期。對於使用ApplicationContext,獲取遠程服務的場景不做unbind操作。事實上用Jetpack lifecycle組件也可以方便的監聽Activity/Fragment的生命周期,但是這有個前提,那就是Activity必須繼承android.support.v4.app.FragmentActvity,而Fragment必須繼承android.support.v4.app.Fragment,且v4庫的版本必須大於等於26.1.0,從這個版本開始支持了Lifecycle。事件總線在上述通信框架基礎之上,實現事件總線簡直易如反指。我們來看一下使用。


//訂閱事件,這裏MainActivity實現了EventListener接口
Andromeda.subscribe(EventConstants.APPLE_EVENT,MainActivity.this);
//發布事件
Bundle?bundle?=?new?Bundle();
bundle.putString("Result",?"gave?u?five?apples!");
Andromeda.publish(new?Event(EventConstants.APPLE_EVENT,?bundle)); </pre>

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

這裏的Event是事件傳遞的載體。


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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

至於原理,回想一下我們在注册遠程服務的過程中,同時將本進程的RemoteTransfer的Binder也注册到了Dispatcher中。 當我們訂閱一個事件時,只是將Event名稱和監聽器存儲在了本進程的RemoteTransfer中,當另一個進程發布事件時,會通過一次IPC調用將Event對象發送到Dispatcher,Dispatcher收到事件後,會向注册過的RemoteTransfer依次發送回調信息,也就是說這一步可能進行多次IPC調用,效率問題需diss一下。事件到達訂閱進程後會根據事件名稱,提取所有關於此名稱的監聽器,最終發送給監聽者。注意:這裏的監聽器常常使用的是Activity,但顯然RemoteTransfer是屬於進程生命周期的,因此保存監聽器時需使用弱引用。插樁上面分析原理過程中反複提到了插樁,總結一下共有幾處:

  1. 將屬於Dispatcher進程的DispatcherProvider和DispatcherService插入到manifest中(StubServiceGenerator)。

  2. 將各個進程的預置StubService插入到manifest中(StubServiceGenerator)。

  3. 將進程名與StubService的關系錶插入到StubServiceMatcher類的map中(StubServiceMatchInjector)。

對於manifest的操作,框架內提供了不少工具方法,比如獲取所有聲明的進程,值得好好學習一下;對於class的操作使用的是javasisst,這在之前的AOP文章中也介紹過,感興趣的同學自行查閱。在讀源碼過程中發現兩個值得關注的問題:一是DispatcherProvider偽造的DispatcherCursor繼承MatrixCursor,它通常用於返回幾條固定的已知記錄,不需要從數據庫查詢這種場景。二是跨進程傳遞bundle對象時,如果bundle中存放了parcelable對象需要手動設置setClassLoader。


#DispatcherCursor
public?static?IBinder?stripBinder(Cursor?cursor)?{
????if?(null?==?cursor)?{
????????return?null;
????}
????Bundle?bundle?=?cursor.getExtras();
????//從cursor中取出bundle需要設置classLoader
????bundle.setClassLoader(BinderWrapper.class.getClassLoader());
????BinderWrapper?BinderWrapper?=?bundle.getParcelable(KEY_Binder_WRAPPER);
????return?null?!=?BinderWrapper???BinderWrapper.getBinder()?:?null;
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

因為默認情况下bundle傳輸使用的ClassLoader是BootClassLoader,而BootClassLoader只能加載系統類,我們本工程的class需要使用PathClassLoader進行加載,因此需要額外的調用bundle的setClassLoader方法設置類加載器。/? ?缺點? ?/

  • 服務需要手動注册,這個時機不好把握。最好能提供一個自動注册服務的開關,上層不需要關注服務的注册。

  • 發送一次事件需要多次IPC調用效率低,有優化空間。

  • 仍需要書寫AIDL文件。

至此,Andromeda核心的原理我們就分析完了,雖然有些問題有待完善,但已經給我們提供了很多優秀的解决問題的思路,無論是繼續優化還是精簡一下本地化都是不錯的選擇。

[阿裏P6P7【安卓】進階資料分享+加薪跳槽必備面試題](

)

文末

那麼對於想堅持程序員這行的真的就一點希望都沒有嗎?
其實不然,在互聯網的大浪淘沙之下,留下的永遠是最優秀的,我們考慮的不是哪個行業差哪個行業難,就逃避掉這些,無論哪個行業,都會有他的問題,但是無論哪個行業都會有站在最頂端的那群人。我們要做的就是努力提昇自己,讓自己站在最頂端,學曆不够那就去讀,知識不够那就去學。人之所以為人,不就是有解决問題的能力嗎?擋住自己的由於只有自己。
Android希望=技能+面試

 CodeChina開源項目地址:《Android學習筆記總結+移動架構視頻+大廠面試真題+項目實戰源碼》

  • 技能
    愛奇藝開源的組件化跨進程通信解决方案,不服不行_Android_04
  • 面試技巧+面試題
    愛奇藝開源的組件化跨進程通信解决方案,不服不行_Android_05
版权声明:本文为[mb612e2d70ec53f]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210918062454907w.html