Android 跨程序雙向通訊(Messenger與AIDL)詳解
今天這篇文章主要講一下Messenger與AIDL的區別、優缺點以及各自的使用方法。
Messenger與AIDL的異同
一、Messenger與AIDL相同點
1.都與IPC的呼叫有關;
2.Messenger 是一種輕量級的 IPC方案,底層實現了AIDL,只是進行了封裝,開發的時候不用再寫.aidl檔案。
3.都支援實時通訊;
二、Messenger與AIDL不同點
1.Messenger一次只能處理一個請求(序列)/AIDL一次可以處理多個請求(並行);
2.Messenger不支援RPC,只能通過message傳遞訊息/AIDL支援RPC;
3.Messenger使用簡單,輕量級,不需要建立AIDL檔案/AIDL使用複雜,需要建立AIDL檔案;
三、Messenger與AIDL的優缺點及適用場景
名稱 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
AIDL | 1.功能強大;2.支援實時通訊;3.支援一對多併發通訊;4.支援RPC(遠端過程呼叫) | 1.使用複雜,需建立AIDL檔案;2.需處理好執行緒同步問題 | 低併發的一對多即時通訊,無RPC要求,不需要處理多執行緒) |
Messenger | 1.使用簡單,輕量級;2.支援實時通訊;3.支援一對多序列通訊 | 1.功能簡單;2.不支援RPC;3.資料通過message傳輸;4.不支援高併發場景;5.服務端想要回應客戶端,必須通過Message的replyTo把服務端的Messenger傳遞過去 | 一對多且有RPC需求,想在服務裡處理多執行緒的業務) |
Messenger與AIDL的用法
一、Messenger用法
1、概述
Messenger程序間通訊方式(如圖):
我們可以在客戶端傳送一個Message給服務端,在服務端的handler中會接收到客戶端的訊息,然後進行對應的處理,處理完成後,再將結果等資料封裝成Message,傳送給客戶端,客戶端的handler中會接收到處理的結果。
server端:
收到的請求是放在Handler的MessageQueue裡面,Handler大家都用過,它需要繫結一個Thread,然後不斷poll message執行相關操作,這個過程是同步執行的。client端:
client端要拿到返回值,需要把client的Messenger作為msg.replyTo引數傳遞過去,service端處理完之後,在呼叫客戶端的Messenger的send(Message msg)方法把返回值傳遞迴client
2、例項
接下來我們看一下例項程式碼,一個服務端apk(MessengerServer),一個客戶端apk(MessengerClient)。
=========================服務端=========================:
public class MessengerServer extends Service {
private static final int MSG_FROM_CLIENT = 0x10001;
private static final int MSG_TO_CLIENT = 0x10002;
private static final String IS_LOGIN = "isLogin";
private static final String NICK_NAME = "nickName";
private static final String USER_ID = "userId";
@SuppressLint("HandlerLeak")
private Messenger mMessenger = new Messenger(new Handler() {
@Override
public void handleMessage(Message msgfromClient) {
Message msgToClient = Message.obtain(msgfromClient);//返回給客戶端的訊息
switch (msgfromClient.what) {
//msg 客戶端傳來的訊息
case MSG_FROM_CLIENT:
try {
//模擬耗時
Thread.sleep(2000);
//傳遞資料
Bundle toClicentDate = new Bundle();
toClicentDate.putString(NICK_NAME,"張小可");
toClicentDate.putBoolean(IS_LOGIN,true);
toClicentDate.putInt(USER_ID,10086);
msgToClient.setData(toClicentDate);
msgToClient.what = MSG_TO_CLIENT;
//傳回Client
msgfromClient.replyTo.send(msgToClient);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
super.handleMessage(msgfromClient);
}
});
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
註冊檔案
<service android:name=".MessengerServer">
<intent-filter>
<action android:name="android.intent.action.MESSENGER"/>
</intent-filter>
</service>
服務端就一個Service,可以看到程式碼相當的簡單,只需要去宣告一個Messenger物件,然後onBind方法返回mMessenger.getBinder();
這裡我添加了sleep(2000)模擬耗時,注意在實際使用過程中,可以換成在獨立開闢的執行緒中完成耗時操作,比如和HandlerThread結合使用。
=========================客戶端=========================:
public class MainActivity extends AppCompatActivity {
private static final int MSG_FROM_CLIENT = 0x10001;
private static final int MSG_TO_CLIENT = 0x10002;
private static final String IS_LOGIN = "isLogin";
private static final String NICK_NAME = "nickName";
private static final String USER_ID = "userId";
private boolean isConn;
private Messenger mService;
private TextView tv_state;
private TextView tv_message;
private Button btn_send;
@SuppressLint("HandlerLeak")
private Messenger mMessenger = new Messenger(new Handler()
{
@SuppressLint("SetTextI18n")
@Override
public void handleMessage(Message msgFromServer)
{
switch (msgFromServer.what)
{
case MSG_TO_CLIENT:
Bundle data = msgFromServer.getData();
tv_message.setText("伺服器返回內容\n"+
data.get(NICK_NAME)+"\n"+
data.get(USER_ID)+"\n"+
data.get(IS_LOGIN)+"\n");
break;
}
super.handleMessage(msgFromServer);
}
});
private ServiceConnection mConn = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
mService = new Messenger(service);
isConn = true;
tv_state.setText("連線狀態:connected!");
}
@Override
public void onServiceDisconnected(ComponentName name)
{
mService = null;
isConn = false;
tv_state.setText("連線狀態:disconnected!");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_state = findViewById(R.id.tv_state);
tv_message = findViewById(R.id.tv_message);
btn_send = findViewById(R.id.btn_send);
//開始繫結服務
bindServiceInvoked();
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Message msgFromClient = new Message();
msgFromClient.what = MSG_FROM_CLIENT;
msgFromClient.replyTo = mMessenger;
if (isConn)
{
//往服務端傳送訊息
try {
mService.send(msgFromClient);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConn);
}
private void bindServiceInvoked()
{
Intent intent = new Intent();
intent.setAction("android.intent.action.MESSENGER");
bindService(intent, mConn, Context.BIND_AUTO_CREATE);
}
}
首先bindService,然後在onServiceConnected中拿到回撥的service(IBinder)物件,通過service物件去構造一個mService =new Messenger(service);然後就可以使用mService.send(msg)給服務端了。
我們看到在點選事件裡面我們往服務端傳送訊息:
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Message msgFromClient = new Message();
msgFromClient.what = MSG_FROM_CLIENT;
msgFromClient.replyTo = mMessenger;
if (isConn)
{
//往服務端傳送訊息
try {
mService.send(msgFromClient);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
那麼服務端會收到訊息,處理完成會將結果返回,傳到Client端的mMessenger中的Handler的handleMessage方法中。
這樣我們就實現了用messenger的雙向通訊,不過也發現我們前面說的問題,雖然使用簡單,不用AIDL檔案,但是不支援RPC,那麼我們接下來看一下AIDL的用法。
二、AIDL的用法
1、概述
這裡的Demo主要功能是在客戶端發起登入,登出,服務端處理相應事件,之後將相應事件再回傳給客戶端。
這裡需要先註冊兩個AIDL檔案:
(這裡的AIDL的檔案相當於一個是客戶端的,一個是服務端的)
IGuideAidlInterface 客戶端呼叫服務端的相關介面
package messsage.binli.com.aidlserver;
import messsage.binli.com.aidlserver.IGuideListener;
interface IGuideAidlInterface {
void login(String userName , String passWord , String packageName); //登入
void logout(String packageName); //登出
boolean isLogin(); //是否登入
void registerListener(in IGuideListener listener); //註冊介面
void unregisterListener(in IGuideListener listener); //解註冊介面
}
IGuideListener 返回給客戶端相應的處理結果
// IGuideListener.aidl
package messsage.binli.com.aidlserver;
// Declare any non-default types here with import statements
interface IGuideListener {
void onLoginSuccess(String msg);
void onLoginFail(String msg);
void onLogoutSuccess(String msg);
void onLogoutFail(String msg);
}
⚠️:客戶端和服務端都需要AIDL檔案且需要一致。把服務端生成的AIDL檔案考入到客戶端即可(路基必須保持和服務端一致),如圖:
2、例項
接下來我們看一下例項程式碼,一個服務端apk(AidlServer),一個客戶端apk(AidlClient)。
程式碼非常簡單就不詳細講解了。
======================服務端:======================
public class GuideServer extends Service{
private IGuideListener iGuideListener;
private IGuideAidlInterface.Stub mBinder = new IGuideAidlInterface.Stub() {
@Override
public void login(final String userName, final String passWord, String packageName) throws RemoteException {
new Thread(new Runnable() {
@Override
public void run() {
//模擬延遲任務
try {
Thread.sleep(2000);
if (userName.equals("binli") && passWord.equals("123456")) {
iGuideListener.onLogoutSuccess("登入成功: "+userName);
} else {
iGuideListener.onLoginFail("登入失敗: "+userName);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public void logout(final String packageName) throws RemoteException {
new Thread(new Runnable() {
@Override
public void run() {
//模擬延遲任務
try {
Thread.sleep(2000);
if(packageName.equals("messsage.binli.com.aidlclient")){
iGuideListener.onLogoutSuccess("登出成功!");
}else{
iGuideListener.onLogoutSuccess("登出失敗!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public boolean isLogin() throws RemoteException {
return false;
}
@Override
public void registerListener(IGuideListener listener) throws RemoteException {
if (listener != null) {
iGuideListener = listener;
}
}
@Override
public void unregisterListener(IGuideListener listener) throws RemoteException {
if (listener != null) {
iGuideListener = null;
}
}
};
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
註冊檔案
<service
android:name="messsage.binli.com.aidlserver.GuideServer">
<intent-filter>
<action android:name="com.ecarx.membercenter.action.GUIDE"/>
</intent-filter>
</service>
======================客戶端======================:
public class MainActivity extends AppCompatActivity {
private TextView tv_style;
private EditText et_accotun;
private EditText et_password;
private Button btn_login;
private Button btn_logout;
private IGuideAidlInterface iGuideAidlInterface;
boolean isConnect;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
iGuideAidlInterface = IGuideAidlInterface.Stub.asInterface(iBinder);
tv_style.setText("當前狀態:\n連線成功");
isConnect = true;
try {
iGuideAidlInterface.registerListener(iGuideListener);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
tv_style.setText("當前狀態:\n連線斷開");
isConnect = false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_style = findViewById(R.id.tv_style);
et_accotun = findViewById(R.id.et_accotun);
et_password = findViewById(R.id.et_password);
btn_login = findViewById(R.id.btn_login);
btn_logout = findViewById(R.id.btn_logout);
Intent intent = new Intent();
intent.setAction("com.ecarx.membercenter.action.GUIDE");
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
btn_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(isConnect){
try {
Log.e("TAG","btn_login");
iGuideAidlInterface.login(String.valueOf(et_accotun.getText()),String.valueOf(et_password.getText()) , getPackageName());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
btn_logout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(isConnect){
try {
iGuideAidlInterface.logout(getPackageName());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
iGuideAidlInterface.unregisterListener(iGuideListener);
} catch (RemoteException e) {
e.printStackTrace();
}
unbindService(serviceConnection);
}
private IGuideListener iGuideListener = new IGuideListener.Stub() {
@Override
public void onLoginSuccess(final String msg) throws RemoteException {
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_style.setText("當前狀態:\n連線成功\n"+msg);
}
});
Toast.makeText(MainActivity.this,"客戶端登入成功回撥====" + msg,Toast.LENGTH_LONG).show();
}
@Override
public void onLoginFail(final String msg) throws RemoteException {
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_style.setText("當前狀態:\n連線成功\n"+msg);
Toast.makeText(MainActivity.this,"客戶端登入失敗回撥====" + msg,Toast.LENGTH_LONG).show();
}
});
}
@Override
public void onLogoutSuccess(final String msg) throws RemoteException {
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_style.setText("當前狀態:\n連線成功\n"+msg);
Toast.makeText(MainActivity.this,"客戶端登出成功回撥====" + msg,Toast.LENGTH_LONG).show();
}
});
}
@Override
public void onLogoutFail(final String msg) throws RemoteException {
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_style.setText("當前狀態:\n連線成功\n"+msg);
Toast.makeText(MainActivity.this,"客戶端登出失敗回撥====" + msg,Toast.LENGTH_LONG).show();
}
});
}
};
}
如果有幫助麻煩star一下 ~~
掃碼關注公眾號“偉大程式猿的誕生“,更多幹貨等著你~
掃碼關注公眾號“偉大程式猿的誕生“,更多幹貨等著你~
掃碼關注公眾號“偉大程式猿的誕生“,更多幹貨等著你~
公眾號回覆“資料獲取”,獲取更多幹貨哦~