IPC之Messenger

今天我們來聊一聊Android中另外一種IPC機(jī)制-Messenger路星,可以理解成信使并蝗,通過它可以在不同的進(jìn)程中傳遞Message對(duì)象卧檐,其實(shí)它的底層實(shí)現(xiàn)還是AIDL克懊,為什么這么說忱辅?先賣個(gè)關(guān)子,繼續(xù)往后看就知道了谭溉。我們先看下Messenger怎么使用墙懂。還是通過一個(gè)栗子來說明,客戶端向服務(wù)端發(fā)起請(qǐng)求扮念,服務(wù)端返回一個(gè)百度的url损搬,客戶端再進(jìn)行加載,就是這么簡(jiǎn)單柜与。

1.png
2.png
3.png

當(dāng)然在這之前有點(diǎn)AIDL的知識(shí)看起來會(huì)比較輕松巧勤,可以參考我前面的AIDL的博客:http://www.reibang.com/p/4ebd4783d3d9

Messenger基本使用

當(dāng)然還是分成服務(wù)端進(jìn)程和服務(wù)端進(jìn)程

服務(wù)端進(jìn)程

首先創(chuàng)建一個(gè)Service來處理客戶端的連接請(qǐng)求,創(chuàng)建一個(gè)Messenger弄匕,在onBind中返回這個(gè)Messenger對(duì)象底層的Binder給客戶端即可颅悉。
通過Handler來創(chuàng)建Messenger,在收到客戶端發(fā)過來的請(qǐng)求時(shí)會(huì)回送一個(gè)msg:clientMessenger.send(response);

private Messenger messenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            try {
                Thread.sleep(5 * 1000); //模擬耗時(shí)
            } catch (Exception e) {
                e.printStackTrace();
            }

            Message response = Message.obtain();
            Bundle bundle = new Bundle();
            bundle.putString("body", REMOTE_URL);
            response.setData(bundle);

            Messenger clientMessenger = msg.replyTo;
            try {
                clientMessenger.send(response);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    });

客戶端需要在清單中進(jìn)行注冊(cè),在另外一個(gè)juexingzhe進(jìn)程

<service android:name=".MessengerService" android:process=":juexingzhe"/>

在客戶端綁定服務(wù)的時(shí)候返回這個(gè)Messenger對(duì)象迁匠。

@Override
public IBinder onBind(Intent intent) {
     return messenger.getBinder();
}

客戶端進(jìn)程

客戶端首先發(fā)起綁定服務(wù)的請(qǐng)求剩瓶,在服務(wù)綁定成功后,通過服務(wù)端返回的IBinder對(duì)象就可以創(chuàng)建一個(gè)Messenger mSender城丧,通過它就可以向服務(wù)端發(fā)送信息了延曙。

bindService(intent, serviceConnection, BIND_AUTO_CREATE);

ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mSender = new Messenger(iBinder);
            sendMessage();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
        }
    };

客戶端還需要?jiǎng)?chuàng)建一個(gè)Handler,通過Handler來創(chuàng)建一個(gè)Messenger亡哄,用來接收服務(wù)端發(fā)送的消息:

private Messenger mReceiver = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.i(TAG, "msg is received from service");

            Bundle data = msg.getData();
            if (null != data) {
                mButton.setVisibility(View.GONE);
                String body = data.getString("body");
                mWebView.loadUrl(body);
            }
        }
    });

Messenger源碼分析

使用還是比較簡(jiǎn)單的枝缔,基本和Handler的用法比較類似。我們下面來扒一扒源碼蚊惯。

1.構(gòu)造

我們?cè)谏厦娴睦又杏玫搅藘蓚€(gè)Messenger的構(gòu)造函數(shù):
一個(gè)是在服務(wù)綁定成功后mSender = new Messenger(iBinder);
另外一個(gè)是在創(chuàng)建的時(shí)候private Messenger mReceiver = new Messenger(new Handler())
我們先看下通過Binder的構(gòu)造函數(shù)愿卸,可以看見濃濃的AIDL的味道吧拐辽。在夸進(jìn)程通信的情況下,通過asInterface會(huì)將返回服務(wù)端Binder的代理Proxy擦酌。

    /**
     * Create a Messenger from a raw IBinder, which had previously been retrieved with {@link #getBinder}.
     * 
     * @param target The IBinder this Messenger should communicate with.
     */
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }

我們?cè)倏聪峦ㄟ^Handler的構(gòu)造函數(shù)俱诸,只有一行代碼,就是獲取mTarget對(duì)象赊舶。

    /**
     * Create a new Messenger pointing to the given Handler.  Any Message
     * objects sent through this Messenger will appear in the Handler as if
     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
     * been called directly.
     * 
     * @param target The Handler that will receive sent messages.
     */
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

