这篇文章主要介绍Android AsyncChannel、Messenger原理及其应用实例
注:文章参考的是Andrdoid 8.0源码
AsyncChannel 简介
AsyncChannel的源码位于 frameworks/base/core/java/com/android/internal/util/AsyncChannel.java,是对Handler和Messenger的一个扩展,用于单个进程内(包含不同线程间)或两个进程间的通信。在不同的进程间,AsyncChanne实质上使用的是IMessenger通过Binder的形式对消息进行发送和接收,当然,这种方式对单个进程内非remove service同样适用。
在Android系统中,有很多地方都使用了这个工具类,如ConnectivityService与NetworkAgent的通信,WifiServiceImpl与WifiStateMachine之间的通信等。由于AsyncChannel属于系统内部源码,三方应用无法直接进行使用,但我们可以学习思想,甚至可以自己简单实现并作用于我们的APP代码中。
AsyncChannel的主要特点:
Messenger
由于AsyncChannel实际上使用的是Handler和Messenger的机制,考虑到很多开发者对Messenger还不够了解,所以我们这里有必要先对这个工具做一个详细的解析。
Messenger的源码位于 frameworks/base/core/java/android/os/Messenger.java,是对Handler的一个再包装,并结合了Binder机制,使得跨进程间Message的传递和处理成为了可能。
成员变量及构造函数:
主要成员变量是IMessenger类型的mTarget,该变量可以通过Handler或者IBinder进行初始化,分别应用于同进程消息或者跨Service(remote或者非remote)消息发送:
// IMessenger类型,mTarget代表message的目的端
private final IMessenger mTarget;// 初始化mTarget并指向Handler中的IMessenger
public Messenger(Handler target) {mTarget = target.getIMessenger();
}// 初始化mTarget并指向IBinder的实际类型
public Messenger(IBinder target) {mTarget = IMessenger.Stub.asInterface(target);
}
Messenger的发送函数:
无论是否跨进程,Messenger实际上都是利用IMessenger进行消息发送的:
public void send(Message message) throws RemoteException {mTarget.send(message);
}
IMessenger:
源码位于 frameworks/base/core/java/android/os/IMessenger.aidl,其目的是通过AIDL使用Binder机制对Message进行发送:
/** @hide */
oneway interface IMessenger {void send(in Message msg);
}
通过Handler对mTarget进行初始化:
Handler中MessengerImpl对IMessenger进行了实现,在 send 函数中最终还是使用Handler自身对Message进行了处理,因此,这里的IMessenger仅仅是一个中介:
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) {// 由于存在Binder调用,初始化sendingUid为发送端uidmsg.sendingUid = Binder.getCallingUid();// 通过Handler自身发送消息并进行处理Handler.this.sendMessage(msg);}
}
通过Service IBinder对mTarget进行初始化:
这里以Android源码中针对Messenger的测试case作为例子对其过程进行解析,源码路径:
- frameworks/base/core/tests/coretests/src/android/os/MessengerService.java
- frameworks/base/core/tests/coretests/src/android/os/MessengerTest.java
Service端实现(MessengerService.java):
public class MessengerService extends Service {private final Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// 在这里处理client端发送的消息}};// 定义一个Messenger,Messenger中的mTarget即为mHandler中的mMessengerprivate final Messenger mMessenger = new Messenger(mHandler);@Overridepublic IBinder onBind(Intent intent) {// 返回mHandler中的mMessengerreturn mMessenger.getBinder();}
}
Client端实现(MessengerTest.java):
通过 bindService 与MessengerService建立连接,连接完成后,对Messenger进行初始化:
private ServiceConnection mConnection = new ServiceConnection() {public void onServiceConnected(ComponentName name, IBinder service) {synchronized (MessengerTest.this) {// 通过IBinder类型的service对Messenger中的mTarget进行初始化mServiceMessenger = new Messenger(service);MessengerTest.this.notifyAll();}}public void onServiceDisconnected(ComponentName name) {// service断开后,对应的messenger也不会再有作用mServiceMessenger = null;}
};
因此,这样初始化后,下面的几个变量就可以看做是同一个对象的引用,即:
MessengerTest.mServiceMessenger.mTarget
= MessengerService.mMessenger.mTarget
= MessengerService.mHandler.mMessenger
Messenger小结:
实质上是通过IMessenger利用Binder机制进行消息的发送。Handler中实现了IMessenger中的 send 函数,并在其中调用Handler自身的 sendMessage 函数进行消息处理;Service中可以定义自己的Handler和对应的Messenger,并在onBind时返回Handler中MessengerImpl的实例,Client端在 onServiceConnected 函数中通过IBinder对象对Client中Messenger进行初始化,之后,Client端就可以通过该Messenger将消息发送到Service中的Handler进行处理了。Client和Service间通过Messenger进行消息传递的大致流程如下图:
AsyncChannel成员常量和变量
注明:从分析AsyncChannel开始,我们称连接的首次发起端为"source端",被连接端为"destination端"
连接指令相关静态常量:
/** AsyncChannel消息码在系统中的唯一开始标志 */
private static final int BASE = Protocol.BASE_SYSTEM_ASYNC_CHANNEL;/** 单向连接建立后,AsyncChannel通知source端半连接已建立,可以进行单向通信,但此时destination端是完全没有感知的 */
public static final int CMD_CHANNEL_HALF_CONNECTED = BASE + 0;/** source端在半连接建立后,发送该消息码给destination端,请求建立双向连接 */
public static final int CMD_CHANNEL_FULL_CONNECTION = BASE + 1;/** destination端在建立双向连接后,发送该消息码给source端,告知双向连接建立完成 */
public static final int CMD_CHANNEL_FULLY_CONNECTED = BASE + 2;/** source或者destination主动请求断开 */
public static final int CMD_CHANNEL_DISCONNECT = BASE + 3;/** 在一端主动断开后,对端会收到该消息码 */
public static final int CMD_CHANNEL_DISCONNECTED = BASE + 4;
连接和消息状态相关静态常量:
/** 连接状态相关:成功半连接,成功连接,成功断开 */
public static final int STATUS_SUCCESSFUL = 0;/** 跨service连接,bindService成功或者失败 */
public static final int STATUS_BINDING_UNSUCCESSFUL = 1;/** 消息发送失败 */
public static final int STATUS_SEND_UNSUCCESSFUL = 2;/** 已建立双向连接,拒绝再次连接 */
public static final int STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED = 3;/** 跨service连接,对端service死亡,binderDied返回后发送该状态码 */
public static final int STATUS_REMOTE_DISCONNECTION = 4;
主要成员变量:
/** ServiceConnection,用于和service或remote service建立连接 */
private AsyncChannelConnection mConnection;/** 连接建立端Context,仅用于bind/unbind service时使用 */
private Context mSrcContext;/** binderDied回调监听,仅用于与service连接后使用 */
private DeathMonitor mDeathMonitor;/** 连接建立端Handler */
private Handler mSrcHandler;/** 连接建立端Messenger */
private Messenger mSrcMessenger;/** 连接对端Messenger */
private Messenger mDstMessenger;
AsyncChannel连接过程 - 半连接(Half Connect)
Source端发起连接请求:
/** 发起连接 */
public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {// source端AsyncChannel变量初始化connected(srcContext, srcHandler, dstMessenger);// 发送"CMD_CHANNEL_HALF_CONNECTED"告知source端半连接已建立replyHalfConnected(STATUS_SUCCESSFUL);
}
/** 初始化source端的成员变量,创建source端Messenger */
public void connected(Context srcContext, Handler srcHandler, Messenger dstMessenger) {// Initialize source fieldsmSrcContext = srcContext;mSrcHandler = srcHandler;mSrcMessenger = new Messenger(mSrcHandler);// Initialize destination fieldsmDstMessenger = dstMessenger; linkToDeathMonitor();
}
/** 发送"CMD_CHANNEL_HALF_CONNECTED"告知source端半连接已建立 */
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 failuremsg.arg1 = STATUS_BINDING_UNSUCCESSFUL;}mSrcHandler.sendMessage(msg);
}
半连接小结:
整个半连接过程由连接发起端(source端)完成,destination端没有任何消息告知。source端调用AsyncChannel的 connect 函数后,开始对AsyncChannel中变量的初始化,主要是对mDstMessenger的初始化。完成后source端Handler会收到 “CMD_CHANNEL_HALF_CONNECTED” 的消息告知半连接已经完成,之后,source端就可以通过该AsyncChannel与destination进行通信。这种通信方式是单向的,只能由source端主动向destination推送消息并获取其回复,destination无法主动向source推送消息。这个连接过程可以由下面的时序图来表示:
AsyncChannel连接过程 - 全连接(Full Connect)
AsyncChannel的全连接建立主要有以下两种方式:
下面对这两种全连接建立方式分别作解析。
方式一(在半连接的基础上由source端发送 “CMD_CHANNEL_FULL_CONNECTION” 请求全连接):
这个过程主要是由AsyncChannel使用者自己完成,我们以ConnectivityService与NetworkAgent之间的通信进行举例,例子的关键源码路径如下:
- frameworks/base/services/core/java/com/android/server/ConnectivityService.java
- frameworks/base/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
- frameworks/base/core/java/android/net/NetworkAgent.java
首先,ConnectivityService作为连接主动端主动请求半连接:
/** ConnectivityService#handleRegisterNetworkAgent */
private void handleRegisterNetworkAgent(NetworkAgentInfo na) {// ...// 通过NetworkAgentInfo中新创建的AsyncChannel主动请求连接// mTrackerHandler是ConnectivityService中的Handler,作为source handler// na.messenger是连接的对端,这里就是指 NetworkAgentna.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);// ..
}
通过对半连接的了解,在 connect 之后,source handler就会收到 “CMD_CHANNEL_HALF_CONNECTED”。由于需要建立全连接,ConnectivityService在收到该消息后,立刻向半连接对端请求全连接:
/** ConnectivityService#NetworkStateTrackerHandler */
private class NetworkStateTrackerHandler extends Handler {// ...private boolean maybeHandleAsyncChannelMessage(Message msg) {switch (msg.what) {case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {// 收到 "CMD_CHANNEL_HALF_CONNECTED",在下面的函数中处理handleAsyncChannelHalfConnect(msg);break;}// ...
}/** ConnectivityService#handleAsyncChannelHalfConnect */
private void handleAsyncChannelHalfConnect(Message msg) {// ...else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {if (VDBG) log("NetworkAgent connected");// 发送全连接请求 "CMD_CHANNEL_FULL_CONNECTION"mNetworkAgentInfos.get(msg.replyTo).asyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);}
}
NetworkAgent在收到 “CMD_CHANNEL_FULL_CONNECTION” 请求后,便开始创建自己的AsyncChannel,并完成其初始化,连接的对端是ConnectivityService:
/** NetworkAgent#handleMessage */
public void handleMessage(Message msg) {switch (msg.what) {// 收到由ConnectivityService发来的全连接请求case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {if (mAsyncChannel != null) {log("Received new connection while already connected!");} else {if (VDBG) log("NetworkAgent fully connected");// 创建自己的AsyncChannelAsyncChannel ac = new AsyncChannel();// msg.replayTo 就是NetworkAgentInfo.asyncChannel中的mSrcMessenger,// 消息的处理者就是ConnectivityService.mTrackerHandlerac.connected(null, this, msg.replyTo);// 全连接已经建立,发送 "CMD_CHANNEL_FULLY_CONNECTED" 给ConnectivityServiceac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,AsyncChannel.STATUS_SUCCESSFUL);// ...}break;// ...
}
从上述的过程可以看出,建立全连接的方式就是连接的两端都需要创建自己的AsyncChannel,并且每个AsyncChannel中都保存着对端的Messenger。这种全连接的方式可以通过下面流程图进行表示:
方式二:source端直接调用AsyncChannel的 fullyConnectSync 进行全连接
从 fullyConnectSync 这个函数名,可以知道这是一个同步的操作,AsyncChannel中使用了SyncMessenger这个静态内部类来实现这个同步连接的操作。直接从 fullyConnectSync 这个函数开始分析。
fullyConnectSync 中对source端AsyncChannel进行了初始化,并发送 “CMD_CHANNEL_FULL_CONNECTION” 同步消息请求全连接:
/** AsyncChannel#fullyConnectSync*/
public int fullyConnectSync(Context srcContext, Handler srcHandler, Handler dstHandler) {// 只是调用了connected函数,对source端AsyncChannel成员变量进行初始化int status = connectSync(srcContext, srcHandler, dstHandler);if (status == STATUS_SUCCESSFUL) {// 发送"CMD_CHANNEL_FULL_CONNECTION"同步消息给destination// 同步消息,需要等待回应后才继续Message response = sendMessageSynchronously(CMD_CHANNEL_FULL_CONNECTION);status = response.arg1;}return status;
}
sendMessageSynchronously 实际使用的是 “SyncMessenger.sendMessageSynchronously”:
/** AsyncChannel.SyncMessenger#sendMessageSynchronously*/
private static Message sendMessageSynchronously(Messenger dstMessenger, Message msg) {// 获取一个SyncMessenger对象,启动消息接收者SyncHandlerSyncMessenger sm = SyncMessenger.obtain();try {if (dstMessenger != null && msg != null) {msg.replyTo = sm.mMessenger;// 获取对象锁:"sm.mHandler.mLockObject"synchronized (sm.mHandler.mLockObject) {// 发送 "CMD_CHANNEL_FULL_CONNECTION"dstMessenger.send(msg);// 释放该锁并中断等待notifysm.mHandler.mLockObject.wait();}} else {sm.mHandler.mResultMsg = null;}} catch (InterruptedException e) {sm.mHandler.mResultMsg = null;} catch (RemoteException e) {sm.mHandler.mResultMsg = null;}// 被CPU唤醒,消息回复已经收到,返回该回复Message resultMsg = sm.mHandler.mResultMsg;sm.recycle();return resultMsg;
}
通过SyncMessenger向对端发送完消息后,对端如果已经完成了 connect 的操作或者拒绝连接,都应该回复消息。由于发送消息前设定了msg.replyTo = sm.mMessenger
,因此回复的消息会被SyncManager中的SyncHandler处理:
/** AsyncChannel.SyncManager#SyncHandler#handleMessage*/
public void handleMessage(Message msg) {mResultMsg = Message.obtain();mResultMsg.copyFrom(msg);// 获取mLockObject对象锁synchronized(mLockObject) {// 唤醒在mLockObject这个锁上等待的线程mLockObject.notify();}
}
从上面的流程可以看出,通过 fullyConnectSync 建立全连接省略了中间 “CMD_CHANNEL_HALF_CONNECTED” 消息回复过程,并且通过SyncMessenger,实现了在对端完成connect操作后才返回。因此,这个过程不需要AsyncChannel的主动连接者进行其他操作就可以完成全连接。然而,AsyncChannel的对端依然需要处理 “CMD_CHANNEL_FULL_CONNECTION”。整个过程的大致流程图如下:
AsyncChannel的跨进程消息传递
由于AsyncChannel内部使用的是Messenger,且Handler和Messenger都已经支持了Binder通信方式,因此,AsyncChannel同样可以利用在跨进程通信上。
前文在介绍Messenger时,通过例子”MessengerService”对Messenger做了解析,AsyncChannel在跨进程通信上与前文例子相似:在连接前,需要先bindService,在 onServiceConnected 时,拿到Service的Messenger并完成了对mDstMessenger的初始化,从而实现了半连接。之后,全连接的方式就与前文介绍的相同。
/** AsyncChannel#connect*/
public void connect(Context srcContext, Handler srcHandler, String dstPackageName,String dstClassName) {// 由于有bindService操作,并且需要等待onServiceConnected回调,// 因此实现了一个Runnable并且新开一个线程去完成这个操作final class ConnectAsync implements Runnable {// ...@Overridepublic void run() {// 完成对AsyncChannel中除mDstMessenger的成员变量的初始化// 并执行bindService操作int result = connectSrcHandlerToPackageSync(mSrcCtx, mSrcHdlr, mDstPackageName,mDstClassName);replyHalfConnected(result);}}ConnectAsync ca = new ConnectAsync(srcContext, srcHandler, dstPackageName, dstClassName);// 新开一个线程执行异步连接操作new Thread(ca).start();
}
connectSrcHandlerToPackageSync 完成了对AsyncChannel中基本成员变量的初始化,与destination相关的mDstMessenger变量需要等待 onServiceConnected 回调后再进行初始化:
/** AsyncChannel#connectSrcHandlerToPackageSync */
public int connectSrcHandlerToPackageSync(Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName) {mConnection = new AsyncChannelConnection();/* 初始化AsyncChannel source相关的变量 */mSrcContext = srcContext;mSrcHandler = srcHandler;mSrcMessenger = new Messenger(srcHandler);// 对端Messenger等待onServiceConnected后再进行初始化mDstMessenger = null;// bindServiceIntent intent = new Intent(Intent.ACTION_MAIN);intent.setClassName(dstPackageName, dstClassName);boolean result = srcContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);return result ? STATUS_SUCCESSFUL : STATUS_BINDING_UNSUCCESSFUL;
}
AsyncChannelConnection#onServiceConnected 会在Service bind成功后被回调,在这个函数中,完成了对 mDstMessenger 的初始化,到此,半连接完成:
/** AsyncChannel#AsyncChannelConnection */
class AsyncChannelConnection implements ServiceConnection {AsyncChannelConnection() {}@Overridepublic void onServiceConnected(ComponentName className, IBinder service) {// service bind成功,初始化mDstMessengermDstMessenger = new Messenger(service);replyHalfConnected(STATUS_SUCCESSFUL);}// ...
}
跨进程或同进程Service的AsyncChannel连接与普通的相同,只不过多了一步bindService的操作,并且mDstMessenger 需要等待 onServiceConnected 回调后才能完成初始化。
总结
AsyncChannel 结合Messenger,实现了两个进程或线程间Handler消息的传递。这篇文章主要介绍了Messenger的原理、AsyncChannel的两种连接方式:半连接和全连接、AsyncChannel跨进程连接等,虽然这个工具类无法直接使用,但如果应用中有需要,仍然可以借鉴其原理作适合的简单实现。