Android入门之创建InputChannel

        首先需要知道什么是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);
}

        注意此方法的注释:创建一对新的InputChannel,一个提供给InputDispatcher,另一个用于应用的输入队列。

        前面说到,应用程序和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;
}

        此方法创建了两个通道变量,分别是serverChannel和clientChannel,然后通过InputChannel::openInputChannelPair(name, serverChannel, clientChannel)为通道赋值。

        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创建文件描述符用于多进程间共享,ashmem_create_region定义在system/core/libcutils/ashmem-dev.c中,内容如下:

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

        表示创建一个新的ashmem区并返回文件描述符,异常时返回小于0的数据。

        创建文件描述符后,经过以下处理:

        (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:

Android入门之创建InputChannel_第1张图片

        (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代码中。

你可能感兴趣的:(Android)