Android IPC 之Messenger 原理及應(yīng)用

前言

IPC 系列文章:
建議按順序閱讀。

Android IPC 之Service 還可以這么理解
Android IPC 之Binder基礎(chǔ)
Android IPC 之Binder應(yīng)用
Android IPC 之AIDL應(yīng)用(上)
Android IPC 之AIDL應(yīng)用(下)
Android IPC 之Messenger 原理及應(yīng)用
Android IPC 之服務(wù)端回調(diào)
Android IPC 之獲取服務(wù)(IBinder)
Android Binder 原理換個姿勢就頓悟了(圖文版)

前面從源碼+Demo角度詳盡分析了AIDL,可能會覺得AIDL文件的編寫略微有些麻煩,本篇文章將分析AIDL 簡化版 Messenger 原理及其應(yīng)用。
通過本篇文章,你將了解到:

1、Messenger 客戶端發(fā)送消息給服務(wù)端
2、Messenger 服務(wù)端發(fā)送消息給客戶端
3、Messenger 底層原理
4、Message、AIDL、Messenger區(qū)別與聯(lián)系

1、Messenger 客戶端發(fā)送消息給服務(wù)端

與AIDL 類似,依然分別編寫服務(wù)端、客戶端邏輯。

編寫服務(wù)端

public class MyService extends Service {

    private String TAG = "ipc";

    //創(chuàng)建Handler,用來處理客戶端發(fā)送過來的消息
    private Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            Bundle bundle = msg.getData();
            int id = bundle.getInt("id");
            Log.d(TAG, "receive id from client, id:" + id);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //獲取IBinder 引用
        return new Messenger(handler).getBinder();
    }
}

編寫客戶端

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //獲取服務(wù)端的IBinder引用后,用來構(gòu)造Messenger
            Messenger messenger = new Messenger(service);

            //構(gòu)造Message
            Message message = Message.obtain();
            //往Message填充數(shù)據(jù)
            Bundle bundle = new Bundle();
            bundle.putInt("id", 100);
            message.setData(bundle);
            try {
                //發(fā)送消息
                messenger.send(message);
            } catch (Exception e) {

            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private void bindService() {
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

流程如下:

1、服務(wù)端構(gòu)造Handler用來接收信息
2、客戶端構(gòu)造Messenger來發(fā)送message信息

服務(wù)端收到消息打印如下:


image.png

可以看出不用編寫任何AIDL 文件就可以實現(xiàn)進(jìn)程間通信,很是方便。

2、Messenger 服務(wù)端發(fā)送消息給客戶端

上面的Demo是客戶端往服務(wù)端發(fā)送一條消息,那么如果服務(wù)端想給客戶端發(fā)送回復(fù)消息該怎么實現(xiàn)呢?
以服務(wù)端收到客戶端傳遞過來的id后,查找出id對應(yīng)的學(xué)生的姓名、年齡發(fā)送給客戶端為例。

編寫服務(wù)端

改造一下服務(wù)端代碼:

    //創(chuàng)建Handler,用來處理客戶端發(fā)送過來的消息
    private Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            Bundle bundle = msg.getData();
            int id = bundle.getInt("id");
            Log.d(TAG, "receive id from client, id:" + id);

            //msg.replyTo 為Messenger類型,從客戶端傳遞過來的
            Messenger replyMessenger = msg.replyTo;
            if (replyMessenger != null) {
                //構(gòu)造消息
                Message message = Message.obtain();
                Bundle replyBundle = new Bundle();
                replyBundle.putString("name", "xiaoming");
                replyBundle.putInt("age", 18);
                message.setData(replyBundle);
                try {
                    replyMessenger.send(message);
                } catch (Exception e) {

                }
            }
        }
    };

只是修改了handleMessage(xx),當(dāng)收到客戶端的消息后立即返回查詢到的名字、年齡發(fā)送給客戶端。

編寫客戶端

