IPC之Messenger

今天我们来聊一聊Android中另外一种IPC机制-Messenger,可以理解成信使,通过它可以在不同的进程中传递Message对象,其实它的底层实现还是AIDL,为什么这么说?先卖个关子,继续往后看就知道了。我们先看下Messenger怎么使用。还是通过一个栗子来说明,客户端向服务端发起请求,服务端返回一个百度的url,客户端再进行加载,就是这么简单。

1.png
2.png
3.png

当然在这之前有点AIDL的知识看起来会比较轻松,可以参考我前面的AIDL的博客:http://www.jianshu.com/p/4ebd4783d3d9

Messenger基本使用

当然还是分成服务端进程和服务端进程

服务端进程

首先创建一个Service来处理客户端的连接请求,创建一个Messenger,在onBind中返回这个Messenger对象底层的Binder给客户端即可。
通过Handler来创建Messenger,在收到客户端发过来的请求时会回送一个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); //模拟耗时
            } 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();
            }
        }
    });

客户端需要在清单中进行注册,在另外一个juexingzhe进程

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

在客户端绑定服务的时候返回这个Messenger对象。

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

客户端进程

客户端首先发起绑定服务的请求,在服务绑定成功后,通过服务端返回的IBinder对象就可以创建一个Messenger mSender,通过它就可以向服务端发送信息了。

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) {
        }
    };

客户端还需要创建一个Handler,通过Handler来创建一个Messenger,用来接收服务端发送的消息:

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源码分析

使用还是比较简单的,基本和Handler的用法比较类似。我们下面来扒一扒源码。

1.构造

我们在上面的例子中用到了两个Messenger的构造函数:
一个是在服务绑定成功后mSender = new Messenger(iBinder);
另外一个是在创建的时候private Messenger mReceiver = new Messenger(new Handler())
我们先看下通过Binder的构造函数,可以看见浓浓的AIDL的味道吧。在夸进程通信的情况下,通过asInterface会将返回服务端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);
    }

我们再看下通过Handler的构造函数,只有一行代码,就是获取mTarget对象。

    /**
     * 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();
    }

上面两个构造函数无一例外都是获得mTarget对象,它是一个IMessenger对象
private final IMessenger mTarget;

IMessenger就是Android系统帮我们定义在android.os包下的一个AIDL接口,编译后会生成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转化得到的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.发送消息

Messenger发送消息的源码,都是通过mTarget进行发送。

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

发送消息要分成两种情况:

给服务端发送消息

一种是客户端给服务端发送消息(这种情况的Messenger是客户端通过服务端返回的IBinder进行构造),

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

这种情况下会的调用流程会是这样的(注意前方高能,可能会引起轻微不适):
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--->服务端handleMessage
上面这个过程会比较绕:
1.因为和服务端通信是跨进程,所以首先会在Proxy中序列化msg参数(包括bundle和Messenger),然后会调用到服务端进程的onTransact;

2.在服务端进程中会调用this.send,那这边的this指的是哪个对象?还记得服务端Messenger是怎么创建的吗?也是通过Handler创建的,因此this.send就会调用服务端Messenger的Handler中内部类MessengerImpl中的send方法;

3.MessengerImpl中的send方法会调用Handler的sendMessage方法

Handler.this.sendMessage(msg);

明显这里的Handler就是服务端构建Messenger我们传进去的handler,因此msg也就被handleMessage处理。

给客户端发送消息

这种情况下会拿到客户端发送过来的Messenger,那么为什么服务端可以直接拿到Messenger就给客户端发送消息,不是应该先拿到一个客户端的Binder,然后再转化balabalabalabala吗?

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

其实Android也是这样做的,我们去一探究竟(注:下方源码是我经过删减的):
在客户端send消息给服务端时会,在Proxy的send方法中会将msg和clientMessenger一起会调用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方法中会调用writeMessengerOrNullToParcel,然后在方法中会在data中writeStrongBinder,会将clientMessenger的Binder存储起来,是不是恍然大悟???

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

接下来就顺利成章了,服务端会在onTransact中调用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];
        }
};

然后会调用Message的readFromParcel,接着就是readMessengerOrNullFromParcel,在该方法中会得到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;
}

可以看到服务端发送消息也是通过Binder,之后过程就与客户端发送消息服务端一样了,就不做分析了。

其他

因为Messenger在进程内是通过Handler进行消息发送和处理的,所以只能串行发送消息和处理消息;
Messenger跨进程调用方法是非阻塞的,这个在我们栗子中可以体现,客户端发送消息后会马上打印后面的log,一段时间后服务端才返回消息。还记得IMessenger.aidl这个文件?有oneway这个关键字,这个就是非阻塞的原因,具体可以看后面参考链接中的博客。

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其实就是Android帮我们封装好的AIDL,用法和Handler类似,比较方便,文中的代码可以在Github中看到:https://github.com/juexingzhe/MessengerSample

好了,我们今天的Messenger之旅就到此结束了。谢谢!

参考链接:
http://www.jianshu.com/p/c6a73b9a14ce
http://www.jianshu.com/p/4ebd4783d3d9

欢迎关注公众号:JueCode

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容