上面兩個(gè)構(gòu)造函數(shù)無一例外都是獲得mTarget對(duì)象睁搭,它是一個(gè)IMessenger對(duì)象
private final IMessenger mTarget;

IMessenger就是Android系統(tǒng)幫我們定義在android.os包下的一個(gè)AIDL接口,編譯后會(huì)生成IMessenger.java文件。

package android.os;

import android.os.Message;

oneway interface IMessenger{
void send(in Message msg);
}

因此mTarget有兩種情況,一種是通過Handler或的IMessenger:

    mTarget = Handler.getIMessenger();
    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();
            Handler.this.sendMessage(msg);
        }
    }

一種是通過IBinder轉(zhuǎn)化得到的IMessenger:

public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

public static android.os.IMessenger asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin =obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof android.os.IMessenger))) {
            return ((android.os.IMessenger) iin);
        }
        return new android.os.IMessenger.Stub.Proxy(obj);
}

2.發(fā)送消息

Messenger發(fā)送消息的源碼,都是通過mTarget進(jìn)行發(fā)送笼平。

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

發(fā)送消息要分成兩種情況:

給服務(wù)端發(fā)送消息

一種是客戶端給服務(wù)端發(fā)送消息(這種情況的Messenger是客戶端通過服務(wù)端返回的IBinder進(jìn)行構(gòu)造)园骆,

    private void sendMessage() {
        if (null != mSender) {
            Message msg = Message.obtain();
            msg.replyTo = mReceiver;
            mSender.send(msg);
        }
    }

這種情況下會(huì)的調(diào)用流程會(huì)是這樣的(注意前方高能,可能會(huì)引起輕微不適):
Proxy.send()--->mRemote.transact(Stub.TRANSACTION_send, _data, null, android.os.IBinder.FLAG_ONEWAY)--->Stub.onTransact--->this.send(_arg0)(這里面的arg0就是msg)--->Handler中MessengerImpl.send()--->handler.sendMessage--->服務(wù)端handleMessage
上面這個(gè)過程會(huì)比較繞:
1.因?yàn)楹头?wù)端通信是跨進(jìn)程寓调,所以首先會(huì)在Proxy中序列化msg參數(shù)(包括bundle和Messenger)锌唾,然后會(huì)調(diào)用到服務(wù)端進(jìn)程的onTransact;

2.在服務(wù)端進(jìn)程中會(huì)調(diào)用this.send夺英,那這邊的this指的是哪個(gè)對(duì)象晌涕?還記得服務(wù)端Messenger是怎么創(chuàng)建的嗎?也是通過Handler創(chuàng)建的痛悯,因此this.send就會(huì)調(diào)用服務(wù)端Messenger的Handler中內(nèi)部類MessengerImpl中的send方法余黎;

3.MessengerImpl中的send方法會(huì)調(diào)用Handler的sendMessage方法

Handler.this.sendMessage(msg);

明顯這里的Handler就是服務(wù)端構(gòu)建Messenger我們傳進(jìn)去的handler,因此msg也就被handleMessage處理载萌。

給客戶端發(fā)送消息

這種情況下會(huì)拿到客戶端發(fā)送過來的Messenger惧财,那么為什么服務(wù)端可以直接拿到Messenger就給客戶端發(fā)送消息,不是應(yīng)該先拿到一個(gè)客戶端的Binder扭仁,然后再轉(zhuǎn)化balabalabalabala嗎垮衷?

Messenger clientMessenger = msg.replyTo;
clientMessenger.send(response);

其實(shí)Android也是這樣做的,我們?nèi)ヒ惶骄烤梗ㄗⅲ合路皆创a是我經(jīng)過刪減的):
在客戶端send消息給服務(wù)端時(shí)會(huì)乖坠,在Proxy的send方法中會(huì)將msg和clientMessenger一起會(huì)調(diào)用Message msg.writeToParcel(_data, 0)寫到data中搀突;

public void send(android.os.Message msg) throws android.os.RemoteException {
     android.os.Parcel _data = android.os.Parcel.obtain();
     _data.writeInterfaceToken(DESCRIPTOR);
     if ((msg != null)) {
               _data.writeInt(1);
               msg.writeToParcel(_data, 0);
     }
}

在Message的writeToParcel方法中會(huì)調(diào)用writeMessengerOrNullToParcel,然后在方法中會(huì)在data中writeStrongBinder瓤帚,會(huì)將clientMessenger的Binder存儲(chǔ)起來描姚,是不是恍然大悟?戈次?轩勘?

