Android輕鬆實現跨程序/跨app通訊框架及其原理
Android輕鬆實現跨程序/跨app通訊框架及其原理
先給沒耐心的朋友上酸菜:
ofollow,noindex">框架GitHub 如果覺得好用,希望給個star支援一下
現有跨程序方案:
- aidl - Messenger - broadcast - socket
以上實現都很繁瑣
現基於Messenger 封裝一個跨程序跨app通訊框架
關於Messenger :
可以先看這篇部落格:
Android 基於Message的程序間通訊 Messenger完全解析先簡單介紹下Messenger的原理:
Messenger是系統基於aidl封裝的一個簡易的 通過 handler 傳輸資料
跨程序通訊框架;
建立簡單服務端:
`` mMessenger = new Messenger(handler) ```
在服務裡面返回它的binder即可:
`` @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } ```
在handler 的 handleMessage 即可拿到對面程序的 Messenger
`` static class ServiceHandler extends Handler { @Override public void handleMessage(Message msg) { Messenger client = msg.replyTo; } } ```
簡單客戶端:
`` mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); ```
對,繫結服務端的服務即可
`` static class ClientConn implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { mServer = new Messenger(service); reg(); } @Override public void onServiceDisconnected(ComponentName name) { mServer = null; connect(); } } ```
mServer 即為服務端 的 Messenger 物件;
通過它就可以把訊息發到服務端
服務端在handler裡收到訊息,同時拿到 客戶端的 Messenger
客戶端也要 new 一個 Messenger
`` private static ClientHandler handler = new ClientHandler(); private static Messenger mMessenger = new Messenger(handler); ```
Messenger 裡包含客戶端的handler:
用來處理接受到的服務端傳送來的訊息
`` static class ClientHandler extends Handler { @Override public void handleMessage(Message msg) { //這裡是服務端發來的訊息 } } ```
客戶端必須主動向服務端傳送訊息,同時把自己的Messenger 放到訊息裡面傳送給服務端
`` public void sendMsg(Bundle bundle) { Message msg = Message.obtain(handler, Constant.SEND_MSG_TO_TARGET); msg.setData(bundle); msg.replyTo = mMessenger; //把客戶端自己的Messenger 放到訊息裡面傳送給服務端 try { if (mServer != null) { mServer.send(msg); } } catch (RemoteException e) { e.printStackTrace(); } } ```
這樣,服務端拿到了客戶端的Messenger
它們互相持有對方的 Messenger 就可以做到跨程序的雙向通訊!
注意:Message 為handler的訊息物件,但是不能使用它的obj傳遞訊息
只能用它的 what,arg1,arg2和setData(bundle)傳遞訊息,否則會報:
不可跨程序傳遞非序列化物件錯誤,即時obj傳遞的是一個序列化物件
下面來介紹框架實現思路:
上面這類互相持有對方內部物件的現象是不是跟 介面回撥非常類似!
既然做到了介面回撥,那麼觀察者模式還會遠嗎?
對的,如果服務端通過map持有多個客戶端的Messenger 那不就實現了
觀察者模式嗎?
想到這裡相比就很簡單了
我一開始的思路是,在主程序建立服務;用這個服務持有其他需要通訊的客戶端的
Messenger;
其他通訊的客戶端每個物件建立一個 Messenger;
但是我發現在服務端給每個客戶端儲存物件的map集合不好區分每個物件的key
因為我用的key是Integer;
後來我採用的辦法是獲取客戶端程序的程序名,然後拿程序名的hashCode作為key
這樣每個程序只有一個Messenger物件,但是每個程序可能有多個訊息傳遞
所以再給每個程序建立一個訊息中心
把每個需要接收訊息的物件在訊息中心訂閱;
然後服務端每次接收到訊息都轉發給所有客戶端Messenger
客戶端Messenger再在訊息中心遍歷接收物件訂閱訊息時候的key,把訊息傳送到指定物件
框架基本原理說清楚了!
下面說說跨app通訊
原理:
通過aidl 的跨app通訊,其實就是繫結另一個app的遠端服務:
本框架實現原理是一樣的:
框架用變數${applicationId}表示包名;
這樣包名就是使用本框架的app包名,達到框架在任意專案使用的目的;
<service android:name="cn.leo.messenger.BinderPool" android:exported="true"> <intent-filter> <action android:name="${applicationId}.messenger"/> </intent-filter> </service>
Intent intent = new Intent(mPkgName + ".messenger"); intent.setPackage(mPkgName); mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
剩下的操作就和上面跨程序一樣一樣的了;都是繫結服務而已
框架使用方法:
第一步:
在 application 裡面初始化
MagicMessenger.init(this); //跨程序需要 MagicMessenger.bindOtherAPP(this, "包名"); //跨app需要,不跨app可以去掉
跨app需要主app初始化跨程序功能
注意!初始化程式碼需要在每個程序都初始化,所以不要加程序判斷程式碼
第二步:
在需要接受訊息的物件中,訂閱訊息,第一個引數,是接收訊息的標誌,需要唯一;否則可能會收不到訊息!
訊息依賴bundle傳遞,可傳遞型別跟bundle 允許型別一致
MagicMessenger.subscribe("key", new MessageCallback() { @Override public void onMsgCallBack(Bundle data) { } });
傳送訊息示例:
Bundle bundle = new Bundle(); bundle.putString("test", "activity1 傳送訊息到服務"); MagicMessenger.post("key", bundle); //第一個引數為訊息訂閱標誌,需要唯一
第三步:
在物件銷燬時,取消訂閱,否則會導致記憶體洩漏
引數key 是訂閱時候的唯一標識
MagicMessenger.unsubscribe("key");
依賴方法:
1.在全域性build裡新增倉庫:
allprojects { repositories { ...... maven { url 'https://jitpack.io' } } }
2.在app的build裡新增依賴:
dependencies { ...... implementation 'com.github.jarryleo:MagicMessenger:v2.0' }