背景
在WifiManager.java
中,随处可见这样的方法调用实现:
代码路径:frameworks/base/wifi/java/android/net/wifi/WifiManager.java
public void connect(int networkId, ActionListener listener) {
...
getChannel().sendMessage(CONNECT_NETWORK, networkId, putListener(listener));
}
public void save(WifiConfiguration config, ActionListener listener) {
...
getChannel().sendMessage(SAVE_NETWORK, 0, putListener(listener), config);
}
继续追踪getChannel()
的实现不难发现,这个方法返回的是一个AsyncChanel
的实例对象:
private synchronized AsyncChannel getChannel() {
if (mAsyncChannel == null) {
Messenger messenger = getWifiServiceMessenger();
...
mAsyncChannel = new AsyncChannel();
mConnected = new CountDownLatch(1);
Handler handler = new ServiceHandler(mLooper);
mAsyncChannel.connect(mContext, handler, messenger);
try {
mConnected.await();
} catch (InterruptedException e) {
Log.e(TAG, "interrupted wait at init");
}
}
return mAsyncChannel;
}
虽然可以进一步快速定位到AsyncChannel.sendMessage()
的接收端是getWifiServiceMessenger()
返回的Messenger
实例对象,并通过Messenger.send()
方法实现跨进程调用,从而由WifiServiceImpl.ClientHandler
接收并处理消息;
但是由于有如下疑问:
Handler
本身是不可跨进程的,因此AsyncChannel
的跨进程是如何实现的;- 为何要使用
AsyncChannel
,而不是直接使用AIDL
调用?
因此决定好好理一理这个Android Framework的Internal工具类;
整体介绍
根据类声明处的注释可知,AsyncChannel可用于实现如下四种通信之一:
- 进程内两个线程Handler的单向通信;
- 进程内两个线程Handler的双向通信;
- 进程间两个线程Handler的单向通信;
- 进程间两个线程Handler的双向通信;
同时,这4种通信方式还可以以同步与异步两种连接、通信方式实现;
所以严格意义来说,一共是8种通信方式;
但是这里的同步与异步差别不大,因此只会在需要区别的地方提出,其余部分就一概而论了;
进程内和进程间很好理解,事实上,无论是进程内,还是进程间的两个Handler
,AsyncChannel
的通信都是借助实现了Parcelable
接口的Messenger
类实现,因此区别仅是走不走Binder,对于上层表征来看是没有区别的。
这里重点解释下单向与双向的含义:
单向指的是客户端可以向服务端发送消息,但是服务端无法知道客户端的任何情况(总共连接上的客户端个数,发送这个消息的客户端是哪一个等)
(注意:这里说的单向并不是指数据流只能从Client
到Server
。事实上Server
端是可以回执消息给Client
的,只是Server
端无法知晓Client
,因此无法主动发送消息给Client
;)
双向指的是客户端与服务端可以互发消息,因此彼此是可以了解对方情况的;
由于双向本质上就是两端都连接一次对端,各自持有一个AsyncChannel
实例,向对端发送消息,互为Client/Server
,并且的场景使用得比较少,因此本文后续若无特别说明,均以单向的连接、通信方式为例;
工作原理
概览
先上一张图:
简单解读下:
Looper1
与Looper2
表示两个线程,由于在讨论Handler
的通信,因此用Looper
更直观;Looper2
可以与Looper1
在不同进程,也可以在同一进程,甚至可以是同一个Looper
(即同一线程);- 当
Looper1
下的Handler1x
构造AsyncChannel
并发起对Looper2
下Handler2x
的连接请求时,我们认为Handler1x
是客户端(Client),Looper2
中的目标Handler
则为服务端(Server); - 在单向连接情况下,只能由
Client
发送消息到Server
,Server
端只能在处理完Client
发送过来的消息后,通过replyToMessage
方法定向回执一个Message
给Client
,无法主动调用sendMessage/sendMessageSynchronously
给Client
发送消息; - 而在双向连接的状态下,双方均可调用
sendMessage/sendMessageSynchronously
方法向对端发送消息,也可通过replyToMessage
方法定向回执一个Message
给对端; - 消息传递利用了
IMessenger
这一AIDL
接口:如果Looper1
与Looper2
在同一进程内,甚至为同一线程时,AIDL
会直接走本地调用;如果Looper1
与Looper2
在不同进程时,会利用Binder
调用过去(详见AIDL
接口文件生成的JAVA
类中Stub.asInterface()
方法实现)
连接
最常用的连接方法是如下几个:
//异步
public void connect(Context srcContext, Handler srcHandler, Handler dstHandler);
public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger);
//同步
public int connectSync(Context srcContext, Handler srcHandler, Handler dstHandler);
public int connectSync(Context srcContext, Handler srcHandler, Messenger dstMessenger);
public int fullyConnectSync(Context srcContext, Handler srcHandler, Handler dstHandler);
方法1、3都是简单地将最后一个类型为Handler
参数封装成Messenger
并调用重载方法,因此我们以方法2、4为例分析:
public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
// We are connected
connected(srcContext, srcHandler, dstMessenger);
// Tell source we are half connected
replyHalfConnected(STATUS_SUCCESSFUL);
}
public int connectSync(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
// We are connected
connected(srcContext, srcHandler, dstMessenger);
return STATUS_SUCCESSFUL;
}
public void connected(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
// Initialize source fields
mSrcContext = srcContext;
mSrcHandler = srcHandler;
mSrcMessenger = new Messenger(mSrcHandler);
// Initialize destination fields
mDstMessenger = dstMessenger;
}
前面说过,AsyncChannel
利用的是IMessenger
实现的通信,那么所谓的“连接”实际上也就是把mSrcMessenger
与mDstMessenger
等相关成员变量初始化、赋值即可;这部分无论是connect
还是connectSync
方法,都是同步执行的;
那么所谓的异步是什么?
其实就是返回值是否同步返回:如果是connectSync
方法,则在完成连接后直接return STATUS_SUCCESSFUL
,而如果是connect
方法,则没有返回值,连接成功的状态通过replyHalfConnected
传递给mSrcMessenger
,需要mSrcHandler
在handleMessage
中处理:
private void replyHalfConnected(int status) {
Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_HALF_CONNECTED);
msg.arg1 = status;
msg.obj = this;
msg.replyTo = mDstMessenger;
if (!linkToDeathMonitor()) {
// Override status to indicate failure
msg.arg1 = STATUS_BINDING_UNSUCCESSFUL;
}
mSrcHandler.sendMessage(msg);
}
关于异步连接,考虑到跨进程的AsyncChannel
需要传递Binder
以建立连接,而这一操作普遍通过bindService
后从ServiceConnection.onServiceConnected()
回调方法处获取,因此针对这一使用场景,AsyncChannel
提供了两个个封装方法:
public void connect(Context srcContext, Handler srcHandler, Class<?> klass);
public void connect(Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName);
当然,由于需要等待ServiceConnection.onServiceConnected()
回调方法,因此这两个方法没有同步的响应实现;
断开
断开方法很单一,只有一个:
public void disconnect() {
/*
* 如果mConnection不为null,表明使用了上方所述的两种封装方法进行bindService建立连接:
* - connect(Context srcContext, Handler srcHandler, Class<?> klass);
* - connect(Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName);
* 这种情况下需要调用unbindService断开与远端Service的连接;
* 注意,unbindService不会调用到ServiceConnection.onServiceDisconnected()
*/
if ((mConnection != null) && (mSrcContext != null)) {
/*
* 注意,unbindService不会调用到ServiceConnection.onServiceDisconnected(),因此
* ServiceConnection.onServiceDisconnected()中的replyDisconnected(STATUS_SUCCESSFUL)不会执行,
* 需要下方显式调用一次
*/
mSrcContext.unbindService(mConnection);
mConnection = null;
}
try {
/*
* 尝试给服务端发送CMD_CHANNEL_DISCONNECTED信息,尽管可能收不到
*/
Message msg = Message.obtain();
msg.what = CMD_CHANNEL_DISCONNECTED;
msg.replyTo = mSrcMessenger;
mDstMessenger.send(msg);
} catch(Exception e) {
}
/*
* 给客户端的Handler发送CMD_CHANNEL_DISCONNECTED消息;
* 如果上面mConnection不为null,这里会重复发送
*/
replyDisconnected(STATUS_SUCCESSFUL);
mSrcHandler = null;
// Unlink only when bindService isn't used
if (mConnection == null && mDstMessenger != null && mDeathMonitor!= null) {
mDstMessenger.getBinder().unlinkToDeath(mDeathMonitor, 0);
mDeathMonitor = null;
}
}
有几个点需要澄清一下:
- 无论是通过自己实现
bindService
,或是通过AsyncChannel
提供的内部封装bindService
实现的connect
方法,其本质只是通过ServiceConnection.onServiceConnected()
回调方法获取到服务端的Messenger
(以IMessenger
实例对象传递);此后无论Service
是否断开,均与AsyncChannel
的连接没有必然联系; disconnect()
方法内之所以当mConnection
不为null
时要调用unbindService(mConnection)
,并不是为了彻底断开AsyncChannel
的连接,而是由于mConnection
仅在内部封装了bindService
逻辑的connect
方法调用时创建,因此在断开连接后,mConnection
维系的与服务端Service
的绑定关系也就没有必要了,因此需要断开;- 由上可得,
unbindService
后,只要服务端的Handler
还在,AsyncChannel
连接没有断开,则依旧可以通过AsyncChannel
发送消息到服务端; - 同理,当
AsyncChannel
断开后,如果是自行建立的绑定Service
并不需要立即解绑(unbindService
);
通信
发送消息
最常用的发送消息方法是如下几个:
//异步
public void sendMessage(Message msg);
public void sendMessage(int what);
public void sendMessage(int what, int arg1);
public void sendMessage(int what, int arg1, int arg2);
public void sendMessage(int what, int arg1, int arg2, Object obj);
public void sendMessage(int what, Object obj);
//同步
public Message sendMessageSynchronously(Message msg);
public Message sendMessageSynchronously(int what);
public Message sendMessageSynchronously(int what, int arg1);
public Message sendMessageSynchronously(int what, int arg1, int arg2);
public Message sendMessageSynchronously(int what, int arg1, int arg2, Object obj);
public Message sendMessageSynchronously(int what, Object obj);
无论是同步还是异步,大部分方法都是重载方法,用于适用于各种场景的Message
对象封装,只是同步与异步在发送消息后,是否等待对端返回结果:
//frameworks/base/core/java/com/android/internal/util/AsyncChannel.java
public void sendMessage(Message msg) {
//重要:这个字段决定了回执消息发送给谁,此处主要回执给自己,所以必然是mSrcMessenger
msg.replyTo = mSrcMessenger;
try {
mDstMessenger.send(msg);
} catch (RemoteException e) {
replyDisconnected(STATUS_SEND_UNSUCCESSFUL);
}
}
...
public Message sendMessageSynchronously(Message msg) {
Message resultMsg = SyncMessenger.sendMessageSynchronously(mDstMessenger, msg);
return resultMsg;
}
...
//专门用于同步等待返回消息的工具类
private static class SyncMessenger {
//用一个栈来管理已经构造出来的SyncMessenger对象,用于复用
private static Stack<SyncMessenger> sStack = new Stack<SyncMessenger>();
private static int sCount = 0;
//每个SyncMessenger运行在一个独立的线程,该线程有一个Handler用于获取返回的Message
private HandlerThread mHandlerThread;
private SyncHandler mHandler;
//用于接收返回消息的Messenger,即利用上方mHandler构造的Messenger
private Messenger mMessenger;
...
/** Synchronous Handler class */
private class SyncHandler extends Handler {
/** The object used to wait/notify */
private Object mLockObject = new Object();
/** The resulting message */
private Message mResultMsg;
/** Constructor */
private SyncHandler(Looper looper) {
super(looper);
}
/** Handle of the reply message */
@Override
public void handleMessage(Message msg) {
//将收到的消息生拷贝一份,并唤醒等待线程
Message msgCopy = Message.obtain();
msgCopy.copyFrom(msg);
synchronized(mLockObject) {
mResultMsg = msgCopy;
mLockObject.notify();
}
}
}
//尝试复用栈内已经构造,且可用的SyncMessenger对象
//如果栈是空的,说明没有可用的SyncMessenger对象,需要构造一个
private static SyncMessenger obtain() {
SyncMessenger sm;
synchronized (sStack) {
if (sStack.isEmpty()) {
sm = new SyncMessenger();
sm.mHandlerThread = new HandlerThread("SyncHandler-" + sCount++);
sm.mHandlerThread.start();
sm.mHandler = sm.new SyncHandler(sm.mHandlerThread.getLooper());
sm.mMessenger = new Messenger(sm.mHandler);
} else {
sm = sStack.pop();
}
}
return sm;
}
//使用完毕的SyncMessenger对象压入栈内,供下次obtain方法获取
private void recycle() {
synchronized (sStack) {
sStack.push(this);
}
}
//发送同步消息
private static Message sendMessageSynchronously(Messenger dstMessenger, Message msg) {
//尝试从栈内获取SyncMessenger对象,如果栈内没有,构造一个返回
SyncMessenger sm = SyncMessenger.obtain();
Message resultMsg = null;
try {
if (dstMessenger != null && msg != null) {
//替换监听回执消息的Messenger为本地临时构造的mMessenger
//这样mHandler就可以收到返回消息,并唤醒下方的wait,将回执消息同步返回给调用方
msg.replyTo = sm.mMessenger;
synchronized (sm.mHandler.mLockObject) {
if (sm.mHandler.mResultMsg != null) {
Slog.wtf(TAG, "mResultMsg should be null here");
sm.mHandler.mResultMsg = null;
}
//发送消息依然是异步的
dstMessenger.send(msg);
//然后调用线程等待mHandler收到回执消息并notify
sm.mHandler.mLockObject.wait();
//走到这里即表示回执消息已经收到,并且mHandler内部已经调用notify唤醒了该线程
//此时获取到mResultMsg返回给调用方即可;
resultMsg = sm.mHandler.mResultMsg;
sm.mHandler.mResultMsg = null;
}
}
} catch (InterruptedException e) {
Slog.e(TAG, "error in sendMessageSynchronously", e);
} catch (RemoteException e) {
Slog.e(TAG, "error in sendMessageSynchronously", e);
}
//回收SyncMessenger对象,将其压栈,方便后续复用
sm.recycle();
return resultMsg;
}
}
从上面代码片段以及注释讲解可以知道,异步发送消息,srcHandler
这边要么不关心该消息的回执情况;要么需要在内部的handleMessage
中监听回执结果;
而同步发送消息,则会在调用线程等待,并临时构造了一个SyncMessenger
,通过其内部的SyncMessenger
来接受回执消息,并通知调用线程唤醒,获取回执消息并返回;
回执消息
回执消息也有大量重载方法,且全为异步操作:
public void replyToMessage(Message srcMsg, Message dstMsg);
public void replyToMessage(Message srcMsg, int what);
public void replyToMessage(Message srcMsg, int what, int arg1);
public void replyToMessage(Message srcMsg, int what, int arg1, int arg2);
public void replyToMessage(Message srcMsg, int what, int arg1, int arg2, Object obj);
public void replyToMessage(Message srcMsg, int what, Object obj);
本质上都是调用的第一个:
/**
* Reply to srcMsg sending dstMsg
*
* @param srcMsg
* @param dstMsg
*/
public void replyToMessage(Message srcMsg, Message dstMsg) {
try {
dstMsg.replyTo = mSrcMessenger;
srcMsg.replyTo.send(dstMsg);
} catch (RemoteException e) {
log("TODO: handle replyToMessage RemoteException" + e);
e.printStackTrace();
}
}
这里可以看到,回执消息本质上是调用的srcMsg.replyTo.send()
方法发送了一个Message
;
srcMsg
即为Server
端handleMessage
方法传入的参数;
srcMsg.replyTo
是一个IMessenger
,即为发送这个srcMsg
的Handler
对应的Messenger
也就是说,通过这句调用srcMsg.replyTo.send()
,即可向Client
端发送一个Message
,而在Client
端会将其认为是刚才发送的那条消息的回执;
这里也反向印证了,上面sendMessage
的时候,为何需要将mSrcMessenger
赋值给msg.replyTo
,而sendMessageSynchronously
则需要将临时的SyncMessenger
对象中mMessenger
赋值给msg.replyTo
,这样才能让Client
端的对应Handler
收到回执消息;
此外,细心的同学其实可以发现,单向通信模型下的AsyncChannel
,replyToMessage
调用会发送给哪个Handler
,取决于接收到的这个Message
对象(即srcMsg
中封装的replyTo
成员变量指向哪个Messenger
),与AsyncChannel
是不是同一个实例对象没有关系(这也是必然的,跨进程的场景下,两端不可能持有同一个AsyncChannel
实例对象)
以至于我们可以在WifiStateMachine.java
中看到,回执消息用的AsyncChannel
只是随便new的一个:
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java
...
// Channel for sending replies.
private AsyncChannel mReplyChannel = new AsyncChannel();
...
private void replyToMessage(Message msg, int what) {
if (msg.replyTo == null) return;
Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
mReplyChannel.replyToMessage(msg, dstMsg);
}
private void replyToMessage(Message msg, int what, int arg1) {
if (msg.replyTo == null) return;
Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
dstMsg.arg1 = arg1;
mReplyChannel.replyToMessage(msg, dstMsg);
}
private void replyToMessage(Message msg, int what, Object obj) {
if (msg.replyTo == null) return;
Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
dstMsg.obj = obj;
mReplyChannel.replyToMessage(msg, dstMsg);
}
总结
整个AsyncChannel
对于第一次接触的开发人员来说,还是比较难以理解的,这里我列几条总结性的描述,方便理解:
AsyncChannel
通常使用的是单向连接模式;AsyncChannel
的单向连接模式,仅需要Client
构造AsyncChannel
实例对象,并调用connect/connectSync
即可,前者在其收到CMD_CHANNEL_HALF_CONNECTED
消息后表示连接成功,后者在方法返回值中即可确定连接是否成功;AsyncChannel
的单向连接模式下,Client
与Server
端身份固定,不会变换:Client
通过sendMessage/sendMessageSynchronously
方法向Server
端发送Message
,Server
端只能在接收到Message
后处理并通过replyToMessage
方法回执给Client
端,无法主动发送其他Message
给Client
;- 进程内通信使用
AsyncChannel
比较冗余,可直接使用两个Handler
即可,除非对发送消息的回执有一定要求; - 因此
AsyncChannel
更多还是用于跨进程的通信上;
最后,解答一下一开始的两个疑问:
-
Handler
本身是不可跨进程的,因此AsyncChannel
的跨进程是如何实现的;答:进程间通信使用
AsyncChannel
,本质上是利用了IMessenger
这个Parcelable
接口的跨进程特性,本质上还是Binder
调用; -
为何要使用
AsyncChannel
,而不是直接使用AIDL
调用?
答:Android Framework中定义AIDL接口如果需要增加、更改、删减,需要update API,同时通知APP适配。这通常是不被建议、甚至不被允许的。而使用AsyncChannel
实现进程间通信,只需要定义一个获取对端Messenger
的AIDL
接口,后续扩展只需要两端协商好Message
的what
取值即可,可扩展性方面较好;