分析CmProcess跨進程通信的實現
一、基礎知識準備
1.1、多進程
Android多進程概念:一般一個 app 隻有一個進程,所有的 components 都運行在同一個進程中,進程名稱就是 app 包名。但是每一個進程都有內存的限制,如果一個進程的內存超過瞭這個限制的時候就會報 OOM 錯誤。為瞭解決內存限制的問題,Android 引入瞭多進程的概念,將占用內存的操作放在一個單獨的進程中分擔主進程的壓力。
多進程的好處:
- 分擔主進程的內存壓力。
- 常駐後臺任務。
- 守護進程,主進程和守護進程相互監視,有一方被殺就重新啟動它。
- 多麼塊,對有風險的模塊放在單獨進程,崩潰後不會影響主進程的運行。
多進程的缺點:
- Applicaton的重新創建,每個進程有自己獨立的virtual machine,每次創建新的進程就像創建一個新的Application
- 靜態成員變量和單例模式失效,每個進程有自己獨立的虛擬機,不同虛擬機在內存分配上有不同的地址空間,這就導致不同虛擬機在訪問同一個對象時會產生多分副本。
- SharedPreference的可靠性下降,不支持多進程
- 線程同步機制失效
1.2、Bundle類
bundle 定義 bundle 是一個 final 類,final 類通常功能是完整的,它們不能被繼承。Java 中有許多類是 final 的,譬如 String, Interger 以及其他包裝類。
public final class Bundle extends BaseBundle implements Cloneable, Parcelable
bundle 傳遞的數據可以是 boolean、byte、int、long、float、double、string 等基本類型或它們對應的數組,也可以是對象或對象數組。但是如果傳遞對象或對象數組,該對象必須實現 Serializable 或 Parcelable 接口。由 Bundle 定義我們也可以看到其實現瞭 Parcelable 接口,所以支持實現瞭Parcelable 接口的對象。
因此當我們在一個進程中啟動瞭另外一個進程的 Activity、Service、Receiver,我們就可以在 Bundle 中附加我們需要傳輸給遠程進程的信息(前提是能夠被序列化)並通過 Intent 發送出去。
二、代碼解析
2.1、AIDL接口
1、IEventReceiver:事件接收器
// 事件接受器 interface IEventReceiver { // 這裡的 event 是 bundle 類型 void onEventReceive(String key,in Bundle event); }
2、IPCCallback:看名字也可以看出來是跨進程 callback
interface IPCCallback { // result 也是 bundle void onSuccess(in Bundle result); void onFail(String reason); }
3、IServiceFetcher:獲取服務的。可以再此進行註冊。
interface IServiceFetcher { // service 是 Ibinder 類型 android.os.IBinder getService(java.lang.String name); // 註冊服務 void addService(java.lang.String name, android.os.IBinder service); // 添加回調 void addEventListener(java.lang.String name, android.os.IBinder service); // 移除 service void removeService(java.lang.String name); // 移除回調 void removeEventListener(java.lang.String name); // 發送消息 void post(String key,in Bundle result); }
2.2、啟動分析
根據代碼可知,咱們有三個進程,分別是:
- com.ipc.code:vc :TestActivity 運行所在的進程;這是屬於用戶測的。
- com.ipc.code:vm : 也就是BinderProvider 存在的進程;IPCBus 也在該進程,主要是用於保存和傳遞數據
- com.ipc.code :MainActivity 主進程;
也就是每個進程在初始化的時候,都會走一遍Application 的初始化,因此如果需要對進程做啥操作,可以判斷出具體的進程,然後做一些額外的操作。對於 CmProcess ,所有進程的初始化邏輯都是一樣的。
public class App extends Application { private static final String TAG = "App"; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // 先啟動主進程,之後才啟動其他進程 VCore.init(base); } }
啟動過程中,會主動為每個進程註冊回調,註意是每個進程。
該 init 方法最終會走入到下面的方法中:
public void startup(Context context) { if (!isStartUp) { // 在主線程啟動,每個進程都有一個自己的主線程 if (Looper.myLooper() != Looper.getMainLooper()) { throw new IllegalStateException("VirtualCore.startup() must called in main thread."); } ServiceManagerNative.SERVICE_CP_AUTH = context.getPackageName() + "." + ServiceManagerNative.SERVICE_DEF_AUTH; this.context = context; // 傳入瞭一個 cache 實例,這個實例是隻有主線程有的 IPCBus.initialize(new IServerCache() { @Override public void join(String serverName, IBinder binder) { ServiceManagerNative.addService(serverName, binder); } @Override public void joinLocal(String serverName, Object object) { ServiceCache.addLocalService(serverName,object); } @Override public void removeService(String serverName) { ServiceManagerNative.removeService(serverName); } @Override public void removeLocalService(String serverName) { ServiceCache.removeLocalService(serverName); } @Override public IBinder query(String serverName) { return ServiceManagerNative.getService(serverName); } @Override public Object queryLocal(String serverName) { return ServiceCache.getLocalService(serverName); } @Override public void post(String key,Bundle bundle) { ServiceManagerNative.post(key,bundle); } }); // 這裡是根據進程名字添加註冊的事件接收器 ServiceManagerNative.addEventListener(AppUtil.getProcessName(context, Process.myPid()), EventReceiver.getInstance()); isStartUp = true; } }
這裡整個邏輯很簡單,就是在主線程初始化瞭IPCBus,然後給該進程註冊瞭一個事件分發的監聽。
三、EventReceiver
public class EventReceiver extends IEventReceiver.Stub { private static final String TAG = "EventReceiver"; private static final EventReceiver EVENT_RECEIVER = new EventReceiver(); private EventReceiver(){} public static final EventReceiver getInstance(){ return EVENT_RECEIVER; } @Override public void onEventReceive(String key,Bundle event) { EventCenter.onEventReceive(key,event); } }
整個類的代碼很簡單。但是要註意的是,其繼承瞭IEventReceiver.Stub,說明他具有跨進程傳輸的能力。主要就是通過EventCenter 來分發消息。
由於每個進程都會走一遍初始化邏輯,所以每個進程都註冊瞭事件的接收。
四、ServiceManagerNative
從名字也可以看出來,這個跟我們平時看到的ServiceManager 很像。主要就是用來獲取 service 和註冊 listener 的。
public static void addEventListener(String name, IBinder service) { IServiceFetcher fetcher = getServiceFetcher(); if (fetcher != null) { try { fetcher.addEventListener(name, service); } catch (RemoteException e) { e.printStackTrace(); } } }
首先是調用getServiceFetcher 來獲取最終保存服務的 fetcher。
註冊回調的時候,會先獲取是否存在 (binder)ServiceFetcher ,在將其轉化為本地 binder;這樣 ServiceFetcher 的管理器就可以用瞭。
private static IServiceFetcher getServiceFetcher() { if (sFetcher == null || !sFetcher.asBinder().isBinderAlive()) { synchronized (ServiceManagerNative.class) { Context context = VirtualCore.get().getContext(); Bundle response = new ProviderCall.Builder(context, SERVICE_CP_AUTH).methodName("@").call(); if (response != null) { IBinder binder = BundleCompat.getBinder(response, "_VM_|_binder_"); linkBinderDied(binder); sFetcher = IServiceFetcher.Stub.asInterface(binder); } } } return sFetcher; }
首先是看ProviderCall.Builder(context, SERVICE_CP_AUTH).methodName(“@”).call(),他最終會調用下面的方法:
//ContentProviderCompat 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 = crazyAcquireContentProvider(context, uri); // 這裡會不斷重試最終會獲得對 BinderProvider 的引用 Bundle res = null; try { // 通過約定好的方法名字獲得bindle res = client.call(method, arg, extras); } catch (RemoteException e) { e.printStackTrace(); } finally { releaseQuietly(client); } return res; }
五、BinderProvider
下面看下 BinderProvider 的 call 方法。
新建瞭一個 bundle 對象,然後將 binder 保存在裡面。註意這是通過跨進程調用,最終將 bundle 傳回主進程,然後拿到瞭ServiceFetcher 的 binder,並將其轉為本地 binder。
可以發現這裡對於方法名是 “@” 時,就會返回 bundle ,否則就是返回 null 。
public Bundle call(String method, String arg, Bundle extras) { if ("@".equals(method)) { Bundle bundle = new Bundle(); BundleCompat.putBinder(bundle, "_VM_|_binder_", mServiceFetcher); return bundle; } return null; }
簡單來說,就是大傢都通過 binderProvider 這個進程來保存對於回調的註冊,保存是基於進城名字來的,因此可以保證不會被覆蓋。
此處的mServiceFetcher 是BinderProvider 內部內的實例,但是其繼承瞭IServiceFetcher.Stub,因此也就有瞭跨進程的能力。
到這裡,理一下前面的邏輯:
ServiceManagerNative.addEventListener(AppUtil.getProcessName(context, Process.myPid()), EventReceiver.getInstance());
某個進程的主線程調用這個方法,所做的具體事情如下:
1.通過 binder 拿到瞭binderProvider 中的 IServiceFetcher.Stub 的實例;
2.向IServiceFetcher.Stub 註冊回調,該回調最終會被保存binderProvider 進程裡面。
六、BinderProvider 啟動分析
上面介紹瞭其是怎麼將 listener 註冊到 binderProvider 進程的,但是並沒有講到接下去我們看下 BinderProvider 的啟動過程,
下圖是ContentProvider 的啟動流程。當我們在主進程想獲取 server 的時候,這時候,會看看 provider 存不存在,沒有的就會進行啟動,同時會走 Application 的初始化邏輯,
具體我們可以看下面這個啟動流程圖:
- Application 的 attachBaseContext 方法是優先執行的;
- ContentProvider 的 onCreate的方法 比 Application的onCreate的方法先執行;
- Activity、Service 的 onCreate 方法以及 BroadcastReceiver 的 onReceive 方法,是在 MainApplication 的 onCreate 方法之後執行的;
- 調用流程為: Application 的 attachBaseContext —> ContentProvider 的 onCreate —-> Application 的 onCreate —> Activity、Service 等的 onCreate(Activity 和 Service 不分先後);
這裡主要是梳理瞭下 provider 的啟動過程,並沒有很細講,但是有必要瞭解一下。
七、MainActivity
接下去,開始看MainActivity 裡面的代碼。
調用registerService 註冊服務,傳入IPayManager.class 和MainActivity;記得MainActivity 也實現瞭IPayManager 接口。
VCore.getCore().registerService(IPayManager.class, this);
看下,裡面的具體代碼邏輯
// Vcore public VCore registerService(Class<?> interfaceClass, Object server){ if (VirtualCore.get().getContext() == null){ return this; } Object o = IPCBus.getLocalService(interfaceClass); // 如果是第一次調用就會返回空 IBinder service = ServiceManagerNative.getService(interfaceClass.getName()); if (service != null && o != null){ return this; } IPCBus.registerLocal(interfaceClass,server); // 這裡的註冊就是把 server 保存到 binder 中 IPCBus.register(interfaceClass,server); return this; }
這裡使用瞭一個registerLocal和register 方法,但是本質上兩個方法是有區別的。registerLocal 意思很明確,就是本地ServiceCache保存一份。但是register,確實做瞭一些額外的操作。
public static void register(Class<?> interfaceClass, Object server) { checkInitialized(); // 這裡主要是獲取一個 binder,或者換句話來說,采用 binder 來保存相關數據 ServerInterface serverInterface = new ServerInterface(interfaceClass); // 這裡就是把 binder 保存到 binderProvider TransformBinder binder = new TransformBinder(serverInterface, server); sCache.join(serverInterface.getInterfaceName(), binder); }
首先這裡創建瞭一個ServerInterface 實例,該實例內部保存瞭傳過瞭來的接口和接口的方法,並將方法和 code 聯系在一起。
public ServerInterface(Class<?> interfaceClass) { this.interfaceClass = interfaceClass; Method[] methods = interfaceClass.getMethods(); codeToInterfaceMethod = new SparseArray<>(methods.length); methodToIPCMethodMap = new HashMap<>(methods.length); for (int i = 0; i < methods.length; i++) {// 這裡每一個方法都有一個 code int code = Binder.FIRST_CALL_TRANSACTION + i;// 組成一個 ipcMenhod IPCMethod ipcMethod = new IPCMethod(code, methods[i], interfaceClass.getName()); codeToInterfaceMethod.put(code, ipcMethod);// 保存他們的映射關系 methodToIPCMethodMap.put(methods[i], ipcMethod); } }
同時利用TransformBinder 將接口和 實例保存到 binder 中。再將 binder 和 接口名字 保存到ServiceCache 中。
註冊完以後,下面是調用獲取本地服務:
// 其實 service 本質還是這個 MainActivity IPayManager service = VCore.getCore().getLocalService(IPayManager.class);
最後註冊瞭一個回調:
VCore.getCore().subscribe("key", new EventCallback() { @Override public void onEventCallBack(Bundle event) { } });
最終EventCenter 會保存相關信息;
八、TestActivity
最後啟動 TestActivity ,這個是在另一個進程。在 onCreate 裡面調用下面的方法:
IPayManager service = VCore.getCore().getService(IPayManager.class);
進程剛剛創建,我們看看是怎麼獲取服務的:
// Vcore public <T> T getService(Class<T> ipcClass){ T localService = IPCBus.getLocalService(ipcClass); if (localService != null){ return localService; } return VManager.get().getService(ipcClass); }
這裡很明確,本地肯定是沒有的,因此,最後會從 VManager 中獲取:
// VManager public <T> T getService(Class<T> ipcClass) { T t = IPCBus.get(ipcClass); if (t != null){ return t; } IPCSingleton<T> tipcSingleton = mIPCSingletonArrayMap.get(ipcClass); if (tipcSingleton == null){ tipcSingleton = new IPCSingleton<>(ipcClass); mIPCSingletonArrayMap.put(ipcClass,tipcSingleton); } return tipcSingleton.get(); }
接下去我們看下IPCSingleton 相關邏輯
// IPCSingleton public T get() { if (instance == null) { synchronized (this) { if (instance == null) { instance = IPCBus.get(ipcClass); } } } return instance; }
這是一個單例,目的也很明確,就是隻獲取一次,可以看到後面又調到瞭 IPCBus 裡面。
// IPCBus public static <T> T get(Class<?> interfaceClass) { checkInitialized(); ServerInterface serverInterface = new ServerInterface(interfaceClass); // 這裡獲取的 binder 應該是 TransformBinder IBinder binder = sCache.query(serverInterface.getInterfaceName()); if (binder == null) { return null; } // 這裡使用瞭動態代理 return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new IPCInvocationBridge(serverInterface, binder)); }
這裡采用瞭動態代理創造瞭一個實例,最終返回的實例被保存在一個單例中。
可以看到,這裡回去查找存不存在 binder。
// VirtualCore public IBinder query(String serverName) { return ServiceManagerNative.getService(serverName); }
還是通過ServiceManagerNative 來獲取的 service;這裡又回到我們之前分析過的邏輯。先從 binderProvider 獲取fetcher, 也就是 ServiceFetcher。
IServiceFetcher fetcher = getServiceFetcher();
它也會從ServiceFetcher 中獲取到 binder ,而這個 binder 就是之前我們保存的TransformBinder 。拿到這個之後,還是一樣,將其轉化為該進程的本地 binder .
// BinderProvider private class ServiceFetcher extends IServiceFetcher.Stub {
最後,我們通過動態代理的形式,創建瞭一個 IPayManager 的實例。
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new IPCInvocationBridge(serverInterface, binder));
這裡需要註意的是IPCInvocationBridge 繼承自InvocationHandler。
拿到後,開始調用對應的方法:
if (service != null){ Log.d(TAG, "onCreate: shentest before vcore " + AppUtil.getAppName(this)); // 首先這個 service 是跨進程調用的,怎麼才通知到其他組件?這裡大傢可以思考下 service.pay(5000, new BaseCallback() { @Override public void onSucceed(Bundle result) { textview.setText(result.getString("pay")); Bundle bundle = new Bundle(); bundle.putString("name", "DoDo"); VCore.getCore().post("key",bundle); } @Override public void onFailed(String reason) { } }); }
調用 service.pay 的時候,就會調用動態代理中的 invoke 方法:
public Object invoke(Object o, Method method, Object[] args) throws Throwable { IPCMethod ipcMethod = serverInterface.getIPCMethod(method); if (ipcMethod == null) { throw new IllegalStateException("Can not found the ipc method : " + method.getDeclaringClass().getName() + "@" + method.getName()); } // 這裡很關鍵 return ipcMethod.callRemote(binder, args); }
首先根據方法名獲得ipcMethod,裡面保存瞭方法的 code,接口名字,參數,返回值。接著調用瞭 ipcMethod.callRemote, 該方法又會調用:
// IPCMethod // 這個方法很重要,需要理解其實現過程 public Object callRemote(IBinder server, Object[] args) throws RemoteException { Parcel data = Parcel.obtain(); // 獲取一個新的 parcel 對象 Parcel reply = Parcel.obtain(); Object result; try { data.writeInterfaceToken(interfaceName); data.writeArray(args); // 這裡 server 就是 transformBinder server.transact(code, data, reply, 0); reply.readException(); result = readValue(reply); if (resultConverter != null) { result = resultConverter.convert(result); } } finally { data.recycle(); reply.recycle(); } return result; }
code 變量用於標識客戶端期望調用服務端的哪個函數,因此,雙方需要約定一組 int 值,不同的值代表不同的服務端函數,該值和客戶端的 transact() 函數中第一個參數 code 的值是一致的。
enforceInterface() 是為瞭某種校驗,它與客戶端的 writeInterfaceToken() 對應,具體見下一小節。
readString() 用於從包裹中取出一個字符串。如果該 IPC 調用的客戶端期望返回一些結果,則可以在返回包裹 reply 中調用 Parcel 提供的相關函數寫入相應的結果。 Parcel.writeXXX();
現在要看的是怎麼通過 binder 一步一步拿到參數。
使用 Parcel 一般是通過 Parcel.obtain() 從對象池中獲取一個新的 Parcel 對象,如果對象池中沒有則直接 new 的 Parcel 則直接創建新的一個 Parcel 對象,並且會自動創建一個Parcel-Native 對象。
writeInterfaceToken 用於寫入 IBinder 接口標志,所帶參數是 String 類型的,如 IServiceManager.descriptor = “android.os.IServiceManager”。
之前說的 code 在這裡用上瞭,code 是一個私有變量,跟 method 綁定在一起的。
中間有個 server.transact(code, data, reply, 0); 該方法實現瞭跨進程調用,最終會走到 binderProvider 的下面onTransact方法:
// TransformBinder 運行在主進程 @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { // 回調進來後,就到瞭 MainActivity 的進程 if (code == INTERFACE_TRANSACTION) { reply.writeString(serverInterface.getInterfaceName()); return true; } IPCMethod method = serverInterface.getIPCMethod(code); if (method != null) { try { method.handleTransact(server, data, reply); } catch (Throwable e) { e.printStackTrace(); } return true; } return super.onTransact(code, data, reply, flags); }
這裡主要是根據 code 來獲取到是哪個方法被調用瞭,下面才是真正的處理。
// IPCMethod public void handleTransact(Object server, Parcel data, Parcel reply) { data.enforceInterface(interfaceName); // 確保是目標接口 Object[] parameters = data.readArray(getClass().getClassLoader()); if (parameters != null && parameters.length > 0) { for (int i = 0; i < parameters.length; i++) { if (converters[i] != null) { parameters[i] = converters[i].convert(parameters[i]); } // 如果參數裡面包含有 binder if (parameters[i] instanceof IBinder){ parameters[i] = IPCCallback.Stub.asInterface(((IBinder)parameters[i])); } } } try { // 最終通過反射的形式實現瞭的調用 // 其實最主要的是通過 binder 拿到參數,然後知道對方調用的是哪個方法。 // 現在要分析的是,他怎麼將數據傳過來的 Object res = method.invoke(server, parameters); reply.writeNoException(); reply.writeValue(res); } catch (IllegalAccessException e) { e.printStackTrace(); reply.writeException(e); } catch (InvocationTargetException e) { e.printStackTrace(); reply.writeException(e); } }
看看 convert 裡面的操作:
public Object convert(Object param) { if (param != null) { if (asInterfaceMethod == null) { synchronized (this) { if (asInterfaceMethod == null) { // 找到 asInterface 方法 asInterfaceMethod = findAsInterfaceMethod(type); } } } try { // 因為 asInterface 方法是靜態方法,所以對象可以傳入空,最終轉變成所需要的參數類型 return asInterfaceMethod.invoke(null, param); } catch (Throwable e) { throw new IllegalStateException(e); } } return null; }
通過 convert 這個一調用,就轉變成我們所需要的參數瞭。
private static Method findAsInterfaceMethod(Class<?> type) { for (Class<?> innerClass : type.getDeclaredClasses()) { // public static class Stub extends Binder implements IType if (Modifier.isStatic(innerClass.getModifiers()) && Binder.class.isAssignableFrom(innerClass) && type.isAssignableFrom(innerClass)) { // public static IType asInterface(android.os.IBinder obj) for (Method method : innerClass.getDeclaredMethods()) { if (Modifier.isStatic(method.getModifiers())) { Class<?>[] types = method.getParameterTypes(); if (types.length == 1 && types[0] == IBinder.class) { return method; } } } } } throw new IllegalStateException("Can not found the " + type.getName() + "$Stub.asInterface method."); }
findAsInterfaceMethod 通過層層篩選,最終獲得需要的那個方法:
public static com.cmprocess.ipc.server.IPCCallback com.cmprocess.ipc.server.IPCCallback$Stub.asInterface(android.os.IBinder)
通過 invoke 方法,終將獲得瞭我們需要的類型。
這裡 server 就是 mainActivity。在把對應的參數傳進去即可。最終調到瞭mainActivity 裡面的 pay 方法。
public void pay(final int count, final IPCCallback callBack) { new Thread(new Runnable() { @Override public void run() { SystemClock.sleep(2000); Bundle bundle = new Bundle(); bundle.putString("pay", count + 100 + ""); try { // callback 也是一個binder callBack.onSuccess(bundle); } catch (RemoteException e) { e.printStackTrace(); } } }).start(); }
此處,callback 也是一個binder,調用成功後,發送瞭post。 其實最終也是調用瞭ServiceFetcher 。
// TestActivity VCore.getCore().post("key",bundle);
其實也是通過 binder 來進行發送消息的。由於每個進程都註冊瞭消息回調,因此,每個進程都會收到。
// ServiceCache public static synchronized void sendEvent(String key,Bundle event){ if (sEventCache.isEmpty()){ return; } for (IBinder binder:sEventCache.values()){ IEventReceiver eventReceiver = IEventReceiver.Stub.asInterface(binder); try { eventReceiver.onEventReceive(key, event); } catch (RemoteException e) { e.printStackTrace(); } } }
EventReceiver 存在於每個進程,因此,對於 binderprovider 來說都是客戶端,其他進程則是服務端。最終 EventCenter 會根據 KEY 值來做分發。
到這裡整個流程就基本講完瞭。
不過我們發現還有兩個 service ,他們的作用是幹嘛用的呢?感覺是用來保活的,防止 provider 死瞭。
以上就是分析CmProcess跨進程通信的實現的詳細內容,更多關於CmProcess跨進程通信的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 解析Android AIDL的實例與原理
- Android BindService使用案例講解
- 手把手教你Android全局觸摸事件監聽
- Android中關於Binder常見面試問題小結
- Android掛斷電話最新實現方法