Messenger.writeMessengerOrNullToParcel(replyTo, dest);
dest.writeInt(sendingUid);
public static void writeMessengerOrNullToParcel(Messenger messenger, Parcel out) {
        out.writeStrongBinder(messenger != null ? messenger.mTarget.asBinder(): null);
}

接下來就順利成章了,服務(wù)端會(huì)在onTransact中調(diào)用CREATOR怯邪;

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_send: {
                    data.enforceInterface(DESCRIPTOR);
                    android.os.Message _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = android.os.Message.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.send(_arg0);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
}

public static final Parcelable.Creator<Message> CREATOR
            = new Parcelable.Creator<Message>() {
        public Message createFromParcel(Parcel source) {
            Message msg = Message.obtain();
            msg.readFromParcel(source);
            return msg;
        }
        
        public Message[] newArray(int size) {
            return new Message[size];
        }
};

然后會(huì)調(diào)用Message的readFromParcel,接著就是readMessengerOrNullFromParcel绊寻,在該方法中會(huì)得到clientMessenger

private void readFromParcel(Parcel source) {
        replyTo = Messenger.readMessengerOrNullFromParcel(source);
        sendingUid = source.readInt();
}
public static Messenger readMessengerOrNullFromParcel(Parcel in) {
        IBinder b = in.readStrongBinder();
        return b != null ? new Messenger(b) : null;
}

可以看到服務(wù)端發(fā)送消息也是通過Binder,之后過程就與客戶端發(fā)送消息服務(wù)端一樣了,就不做分析了澄步。

其他

因?yàn)镸essenger在進(jìn)程內(nèi)是通過Handler進(jìn)行消息發(fā)送和處理的冰蘑,所以只能串行發(fā)送消息和處理消息;
Messenger跨進(jìn)程調(diào)用方法是非阻塞的村缸,這個(gè)在我們栗子中可以體現(xiàn)祠肥,客戶端發(fā)送消息后會(huì)馬上打印后面的log,一段時(shí)間后服務(wù)端才返回消息梯皿。還記得IMessenger.aidl這個(gè)文件仇箱?有oneway這個(gè)關(guān)鍵字,這個(gè)就是非阻塞的原因东羹,具體可以看后面參考鏈接中的博客剂桥。

private void sendMessage() {
            mSender.send(msg);
            Log.i(TAG, "msg is right now send");
}

private Messenger mReceiver = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.i(TAG, "msg is received from service");
        }
});
4.png

Messenger其實(shí)就是Android幫我們封裝好的AIDL,用法和Handler類似属提,比較方便权逗,文中的代碼可以在Github中看到:https://github.com/juexingzhe/MessengerSample

好了,我們今天的Messenger之旅就到此結(jié)束了冤议。謝謝斟薇!

參考鏈接:
http://www.reibang.com/p/c6a73b9a14ce
http://www.reibang.com/p/4ebd4783d3d9

歡迎關(guān)注公眾號(hào):JueCode

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市求类,隨后出現(xiàn)的幾起案子奔垦,更是在濱河造成了極大的恐慌,老刑警劉巖尸疆,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惶岭,居然都是意外死亡寿弱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門按灶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來症革,“玉大人,你說我怎么就攤上這事鸯旁≡朊” “怎么了?”我有些...
    開封第一講書人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵铺罢,是天一觀的道長艇挨。 經(jīng)常有香客問我,道長韭赘,這世上最難降的妖魔是什么缩滨? 我笑而不...
    開封第一講書人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上脉漏,老公的妹妹穿的比我還像新娘苞冯。我一直安慰自己,他們只是感情好侧巨,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開白布舅锄。 她就那樣靜靜地躺著,像睡著了一般司忱。 火紅的嫁衣襯著肌膚如雪巧娱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,874評(píng)論 1 314
  • 那天烘贴,我揣著相機(jī)與錄音禁添,去河邊找鬼。 笑死桨踪,一個(gè)胖子當(dāng)著我的面吹牛老翘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锻离,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼铺峭,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了汽纠?” 一聲冷哼從身側(cè)響起卫键,我...
    開封第一講書人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虱朵,沒想到半個(gè)月后莉炉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碴犬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年絮宁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片服协。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绍昂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出偿荷,到底是詐尸還是另有隱情窘游,我是刑警寧澤,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布跳纳,位于F島的核電站忍饰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏棒旗。R本人自食惡果不足惜喘批,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一撩荣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧饶深,春花似錦餐曹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至俱两,卻和暖如春饱狂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宪彩。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來泰國打工休讳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尿孔。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓俊柔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親活合。 傳聞我的和親對(duì)象是個(gè)殘疾皇子雏婶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361

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