首先需要知道什么是InputChannel。
我们说,InputDispatcher和客户窗口ViewRoot之间,是通过Pipe传递消息的,而Pipe是Linux系统调用的一部分,Android为了能够调用Pipe而创建了InputChannel类,可以说,InputChannel是Pipe的Android版。
InputChannel类有两个基本作用,一是保存消息端口对应的Pipe的读、写描述符,另一个是封装了Linux的Pipe系统调用,即程序员可以使用InputChannel所提供的函数创建底层的Pipe对象。
在阅读WmS的addWindow方法时,我们提到有以下代码:
if (outInputChannel != null) {
String name = win.makeInputChannelName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
win.mInputChannel = inputChannels[0];
inputChannels[1].transferToBinderOutParameter(outInputChannel);
mInputManager.registerInputChannel(win.mInputChannel);
}
WmS即是在这里创建InputChannel的,来看看openInputChannelPair方法:
/**
* Creates a new input channel pair. One channel should be provided to the input
* dispatcher and the other to the application's input queue.
* @param name The descriptive (non-unique) name of the channel pair.
* @return A pair of input channels. They are symmetric and indistinguishable.
*/
public static InputChannel[] openInputChannelPair(String name) {
if (name == null) {
throw new IllegalArgumentException("name must not be null");
}
if (DEBUG) {
Slog.d(TAG, "Opening input channel pair '" + name + "'");
}
return nativeOpenInputChannelPair(name);
}
前面说到,应用程序和WmS之间交互会创建两个Pipe,一个用于读、一个用于写,在此处得到验证。
nativeOpenInputChannelPair方法是InputChannel对应的cpp的方法,找到文件android_view_InputChannel.cpp,找到nativeOpenInputChannelPair方法:
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
jclass clazz, jstring nameObj) {
const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
String8 name(nameChars);
env->ReleaseStringUTFChars(nameObj, nameChars);
sp serverChannel;
sp clientChannel;
status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
if (result) {
LOGE("Could not open input channel pair. status=%d", result);
jniThrowRuntimeException(env, "Could not open input channel pair.");
return NULL;
}
// TODO more robust error checking
jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
new NativeInputChannel(serverChannel));
jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
new NativeInputChannel(clientChannel));
jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
return channelPair;
}
InputChannel::openInputChannelPair这个方法来自于frameworks/base/libs/ui/InputTransport.cpp,感谢柯元旦先生,因为我到目前为止还不知道是如何找到这个文件的。。
InputChannel::openInputChannelPair内容如下所示:
status_t InputChannel::openInputChannelPair(const String8& name,
sp& outServerChannel, sp& outClientChannel) {
status_t result;
int serverAshmemFd = ashmem_create_region(name.string(), DEFAULT_MESSAGE_BUFFER_SIZE);
if (serverAshmemFd < 0) {
result = -errno;
LOGE("channel '%s' ~ Could not create shared memory region. errno=%d",
name.string(), errno);
} else {
result = ashmem_set_prot_region(serverAshmemFd, PROT_READ | PROT_WRITE);
if (result < 0) {
LOGE("channel '%s' ~ Error %d trying to set protection of ashmem fd %d.",
name.string(), result, serverAshmemFd);
} else {
// Dup the file descriptor because the server and client input channel objects that
// are returned may have different lifetimes but they share the same shared memory region.
int clientAshmemFd;
clientAshmemFd = dup(serverAshmemFd);
if (clientAshmemFd < 0) {
result = -errno;
LOGE("channel '%s' ~ Could not dup() shared memory region fd. errno=%d",
name.string(), errno);
} else {
int forward[2];
if (pipe(forward)) {
result = -errno;
LOGE("channel '%s' ~ Could not create forward pipe. errno=%d",
name.string(), errno);
} else {
int reverse[2];
if (pipe(reverse)) {
result = -errno;
LOGE("channel '%s' ~ Could not create reverse pipe. errno=%d",
name.string(), errno);
} else {
String8 serverChannelName = name;
serverChannelName.append(" (server)");
outServerChannel = new InputChannel(serverChannelName,
serverAshmemFd, reverse[0], forward[1]);
String8 clientChannelName = name;
clientChannelName.append(" (client)");
outClientChannel = new InputChannel(clientChannelName,
clientAshmemFd, forward[0], reverse[1]);
return OK;
}
::close(forward[0]);
::close(forward[1]);
}
::close(clientAshmemFd);
}
}
::close(serverAshmemFd);
}
outServerChannel.clear();
outClientChannel.clear();
return result;
}
/*
* ashmem_create_region - creates a new ashmem region and returns the file
* descriptor, or <0 on error
*
* `name' is an optional label to give the region (visible in /proc/pid/maps)
* `size' is the size of the region, in page-aligned bytes
*/
int ashmem_create_region(const char *name, size_t size)
{
int fd, ret;
fd = open(ASHMEM_DEVICE, O_RDWR);
if (fd < 0)
return fd;
if (name) {
char buf[ASHMEM_NAME_LEN];
strlcpy(buf, name, sizeof(buf));
ret = ioctl(fd, ASHMEM_SET_NAME, buf);
if (ret < 0)
goto error;
}
ret = ioctl(fd, ASHMEM_SET_SIZE, size);
if (ret < 0)
goto error;
return fd;
error:
close(fd);
return ret;
}
创建文件描述符后,经过以下处理:
(1) 调用ashmem_set_port_region设置该文件描述符的属性为可读、可写方式;
(2) 然后调用dup()函数,复制一个同样的描述符,赋值给clientAshmemFd;
(3) 使用pipe()系统调用创建一个管道forward,返回管理的两个端点,第一个是读,第二个是写;
(4) 使用pipe()系统调用创建两个管道reverse;
(5) 使用forward[0]、reverse[1]作为outServerChannel,使用forward[1]、reverse[0]作为outClientChannel;
重点项:
(1) 为什么要使用dup()?因为InputDispatcher线程和客户线程将共享这段内存区存储交互消息,或者说,两者都可以向这段内存区写数据,至于这些消息来自于InputDispatcher还是来自客户窗口,则在消息本身中进行了标;
(2) 管道和通道的对应关系如下图所示,其中f代表forward,r代表reverse:
(3) 注意管道(pipe)、共享内存(ashemem)和通道(channel)的区别,pipe用于InputDispatcher和客户窗口进行消息传递,但有时可能需要传递大量数据,因此又开辟了一段共享内存,这段共享内存不是必需的,而通道是每个通信端点保存的一个数据类,保存了pipe和共享内存的相关信息,因此,在ViewRoot类中包含一个InputChannel内部变量mInputChannel,而在WmS的mInputManager对象中也保存了和每个客户窗口通道的InputChannel对象列表。
(4) 这里创建的两个通道是可以互换的,即f0和r1作为outServerChannel,而r0和f1作为outClientChannel。
android_view_InputChannel创建通道完成后,创建一个jobjectArray channelPair对象,将该对象返回到Java代码中。