异步通道(AsyncChannel)工作原理

在分析 connectivity 服务过程中,会遇到大量的 AsyncChannel 使用。网上不乏有关于 AsyncChannel 实现原理分析的文章,本人觉得部分分析过于复杂,毕竟 AsyncChannel 代码实现不过几百行而已。因此作此小总结。

一、功能:

  • 实现两个线程间消息的发送与回复。
  • 发送同步消息。
  • 跨进程消息传递。

二、使用方法

使用 AsyncChannel 功能依赖于 framework,其实现代码主要是以下文件:

  • frameworks/base/core/java/com/android/internal/util/AsyncChannel.java
  • frameworks/base/core/java/com/android/internal/util/AsyncService.java

以一个线程向另一个线程发送消息,另一个线程回复消息为例,说明 AsyncChannel 使用步骤:

1、定义 AsyncChannel 对象。

AsyncChannel myChannel = new AsyncChannel();

2、定义两个 HandlerThread 作为通信的线程双方。

HandlerThread handlerThread1 = new HandlerThread("Thread1");
HandlerThread handlerThread2 = new HandlerThread("Thread2");

3、创建两个 Handler 分别关联上一步中线程的 Looper。

创建属于 Thread1 的 Handler:

Handler handler1 = new Handler(handlerThread1.getLooper()) {
        switch (msg.what) {
            case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                ...
                break;
            ...
        }
    }
};

创建属于 Thread2 的 Handler:

Handler handler2 = new Handler(handlerThread2.getLooper()) {
        switch (msg.what) {
            case CMD1:
                new AsyncChannel().replyToMessage(msg, CMD1);
                break;
            ...
        }
    }
};

4、调用 AsyncChannel 的 connect 函数,建立两个 Handler 的关联。

myChannel.connect(this, handler1, handler2);

此时 handler1 会收到 CMD_CHANNEL_HALF_CONNECTED 消息,可作为连接成功的标识。

5、源线程调用 AsyncChannel 的 sendMessage 发送消息。

myChannel.sendMessage(CMD1);

6、调用 AsyncChannel 的 replyToMessage 发送回复消息。

new AsyncChannel().replyToMessage(msg, CMD1);

二、工作原理

从使用过程来看,仅调用了 AsyncChannel 的三个 API 函数:connect、sendMessage、replyToMessage。

1、建立连接

首先看 connect 函数实现:

public void connect(Context srcContext, Handler srcHandler, Handler dstHandler) {
    connect(srcContext, srcHandler, new Messenger(dstHandler));
}

public void connect(AsyncService srcAsyncService, Messenger dstMessenger) {
    connect(srcAsyncService, srcAsyncService.getHandler(), dstMessenger);
}

public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
    if (DBG) log("connect srcHandler to the dstMessenger  E");

    // We are connected
    connected(srcContext, srcHandler, dstMessenger);
    
    // Tell source we are half connected
    replyHalfConnected(STATUS_SUCCESSFUL);

    if (DBG) log("connect srcHandler to the dstMessenger X");
}

connect 函数有多个重载版本,最后会调用 connectd 建立连接(暂不分析连接服务的情况)。

public void connected(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
    if (DBG) log("connected srcHandler to the dstMessenger  E");

    // Initialize source fields
    mSrcContext = srcContext;
    mSrcHandler = srcHandler;
    mSrcMessenger = new Messenger(mSrcHandler);

    // Initialize destination fields
    mDstMessenger = dstMessenger;
    if (DBG) log("connected srcHandler to the dstMessenger X");
}

这里初始化了 AsyncChannel 的 4 个成员变量。
再看 replyHalfConnected 函数:

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

这里是调用了 mSrcHandler 发送 CMD_CHANNEL_HALF_CONNECTED 消息,并且消息的回复方指定为 mDstMessenger。与案例中 handler1 处理的消息对应。
所以建立连接就是确定消息的发送方和接收方。

2、发送消息

首先介绍 Messenger 类的实现,Messenger 翻译为送信者,其实是对 Handler 的进一步封装,使其具备跨进程调用能力。Messenger 的构造函数如下:

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

Handler 的 getIMessenger 函数实现:

final IMessenger getIMessenger() {
    synchronized (mQueue) {
        if (mMessenger != null) {
            return mMessenger;
        }
        mMessenger = new MessengerImpl();
        return mMessenger;
    }
}

这里实例化 MessengerImpl 类:

