Android進程間通信 Messenger詳解

1. 概念

Messenger,即進程間通信的信使.它是基于Message的進程間通信,我們可以像在線程間利用Handler.send(Message)一樣.

Messenger是一種輕量級的IPC方案,它的底層實現(xiàn)其實就是AIDL.跨進程通信使用Messenger時,Messenger會將所有服務調用加入隊列,然后服務端那邊一次處理一個調用,不會存在同時調用的情況.而AIDL則可能是多個調用同時執(zhí)行,必須處理多線程問題.

對于大多數(shù)應用,跨進程通信無需一對多,也就是無需執(zhí)行多線程處理,此時使用Messenger更適合.

2. 使用

2.1 大致流程

  1. 服務端實現(xiàn)一個Handler,由其接收來自客戶端的每個調用的回調
  2. 服務端使用Handler來創(chuàng)建Messenger對象
  3. Messenger創(chuàng)建一個IBinder,服務端通過onBind()將其返回給客戶端
  4. 客戶端使用IBinder將Messenger實例化,然后再用起將Message對象發(fā)送給服務端
  5. 服務端在其Handler#handleMessage()中,接收每個Message

2.2 案例

2.2.1 服務端

首先需要在服務端創(chuàng)建一個Handler用于接收消息,然后將此Handler傳遞給Messenger,并在onBind中將該Messenger的底層binder返回回去.

//這里服務端Service是運行在單獨的進程中的 android:process=":other"
class MessengerService : Service() {

    private lateinit var mMessenger: Messenger

    override fun onBind(intent: Intent): IBinder {
        log(TAG, "onBind~")
        //傳入Handler實例化Messenger
        mMessenger = Messenger(IncomingHandler(this))
        //將Messenger中的binder返回給客戶端,讓它可以遠程調用
        return mMessenger.binder
    }

    //處理客戶端傳遞過來的消息(Message)  并根據(jù)what決定下一步操作
    internal class IncomingHandler(
        context: Context,
        private val applicationContext: Context = context.applicationContext
    ) : Handler(
        Looper.getMainLooper()
    ) {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO -> {
                    Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show()
                    log(TAG, "hello!")
                }
                else -> super.handleMessage(msg)
            }
        }
    }
}

2.2.2 客戶端

客戶端進程中,首先是需要綁定遠程Service.綁定完成之后,在onServiceConnected()中拿到遠程Service返回的IBinder對象,用此IBinder對象實例化客戶端這邊的Messenger.有了這個Messenger,就可以通過這個Messenger往服務端發(fā)送消息了.示例代碼如下:

class MessengerActivity : TitleBarActivity() {

    /** 與服務端進行溝通的Messenger */
    private var mService: Messenger? = null

    /** 是否已bindService */
    private var bound: Boolean = false

    private val mServiceConnection = object : ServiceConnection {

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            mService = Messenger(service)
            bound = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            mService = null
            bound = false
        }
    }

    override fun getThisTitle(): CharSequence {
        return "Messenger"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_messenger)

        btnConnect.setOnClickListener {
            connectService()
        }
        btnSayHello.setOnClickListener {
            sayHello()
        }
    }

    private fun sayHello() {
        if (!bound) {
            return
        }
        //創(chuàng)建,并且發(fā)送一個message給服務端   Message中what指定為MSG_SAY_HELLO
        val message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
        try {
            mService?.send(message)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }

    private fun connectService() {
        Intent().apply {
            action = "com.xfhy.messenger.Server.Action"
            setPackage("com.xfhy.allinone")
        }.also { intent ->
            bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        if (bound) {
            unbindService(mServiceConnection)
            bound = false
        }
    }

}

通過示例代碼我們知道客戶端通過Messenger與服務端進行通信時,必須將數(shù)據(jù)放入Message中,Messenger和Message都實現(xiàn)了Parcelable接口,因此是可以跨進程傳輸?shù)?Message只能通過what、arg1、arg2眉睹、Bundle以及replyTo來承載需要傳遞的數(shù)據(jù),如果需要傳遞Serializable或者Parcelable的對象則可以放進Bundle里面進行傳遞,Bundle還支持其他大量的數(shù)據(jù)類型.

2.2.3 服務端向客戶端發(fā)送消息

有時候我們需要客戶端能響應服務端發(fā)送的消息,此時我們只需要在上面的示例的基礎上簡單修改即可.

服務端這邊每次收到消息,都回復一條消息給客戶端,方便測試

internal class IncomingHandler : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO -> {
                    log(TAG, "hello!")
                    //客戶端的Messenger就是放在Message的replyTo中的
                    replyToClient(msg, "I have received your message and will reply to you later")
                }
                MSG_TRANSFER_SERIALIZABLE -> log(TAG, "傳遞過來的對象:  ${msg.data?.get("person")}")
                else -> super.handleMessage(msg)
            }
        }

        private fun replyToClient(msg: Message, replyText: String) {
            val clientMessenger = msg.replyTo
            val replyMessage = Message.obtain(null, MSG_FROM_SERVICE)
            replyMessage.data = Bundle().apply {
                putString("reply", replyText)
            }
            try {
                clientMessenger?.send(replyMessage)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }
    }

