1. 程式人生 > >Android 跨程序雙向通訊(Messenger與AIDL)詳解

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一下 ~~

掃碼關注公眾號“偉大程式猿的誕生“,更多幹貨等著你~
掃碼關注公眾號“偉大程式猿的誕生“,更多幹貨等著你~
掃碼關注公眾號“偉大程式猿的誕生“,更多幹貨等著你~

公眾號回覆“資料獲取”,獲取更多幹貨哦~