private final class MessengerImpl extends IMessenger.Stub {
    public void send(Message msg) {
        msg.sendingUid = Binder.getCallingUid();
        Handler.this.sendMessage(msg);
    }
}

MessengerImpl 继承至 IMessenger.Stub,说明其具备跨进程通信能力。如果使用 AsyncChannel 只是作为线程间消息传递时,MessengerImpl 当作普通本地类使用即可(等同于 Handler)。

现在可以分析 sendMessage 函数的实现了:

public void sendMessage(int what) {
    Message msg = Message.obtain();
    msg.what = what;
    sendMessage(msg);
}

public void sendMessage(Message msg) {
    msg.replyTo = mSrcMessenger;
    try {
        mDstMessenger.send(msg);
    } catch (RemoteException e) {
        replyDisconnected(STATUS_SEND_UNSUCCESSFUL);
    }
}

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

从上面代码可知,发送消息是通过调用 MessengerImpl 的 send 函数完成,最终会调用接收线程的 Handler 发送此消息。

3、回复消息

通过 AsyncChannel 发送消息时,会在消息的 replyTo 域记录消息的发送方(Messenger),接收方收到消息后,可以通过 replyTo 确定回复消息的目的地。

public void replyToMessage(Message srcMsg, int what, int arg1) {
    Message msg = Message.obtain();
    msg.what = what;
    msg.arg1 = arg1;
    replyToMessage(srcMsg, msg);
}

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

与发送消息类似,回复消息则是调用消息发送方的 Handler 发送此消息。
注意发送回复消息的 AsyncChannel 对象,不需要先建立连接,目的地可以通过 Message 的 replyTo 获取。

关于使用 AsyncChannel 实现与服务通信,可参考如下 connect 函数源码:

public void connect(Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName)

其实就是借助了 binder 通信来实现。这里不作过多分析,在 framework 中也并未使用这种方式。

4、发送同步消息

首先要搞清楚什么是同步消息,同步消息的特点是:发送消息后会阻塞线程,直到收到回复消息后再继续运行。发送同消息的 API是 sendMessageSynchronously,其实现如下:

public Message sendMessageSynchronously(Message msg) {
	Message resultMsg = SyncMessenger.sendMessageSynchronously(mDstMessenger, msg);
	return resultMsg;
}

SyncMessenger 是 AsyncChannel 的内部类,同步消息是通过 SyncMessenger 类的 sendMessageSynchronously 函数发送,并且会返回回复消息。接着分析 SyncMessenger 类的 sendMessageSynchronously 函数实现:

private static Message sendMessageSynchronously(Messenger dstMessenger, Message msg) {
	SyncMessenger sm = SyncMessenger.obtain();		// 创建 HandlerThread 临时接收回复消息
	try {
		if (dstMessenger != null && msg != null) {
			msg.replyTo = sm.mMessenger;			// 注意回复消息接收方并非 AsyncChannel 的 mSrcMessenger  成员
			synchronized (sm.mHandler.mLockObject) {
				dstMessenger.send(msg);				// 发送消息
				sm.mHandler.mLockObject.wait();		// 等待回复消息
			}
		} else {
			sm.mHandler.mResultMsg = null;
		}
	} catch (InterruptedException e) {
		sm.mHandler.mResultMsg = null;
	} catch (RemoteException e) {
		sm.mHandler.mResultMsg = null;
	}
	Message resultMsg = sm.mHandler.mResultMsg;		// 从 handler 中取出回复消息
	sm.recycle();
	return resultMsg;
}

首先通过 obtain 分配一个 SyncMessenger 对象,用于接收回复消息,然后调用 dstMessenger 的 send 函数发送消息,紧接着调用 wait() 阻塞线程,直到 sm 收到回复消息,取出回复消息后返回。
这里有必要说明 obtain 的实现原理:

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());	// 创建 Handler
			sm.mMessenger = new Messenger(sm.mHandler);
		} else {
			sm = sStack.pop();
		}
	}
	return sm;
}

从以上代码来看,本质上就是创建一个临时 Handler 用于处理回复消息。

特别注意:发送同步消息时,发送方的 Handler 是无法接收到回复消息的,而是被 obtain 创建的临时 Handler 接收。

因为发送同步消息必须要收到回复消息程序才能继续运行,因此接收方收到消息后必须回复此消息:

new AsyncChannel().replyToMessage(msg, CMD1);

使用范例
测试程序下载地址

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翻滚吧香香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值