而客戶端這邊需要做出響應,則還需在客戶端創(chuàng)建一個Messenger,并為其創(chuàng)建一個Handler用于接收服務端傳遞過來的消息.在客戶端發(fā)送消息時,需要將Message#replyTo設置為客戶端的Messenger. 服務端拿到這個Messanger才能回復消息.


/** 客戶端這邊的Messenger */
private var mClientMessenger = Messenger(IncomingHandler())

class IncomingHandler : Handler(Looper.getMainLooper()) {
    override fun handleMessage(msg: Message) {
        when (msg.what) {
            MSG_FROM_SERVICE -> {
                log(TAG, "Received from service: ${msg.data?.getString("reply")}")
            }
            else -> super.handleMessage(msg)
        }
    }
}

private fun sayHello() {
    if (!bound) {
        return
    }
    //創(chuàng)建,并且發(fā)送一個message給服務端   Message中what指定為MSG_SAY_HELLO
    val message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
    //注意 這里是新增的
    message.replyTo = mClientMessenger
    message.data = Bundle().apply {
        putSerializable("person", SerializablePerson("張三"))
    }
    try {
        mService?.send(message)
    } catch (e: RemoteException) {
        e.printStackTrace()
    }
}

服務端調用sayHello()之后,輸出日志如下:

2020-12-31 11:59:40.420 29702-29702/com.xfhy.allinone D/xfhy_messenger: hello!
2020-12-31 11:59:40.421 29649-29649/com.xfhy.allinone D/xfhy_messenger: Received from service: I have received your message and will reply to you later

日志里面明顯看到是2個進程,所以現(xiàn)在是達到是雙向通信的目的.Messenger的使用大概就是這些了,下面是Messenger的大致工作原理圖

//todo xfhy 插圖 Messenger的工作原理 Android開發(fā)藝術探索(P93)

3. 原理

3.1 客戶端->服務端通信

服務端

當客戶端到服務端單向通信時,我們來看一下大致的原理.首先是服務端這邊在onBind方法中返回了Messenger的binder對象

override fun onBind(intent: Intent): IBinder {
    //傳入Handler實例化Messenger
    mMessenger = Messenger(IncomingHandler())
    //將Messenger中的binder返回給客戶端,讓它可以遠程調用
    return mMessenger.binder
}

我們看下Messenger里面的binder是什么:

private final IMessenger mTarget;

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

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

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

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

從Messenger的構造方法(IMessenger.Stub.asInterface())可以看出它底層應該是使用的AIDL搞的.getBinder()其實是將調用了mTarget.asBinder(),而mTarget是我們傳進來的Handler里面拿出來的,跟進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);
    }
}

原來IMessenger是Handler的內部類MessengerImpl,它只有一個send方法.結合上面Messenger的源碼,我們發(fā)現(xiàn)調用Messenger的send方法其實就是調用這里的MessengerImpl的send方法,然后這個send里面將Message轉發(fā)給Handler#sendMessage(),最后也就是去了Handler#handleMessage()里面接收到這個Message.

MessengerImpl是繼承自IMessenger.Stub,這一看就感覺是AIDL文件自動生成的嘛,easy.大膽猜測一下對應的aidl文件應該是IMessenger.aidl,我們去源碼里面找IMessenger.aidl,果然在frameworks/base/core/java/android/os/IMessenger.aidl這個位置找到了它.內容如下:

package android.os;

import android.os.Message;

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

根據(jù)aidl文件,它自動生成的IMessenger.java應該長下面這樣:

package android.os;

public interface IMessenger extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements IMessenger {
        private static final java.lang.String DESCRIPTOR = "android.os.IMessenger";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an android.os.IMessenger interface,
         * generating a proxy if needed.
         */
        public static IMessenger asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof IMessenger))) {
                return ((IMessenger) iin);
            }
            return new IMessenger.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            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;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements IMessenger {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public void send(android.os.Message msg) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((msg != null)) {
                        _data.writeInt(1);
                        msg.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_send, _data, null, android.os.IBinder.FLAG_ONEWAY);
                } finally {
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_send = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public void send(android.os.Message msg) throws android.os.RemoteException;
}

這就好辦了,這就明擺著說明Messenger底層是基于AIDL實現(xiàn)的.服務端這邊這條線: Service#onBind()->mMessenger.getBinder()->Handler#getIMessenger()->MessengerImpl(IMessenger.Stub),其實就是和我們使用AIDL一樣將IXXX.Stub的子類通過onBind返回回去,客戶端綁定的時候好拿到binder對象.接收客戶端的消息時,是通過MessengerImpl轉發(fā)給Handler來完成的,服務端這邊定義的那個Handler就可以在handleMessage()中處理跨進程傳遞過來的Message,從而理解客戶端想要調用什么服務,然后執(zhí)行相應的邏輯.

客戶端

再看客戶端這邊,在onServiceConnected()時,將服務端返回的IBinder對象放進Messenger里.

//MessengerActivity.kt
private val mServiceConnection = object : ServiceConnection {

    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        mService = Messenger(service)
        bound = true
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        mService = null
        bound = false
    }
}