客戶端代碼也僅僅需要小小的改動:

    //接收服務(wù)端的信息
    private Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            Bundle bundle = msg.getData();
            if (bundle != null) {
                //提取姓名、年齡
                String name = bundle.getString("name");
                int age = bundle.getInt("age");
                Log.d("ipc", "receive name 、age from server, name:" + name + " age:" + age);
            }
        }
    };

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //獲取服務(wù)端的IBinder引用后,用來構(gòu)造Messenger
            Messenger messenger = new Messenger(service);

            //構(gòu)造Message
            Message message = Message.obtain();
            //往Message填充數(shù)據(jù)
            Bundle bundle = new Bundle();
            bundle.putInt("id", 100);
            message.setData(bundle);

            //為了接收服務(wù)端的消息,把自己的Messenger傳遞給服務(wù)端
            message.replyTo = new Messenger(handler);
            try {
                //發(fā)送消息
                messenger.send(message);
            } catch (Exception e) {

            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

客戶端在發(fā)送給服務(wù)端消息時帶上自己的Messenger以便服務(wù)端拿到該Messenger給客戶端發(fā)送信息。同時,需要重寫Handler的handleMessage(xx)接收服務(wù)端的信息。這與服務(wù)端的實現(xiàn)是一致的,相當(dāng)于雙方都有Messenger。

至此,借助于Messenger,輕易就完成了客戶端/服務(wù)端相互通信的功能。

3、Messenger 底層原理

從發(fā)送消息開始

為什么Messenger 能夠如此簡單地完成了IPC,從其發(fā)送消息開始一探究竟。

#Messenger.java
    public void send(Message message) throws RemoteException {
        mTarget.send(message);
    }

mTarget為IMessenger類型,在Messenger構(gòu)造函數(shù)里初始化:

#Messenger.java
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

而target 為Handler類型,跳轉(zhuǎn)到Handler.java里查看:

#Handler.java
    final IMessenger getIMessenger() {
        synchronized (mQueue) {
            if (mMessenger != null) {
                return mMessenger;
            }
            mMessenger = new MessengerImpl();
            return mMessenger;
        }
    }

    private final class MessengerImpl extends IMessenger.Stub {
        public void send(Message msg) {
            msg.sendingUid = Binder.getCallingUid();
            //調(diào)用Handler發(fā)送信息
            Handler.this.sendMessage(msg);
        }
    }

看到這是不是有種似曾相似的感覺,此處MessengerImpl 繼承自IMessenger.Stub,實現(xiàn)了唯一的方法:send(Message msg),該方法的的形參為:Message。
由此,我們輕易得出結(jié)論:

1、服務(wù)端對外暴露了IMessenger 接口,該接口里有唯一的方法:send(Message msg)。
2、服務(wù)端實現(xiàn)了send(Message msg)方法,在該方法里將Message使用Handler發(fā)送出去。

找到IMessenger 對應(yīng)的AIDL 文件:

package android.os;

import android.os.Message;

/** @hide */
oneway interface IMessenger {
    void send(in Message msg);
}

注:該文件在framework/core/java/android/os/IMessenger.aidl
其實就是之前說的AIDL,Message本身支持序列化,加 "in" 標(biāo)記表示數(shù)據(jù)只能從客戶端流向服務(wù)端。

該接口定義還多了個標(biāo)識:"oneway"。
這個字段最終會影響IBinder.transact(xx)里的最后一個形參:

boolean _status = mRemote.transact(Stub.TRANSACTION_send, 
_data, null, android.os.IBinder.FLAG_ONEWAY);

FLAG_ONEWAY 表示transact(xx)不是阻塞調(diào)用,也就是說客戶端調(diào)用該方法后立即返回,不等待。仔細(xì)想想其實也并不用等待,因為send(xx)沒有返回值,又是in 修飾形參,數(shù)據(jù)流不能從服務(wù)端流入客戶端。

image.png

與普通的AIDL 相比,Messenger其實就是封裝了服務(wù)端的接口及其方法,與此同時也封裝了客戶端調(diào)用服務(wù)端的方法??蛻舳藢⒁獋鬟f的消息封裝在Message里,通過Messenger傳遞出去,服務(wù)端收到后在Handler里處理該Message。

服務(wù)端為什么能夠向客戶端發(fā)送消息

從IMessenger.aidl定義可知,通過send(xx)方法只能是由客戶端往服務(wù)端發(fā)送消息,并且沒有返回值。因此想通過方法的形參或返回值攜帶服務(wù)端的數(shù)據(jù)是不現(xiàn)實的了。
客戶端能給服務(wù)端發(fā)送數(shù)據(jù)的最大憑證是:客戶端能夠拿到服務(wù)端的IBinder接口。那么想想反過來行嗎?
來看看Message.java里的字段:

#Message.java
    /**
     * Optional Messenger where replies to this message can be sent.  The
     * semantics of exactly how this is used are up to the sender and
     * receiver.
     */
    public Messenger replyTo;

Message可以持有Messenger 引用,而我們知道構(gòu)造好了Messenger,就可以通過getBinder(xx)獲取關(guān)聯(lián)的IBinder 引用。

#Messenger.java
    public IBinder getBinder() {
        return mTarget.asBinder();
    }

IBinder有了剩下的就是想辦法把它傳給服務(wù)端。

客戶端傳遞IBinder給服務(wù)端
Message需要跨進(jìn)程傳遞,因此它的成員變量包括Messenger replyTo 都需要序列化及反序列化。

#Message.java
    public void writeToParcel(Parcel dest, int flags) {
        ...
        Messenger.writeMessengerOrNullToParcel(replyTo, dest);
        ...
    }

調(diào)用了Messenger里的靜態(tài)方法:

#Messenger.java
    public static void writeMessengerOrNullToParcel(Messenger messenger,
                                                    Parcel out) {
        //獲取messenger 關(guān)聯(lián)的Binder,寫入到序列化對象
        out.writeStrongBinder(messenger != null ? messenger.mTarget.asBinder()
                : null);
    }

至此,客戶端的IBinder引用就可以傳遞給服務(wù)端了。

服務(wù)端取出IBinder
服務(wù)端收到Message后反序列化成員變量如Messenger replyTo等。

#Message.java
    private void readFromParcel(Parcel source) {
        ...
        replyTo = Messenger.readMessengerOrNullFromParcel(source);
        ...
    }

類似的調(diào)用Messenger 靜態(tài)方法:

#Messenger.java
    public static Messenger readMessengerOrNullFromParcel(Parcel in) {
        //反序列化出IBinder
        IBinder b = in.readStrongBinder();
        //構(gòu)造出Messenger
        return b != null ? new Messenger(b) : null;
    }

最終,服務(wù)端收到了客戶端的IBinder,并構(gòu)造出Messenger,有了Messenger當(dāng)然可以給客戶端發(fā)消息了。

4、Message、AIDL、Messenger區(qū)別與聯(lián)系

Message
用來在線程間傳遞數(shù)據(jù),與Handler配合使用,本身支持序列化,可以跨進(jìn)程傳遞Message對象。
AIDL
用來簡化進(jìn)程間通信時客戶端、服務(wù)端代碼的編寫。
Messenger
在AIDL 的基礎(chǔ)上,進(jìn)一步封裝服務(wù)端暴露的接口,將服務(wù)端收到Message通過Handlder發(fā)送到目標(biāo)線程。

AIDL 與 Messenger 在進(jìn)程間通信區(qū)別:
使用AIDL優(yōu)點:

1、可以靈活的編寫服務(wù)端的接口,并且能夠自定義方法形參類型、數(shù)據(jù)流向、方法返回值。
2、服務(wù)端方法實現(xiàn)里可以開啟多線程處理數(shù)據(jù)。

使用AIDL 缺點:

1、需要編寫AIDL 文件定義服務(wù)端接口。
2、如果是自定義數(shù)據(jù)類型,還需要編寫對應(yīng)的AIDL 文件。

使用Messenger 優(yōu)點:

1、無需定義AIDL 文件,直接構(gòu)造Message發(fā)送。
2、快速實現(xiàn)雙向通信(嚴(yán)格上來說AIDL也能實現(xiàn),只是Messenger封裝好了IBinder的傳遞)

使用Messenger缺點:

參考上方,AIDL 優(yōu)點即是Messenger缺點。

適用場合

如果是簡單的通信,并且服務(wù)端是單線程順序處理客戶端的消息,建議使用Messenger。

本文基于Android 10.0。

您若喜歡,請點贊、關(guān)注,您的鼓勵是我前進(jìn)的動力

持續(xù)更新中,和我一起步步為營學(xué)習(xí)Android

1、Android各種Context的前世今生
2、Android DecorView 必知必會
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分發(fā)全套服務(wù)
6、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8、Android事件驅(qū)動Handler-Message-Looper解析
9、Android 鍵盤一招搞定
10、Android 各種坐標(biāo)徹底明了
11、Android Activity/Window/View 的background
12、Android Activity創(chuàng)建到View的顯示過
13、Android IPC 系列
14、Android 存儲系列
15、Java 并發(fā)系列不再疑惑
16、Java 線程池系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容