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 原理換個(gè)姿勢(shì)就頓悟了(圖文版)

前面從源碼+Demo角度詳盡分析了AIDL做修,可能會(huì)覺得AIDL文件的編寫略微有些麻煩模狭,本篇文章將分析AIDL 簡(jiǎn)化版 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 文件就可以實(shí)現(xiàn)進(jìn)程間通信恳邀,很是方便懦冰。

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

上面的Demo是客戶端往服務(wù)端發(fā)送一條消息谣沸,那么如果服務(wù)端想給客戶端發(fā)送回復(fù)消息該怎么實(shí)現(xiàn)呢刷钢?
以服務(wù)端收到客戶端傳遞過來的id后,查找出id對(duì)應(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ā)送給客戶端举农。

編寫客戶端

客戶端代碼也僅僅需要小小的改動(dòng):

    //接收服務(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ù)端消息時(shí)帶上自己的Messenger以便服務(wù)端拿到該Messenger給客戶端發(fā)送信息。同時(shí)棱貌,需要重寫Handler的handleMessage(xx)接收服務(wù)端的信息玖媚。這與服務(wù)端的實(shí)現(xiàn)是一致的,相當(dāng)于雙方都有Messenger婚脱。

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

3涡贱、Messenger 底層原理

從發(fā)送消息開始

為什么Messenger 能夠如此簡(jiǎn)單地完成了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,實(shí)現(xiàn)了唯一的方法:send(Message msg)垄分,該方法的的形參為:Message宛篇。
由此薄湿,我們輕易得出結(jié)論:

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

找到IMessenger 對(duì)應(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
其實(shí)就是之前說的AIDL蚕泽,Message本身支持序列化,加 "in" 標(biāo)記表示數(shù)據(jù)只能從客戶端流向服務(wù)端桥嗤。

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

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

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

image.png

與普通的AIDL 相比篓像,Messenger其實(shí)就是封裝了服務(wù)端的接口及其方法动知,與此同時(shí)也封裝了客戶端調(diào)用服務(wù)端的方法≡北纾客戶端將要傳遞的消息封裝在Message里盒粮,通過Messenger傳遞出去,服務(wù)端收到后在Handler里處理該Message奠滑。

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

從IMessenger.aidl定義可知丹皱,通過send(xx)方法只能是由客戶端往服務(wù)端發(fā)送消息妒穴,并且沒有返回值。因此想通過方法的形參或返回值攜帶服務(wù)端的數(shù)據(jù)是不現(xiàn)實(shí)的了摊崭。
客戶端能給服務(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确虱,寫入到序列化對(duì)象
        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對(duì)象计螺。
AIDL
用來簡(jiǎn)化進(jìn)程間通信時(shí)客戶端、服務(wù)端代碼的編寫登馒。
Messenger
在AIDL 的基礎(chǔ)上,進(jìn)一步封裝服務(wù)端暴露的接口肺孤,將服務(wù)端收到Message通過Handlder發(fā)送到目標(biāo)線程济欢。

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

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

使用AIDL 缺點(diǎn):

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

使用Messenger 優(yōu)點(diǎn):

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

使用Messenger缺點(diǎn):

參考上方践磅,AIDL 優(yōu)點(diǎn)即是Messenger缺點(diǎn)灸异。

適用場(chǎng)合

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

本文基于Android 10.0疟暖。

您若喜歡田柔,請(qǐng)點(diǎn)贊、關(guān)注欣舵,您的鼓勵(lì)是我前進(jìn)的動(dòng)力

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

1、Android各種Context的前世今生
2准验、Android DecorView 必知必會(huì)
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ū)動(dòng)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 存儲(chǔ)系列
15、Java 并發(fā)系列不再疑惑
16炎滞、Java 線程池系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末册赛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子牡属,更是在濱河造成了極大的恐慌,老刑警劉巖逮栅,帶你破解...
    沈念sama閱讀 223,207評(píng)論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件措伐,死亡現(xiàn)場(chǎng)離奇詭異军俊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)粪躬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來提前,“玉大人泳唠,你說我怎么就攤上這事⊥夭福” “怎么了?”我有些...
    開封第一講書人閱讀 170,031評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵窥摄,是天一觀的道長础淤。 經(jīng)常有香客問我,道長币砂,這世上最難降的妖魔是什么玻侥? 我笑而不...
    開封第一講書人閱讀 60,334評(píng)論 1 300
  • 正文 為了忘掉前任凑兰,我火速辦了婚禮,結(jié)果婚禮上波岛,老公的妹妹穿的比我還像新娘。我一直安慰自己则拷,他們只是感情好曹鸠,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,322評(píng)論 6 398
  • 文/花漫 我一把揭開白布彻桃。 她就那樣靜靜地躺著,像睡著了一般浑吟。 火紅的嫁衣襯著肌膚如雪耗溜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,895評(píng)論 1 314
  • 那天抖拴,我揣著相機(jī)與錄音,去河邊找鬼候衍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蛉鹿,可吹牛的內(nèi)容都是我干的往湿。 我是一名探鬼主播,決...
    沈念sama閱讀 41,300評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼他膳,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼棕孙!你這毒婦竟也來了些膨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,264評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤欧漱,失蹤者是張志新(化名)和其女友劉穎葬燎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窑邦,經(jīng)...
    沈念sama閱讀 46,784評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡壕探,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,870評(píng)論 3 343
  • 正文 我和宋清朗相戀三年李请,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片较幌。...
    茶點(diǎn)故事閱讀 40,989評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡白翻,死狀恐怖绢片,靈堂內(nèi)的尸體忽然破棺而出底循,到底是詐尸還是另有隱情,我是刑警寧澤熙涤,帶...
    沈念sama閱讀 36,649評(píng)論 5 351
  • 正文 年R本政府宣布灭袁,位于F島的核電站窗看,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏显沈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,331評(píng)論 3 336
  • 文/蒙蒙 一涤浇、第九天 我趴在偏房一處隱蔽的房頂上張望魔慷。 院中可真熱鬧,春花似錦蜻展、人聲如沸邀摆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽例获。三九已至,卻和暖如春榨汤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背件余。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評(píng)論 1 275
  • 我被黑心中介騙來泰國打工啼器, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人端壳。 一個(gè)月前我還...
    沈念sama閱讀 49,452評(píng)論 3 379
  • 正文 我出身青樓损谦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親照捡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,995評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容