//Messenger.java
public void send(Message message) throws RemoteException {
    mTarget.send(message);
}
public Messenger(IBinder target) {
            //這里asInterface 出來的其實就是 IMessenger.Stub.Proxy對象
    mTarget = IMessenger.Stub.asInterface(target);
}

IBinder對象放進Messenger原來就是熟悉的操作IMessenger.Stub.asInterface(),簡單.然后客戶端這邊給服務端發(fā)消息的時候通過構建出來的Messenger調用send方法發(fā)送,而Messenger內部send的實現(xiàn)其實就是調用IMessenger.Stub.Proxy(跨進程了)的send方法.調用之后,服務端那邊在Handler的handleMessage里收到這條消息(Message),從而實現(xiàn)了跨進程通信.

3.2 服務端->客戶端通信

客戶端與服務端的通信與我們用AIDL的方式實現(xiàn)幾乎一致,完全可以我們自己實現(xiàn),Messenger只是幫我們封裝好了而已.下面來看一下服務端與客戶端的通信.

服務端需要與客戶端通信的話,需要客戶端在send消息的時候將客戶端Messenger存放在消息的replyTo中.

private fun sayHello() {
    val message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
    //將客戶方的Messenger放replyTo里
    message.replyTo = mClientMessenger
    mService?.send(message)
}

將消息發(fā)送到服務端時,因為是跨進程,所以肯定需要用到序列化與反序列化Message.看下Message的反序列化代碼:

private void readFromParcel(Parcel source) {
    what = source.readInt();
    arg1 = source.readInt();
    arg2 = source.readInt();
    if (source.readInt() != 0) {
        obj = source.readParcelable(getClass().getClassLoader());
    }
    when = source.readLong();
    data = source.readBundle();
    replyTo = Messenger.readMessengerOrNullFromParcel(source);
    sendingUid = source.readInt();
    workSourceUid = source.readInt();
}

主要是看一下replyTo是怎么反序列化的,它調用了Messenger的readMessengerOrNullFromParcel方法:

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

public static Messenger readMessengerOrNullFromParcel(Parcel in) {
    IBinder b = in.readStrongBinder();
    return b != null ? new Messenger(b) : null;
}

writeMessengerOrNullToParcel中將客戶端的messenger.mTarget.asBinder()進行了寫入,然后在readMessengerOrNullFromParcel時進行了恢復,而messenger.mTarget就是上面分析的MessengerImpl,asBinder()是其父類IMessenger.Stub里面的一個方法:

@Override
public android.os.IBinder asBinder() {
    return this;
}

就是將自身返回出去.也就是說,服務端反序列化出來的replyTo對應Messenger中的IBinder其實就是客戶端的MessengerImpl對象.于是服務端拿到這個Messenger就可以發(fā)送消息,通過這個IBinder對象跨進程通信,客戶端就接收到消息了.

4. 小結

跨進程通信時,Messenger比AIDL更常用(滿足使用條件的時候),因為用起來比較方便,而且官方也更推薦.在使用Messenger的同時,我們需要了解其原理:

  • 客戶端與服務端單向通信時,利用的是AIDL接口的原理,和我們平時寫的方式一樣
  • 服務端與客戶端通信時,利用客戶端發(fā)送消息時Message對象需要序列化與反序列化,將客戶端的binder對象封裝在里面的replyTo字段中,服務端那邊反序列化時再將其取出組裝成Messenger.有了這個客戶端的binder對象,當然也就能夠與客戶端進行跨進程通信了.

資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末纯命,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件订雾,死亡現(xiàn)場離奇詭異,居然都是意外死亡矛洞,警方通過查閱死者的電腦和手機洼哎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沼本,“玉大人噩峦,你說我怎么就攤上這事〕檎祝” “怎么了识补?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辫红。 經(jīng)常有香客問我凭涂,道長,這世上最難降的妖魔是什么贴妻? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任切油,我火速辦了婚禮,結果婚禮上名惩,老公的妹妹穿的比我還像新娘澎胡。我一直安慰自己,他們只是感情好绢片,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布滤馍。 她就那樣靜靜地躺著,像睡著了一般底循。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上槐瑞,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天熙涤,我揣著相機與錄音,去河邊找鬼困檩。 笑死祠挫,一個胖子當著我的面吹牛,可吹牛的內容都是我干的悼沿。 我是一名探鬼主播等舔,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼糟趾!你這毒婦竟也來了慌植?” 一聲冷哼從身側響起甚牲,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝶柿,沒想到半個月后丈钙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡交汤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年雏赦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芙扎。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡星岗,死狀恐怖,靈堂內的尸體忽然破棺而出戒洼,到底是詐尸還是另有隱情俏橘,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布施逾,位于F島的核電站敷矫,受9級特大地震影響,放射性物質發(fā)生泄漏汉额。R本人自食惡果不足惜曹仗,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蠕搜。 院中可真熱鬧怎茫,春花似錦、人聲如沸妓灌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虫埂。三九已至祥山,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掉伏,已是汗流浹背缝呕。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留斧散,地道東北人供常。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像鸡捐,于是被迫代替她去往敵國和親栈暇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內容