Android源码分析 - InputManagerService与触摸事件

news2024/11/17 11:40:08

0. 前言

有人问到:“通过TouchEvent,你可以获得到当前的触点,它更新的频率和屏幕刷新的频率一样吗?”。听到这个问题的时候我感到很诧异,我们知道Android是事件驱动机制的设计,可以从多种服务中通过IPC通信获取通知,许多功能并不能混为一谈。
所以我今天想跟踪一下Input系统是怎么一个逻辑。结合多个优秀博客与源码跟踪分析,记录分享。

本文基于 Android 8.0 源码,高版本可能会有不同,仅供参考。本文的图片均来自参考博客,他们已经画的很完善了。

Window相关的知识点欢迎参考:

Android从屏幕刷新到View的绘制(一)之 Window、WindowManager和WindowManagerService之间的关系

1. InputChannel输入事件的消息通道——Socket

1.1 Window与Input输入事件的关系

我们猜想也知道,一个输入系统想要分发触摸事件,首先就要知道触摸在哪个Window上,而APP进程中与Window紧密关联的就是ViewRootImpl与WindowManagerGlobal。果然我们发现,在ViewRootImpl的setView()中有输入事件的注册,我们来看看:

//ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    //1.实例化一个InputChannel
    if ((mWindowAttributes.inputFeatures
         & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
        //实例化一个InputChannel
        mInputChannel = new InputChannel();
    }
    //2.向WMS添加window,同时交出自己的InputChannel
    res = mWindowSession.addToDisplay(...,mInputChanel);//向WMS添加window
    //...
    //3.创建WindowInputEventReceiver接受Input事件
    if (mInputChannel != null) {
        if (mInputQueueCallback != null) {
            mInputQueue = new InputQueue();
            mInputQueueCallback.onInputQueueCreated(mInputQueue);
        }
        //创建了一个WindowInputEventReceiver对象,看名字就才到这是输入事件的监听者,我们着重关注它是如何监听输入事件的。
        mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                                                           Looper.myLooper());
    }

}

正如我们分析所知,输入事件与Window是紧密联系的,我们接下来做两件事:

  1. 添加window的时候,为Input系统做了哪些铺垫?
  2. 注册了Input事件的监听者,它如何接受Input事件的?

解决第一个问题,我们先来看向WMS添加windows的方法 mWindowSession.addToDisplay(),它最终回来到WMS的addWindow():

//WindowManagerService
public int addWindow(Session session,IWindow client,...,InputChannel outInputChannel) {
    //...
    //1. 创建WindowState与Window一一对应,进行管理
    WIndowState win = new WindowState(this,session,client,...);
    //...
    //2. 开启输入通道
    final boolean openInputChannels = (outInputChannel != null
                                       && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
    if  (openInputChannels) {
        win.openInputChannel(outInputChannel);
    }
    
}

接下来就进入到WindowState.openInputChannel(outInputChannel):

//WindowState
// Input channel and input window handle used by the input dispatcher.
//没错,这里的handle就是binder通信中的handle引用
final InputWindowHandle mInputWindowHandle;
InputChannel mInputChannel;
private InputChannel mClientChannel;

void openInputChannel(InputChannel outInputChannel){
    //1. 开启一个channel
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
    //2.返回channel引用
    //服务端的socket
    mInputChannel = inputChannels[0];
    //客户端的socket
    mClientChannel = inputChannels[1];
    //将服务端的socket引用交给客户端
    mInputWindowHandle.inputChannel = inputChannels[0];
    //3. 让WMS中的InputManager注册这个InputChannel
    mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
}

这是在WindowState类中的操作,mInputWindowHandle是对与WindowState对应的Window的handle引用,WindowState又持有WMS,可以告知WMS让IMS注册这个InputChannel输入通道。其实到这里,我们按InputChannel的名字就能猜测到,这里的IPC通信用的不是Binder,而是其他的,如管道或者socket,我们再往后看看。

InputManagerService与WMS、AMS类似的,都是系统服务,都将自己的binder引用交给了ServiceManager的svclist中。服务中相互调用方法也是常见的。这里WMS持有IMS的引用,调用了它的registerInputChannel方法。这个引用,在SystemServer启动服务,实例化WMS的时候就传入了,我们简单看一下就略过:

//SystemServer
private void startOtherServices(){
    inputManager = new InputManagerService(context);
    //这里将IMS交给了WMS
    wm = WindowManagerService.main(context, inputManager,
                                   mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                                   !mFirstBoot, mOnlyCore, new PhoneWindowManager());
    //向ServiceManager注册服务
    ServiceManager.addService(Context.WINDOW_SERVICE, wm);
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
}

//InputManagerService
public InputManagerService(Context context) {
    this.mContext = context;
    //也是有一个looper在跑
    this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
	//这里需要注意,看后文解释
    LocalServices.addService(InputManagerInternal.class, new LocalService());
}

LocalService这个类和ServiceManager的功能类似,可以用于注册服务与获取服务,只不过这里注册的服务不是Binder实体,只能在同一个进程中使用(不用跨进程通信,例如WMS与IMS都在SystemServer进程之下)。顾名思义,LocalService为本地服务,与远程服务的区别就是,远程服务在不同的进程,例如AMS对于APP进程来说就是远程服务,而WMS对于AMS来说就是本地服务,都在SystemServer进程之下。但它们又都开了Binder线程池,所以也同时都注册到了ServiceManager的svclist之中。这里就点到为止,本文主要目的还是探讨输入事件。

1.2 InputChannel的创建与注册

我们接下来关注两个点:

  1. InputChannel是如何打开的?
  2. 如何向InputManagerService注册这个inputChannel

先来看到InputChannel:

//InputChannel
public static InputChannel[] openInputChannelPair(String name) {
    //走了native方法
    return nativeOpenInputChannelPair(name);
}

来到native层:

//android_view_InputChannel
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,jclass clazz, jstring nameObj) {
    //1. 服务端channel和客户端channel
    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    //2.openInputChannelPair()来开启两个channel
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
	//...
    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
    //...
    //将两个channel放到channelPair中
    //...
    return channelPair;
}

来到InputTransport.cpp:

//InputTransport.cpp
status_t InputChannel::openInputChannelPair(const String8& name,sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    //从名字就可以看出来,使用的是socket了
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        //error
        return result;
    }
	//2.处理两个socket
    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
	//给两个socket命名,放到InputChannel之中
    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    //实例化一个InputChannel引用回传
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);

    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

跟到这里我们发现,这个InputChannel使用的是socket,构建InputChannel对象时,传入的第一个参数是name,第二个参数是fd,文件描述符。源码注释是这样解释的:InputChannel输入通道是由本地Unix的socket组成的,用于跨进程发送、接收消息。每个通道都有一个用于调试的描述性名称,也就是第一个参数name。

这下就清楚了,创建InputChannel,就是给客户端、服务端都开启了一个socket,用于进行IPC通信。换句话说,openInputChannelPair生成了两个socket的fd(文件描述符),代表一个双向通道的两端。初始化了两端的包括Native层和Java层的InputChannel对象,native层的InputChannel封装了name和fd。

解决完InputChannel是如何打开的问题后,我们来看IMS如何注册这个InputChannel的:

来到InputManagerService的registerInputChannel():

//InputManagerService
public void registerInputChannel(InputChannel inputChannel,
                                 InputWindowHandle inputWindowHandle) {
    nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);
}
private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel,
                                                      InputWindowHandle inputWindowHandle, boolean monitor);

它调用到了native方法:

//com_android_server_input_InputManagerService.cpp
static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
    //1. NativeInputManager
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
	//2. 传入参数可以看到
    //inputChannelObj是服务端channel
    //inputWindowHandleObj是客户端channel
   	//...
    //3. 来到NativeInputManager的registerInputChannel
    status_t status = im->registerInputChannel(
            env, inputChannel, inputWindowHandle, monitor);
    //...
}

注册InputChannel输入通道的任务又交给了native层来做,来到乐NativeInputManagerService.registerInputChannel():

//com_android_server_input_InputManagerService.cpp
status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
        const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
    return mInputManager->getDispatcher()->registerInputChannel(
            inputChannel, inputWindowHandle, monitor);
}

其中mInputManager是在初始化NativeInputManager的时候初始化的:

//com_android_server_input_InputManagerService.cpp
NativeInputManager::NativeInputManager(jobject contextObj,
                                       jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();
	//...
    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

这里的EventHub叫做事件集线器,时间总线。顾名思义可以获取到各种事件。我们后面再讨论,先来看一下mInputManager->getDispatcher()->registerInputChannel()

//InputDispatcher.cpp
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
                                               const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {

    // acquire lock
    AutoMutex _l(mLock);
	//1.判断,这个连接connection是否已经建立过了
    if (getConnectionIndexLocked(inputChannel) >= 0) {
        ALOGW("Attempted to register already registered input channel '%s'",
              inputChannel->getName().string());
        return BAD_VALUE;
    }
	//2. 建立连接
    sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
	//3. 服务端的socket的fd
    int fd = inputChannel->getFd();
    //将这个connection保存在mConnectionsByFd中,索引为socket的fd
    mConnectionsByFd.add(fd, connection);
	//分发器的looper中添加这个fd
    mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    //唤醒looper,因为连接状态变化了
    mLooper->wake();
    return OK;
}

一个connection对象被创建出来,这个connection表示客户端和服务端之间的一个连接通道,用于传输数据。每个Connection都对应一个服务端的InputChannel,通过这个InputChannel的fd索引,InputDispatcher将所有的connection都保存在mConnectionsByFd中。再将这个fd注册到Looper的监控列表里,这样一旦对socket写入数据,Looper就会被唤醒,接着就会调用回调函数的handleReceiveCallback。由于一个屏幕上可能会有多个Window正在显示,所以一个Dispatcher可能会有多个Connection同时存在。

至此,InputChannel被创建出来,InputChannel两端的socket的fd分别被注册在了所在的Looper中。最后模型大致如下:

InputChannl信道.jpg

2. InputManagerService 服务端从设备中捕获输入事件并派发

我们看完了InputManagerService到APP进程的通信方式,接下来就需要探索:

  1. 如何捕获事件
  2. 如何派发事件

回到在SystemServer中启动InputManagerService的时候,我们提到了一个EventHub但是没有深入探究么?它主要利用Linux的inotify和epoll机制,监听设备事件,例如设备插拔、各种触摸、物理按钮等事件。

EventHub模型.jpg

顾名思义,EventHub就是一个不同设备事件的集线器,它主要面向的是 /dev/input目录下的设备节点,比如 /dev/input/event0 上的事件就是输入事件:

//com_android_server_input_InputManagerService.cpp
NativeInputManager::NativeInputManager(jobject contextObj,
                                       jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();
	//...
    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

初始化InputManager时,将eventHub作为参数传入:

//InputManager.cpp
InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    //1. InputDispatcher在这里初始化的
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    //2. 同时还初始化了一个InputReader,它用于事件读取!
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

//initialize()开启了两个线程,一个接受输入的线程,一个派发线程
void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

源码的注释给了我们InputReaderThread和InputDispatcherThread的定义:

  • InputReaderThread:用于读取并预处理原始输入事件,最后发送消息到dispatcherThread中
  • InputDispatcherThread:阻塞等待新的事件到来,并异步地将他们派发给APP进程。两者之间只有单向通知,即从InputReaderThread将消息发送给InputDispatcherThread,仅此单程。

InputReader:

//InputReader.cpp
bool InputReaderThread::threadLoop() {
    //开启循环
    mReader->loopOnce();
    return true;
}

void InputReader::loopOnce() {
    //...
    //1.从eventhub中获取设备传来的输入事件
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    if (count) {
        //2. 如果读取到了事件,处理该事件
        processEventsLocked(mEventBuffer, count);
    }

    if (inputDevicesChanged) {
        //输入域设备变化了
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }
    //3. 通知派发
    mQueuedListener->flush();
}

从eventhub中读取事件是阻塞的,在eventhub.getEvents()中:

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
	//...
    RawEvent* event = buffer;
    
    //1.从设备中读取数据到readBuffer[]
    //循环读取,从不同的设备device中read()数据到readBuffer[]中
    Device* device = mDevices.valueAt(deviceIndex);
    read(device->fd,readBuffer,sizeof(struct input_event)*capacity);
    //2.将readbuffer中的输入事件input_event放到buffer中,回传回去
    //for循环,从readbuffer中拿到iev,填入event,也就是返回的buffer中
    struct input_event& iev = readBuffer[i];
    event->deviceId = deviceId;
    event->type = iev.type;
    event->code = iev.code;
    event->value = iev.value;
    event += 1;
    //...
    return event-buffer;//返回count,数量。实际数据已经在buffer中了
}

这就简单了,IMS通过其InputReader阻塞地从设备中读取输入信息,并进行数据处理,然后进行事件派发。

InputManager模型.jpg

我们知道InputReader通过EventHub阻塞从设备中读取输入信息,接下来就进行数据处理:

//InputReader.cpp
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    //传入的是之前EventHub从设备中读来的原始输入数据,count为数据个数,因为可能有多个设备
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            int32_t deviceId = rawEvent->deviceId;
            while (batchSize < count) {
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
                        || rawEvent[batchSize].deviceId != deviceId) {
                    break;
                }
                batchSize += 1;
            }
            //批处理设备信息
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            //物理设备变化的处理
            switch (rawEvent->type) {
            case EventHubInterface::DEVICE_ADDED:
                addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::DEVICE_REMOVED:
                removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::FINISHED_DEVICE_SCAN:
                handleConfigurationChangedLocked(rawEvent->when);
                break;
            default:
                ALOG_ASSERT(false); // can't happen
                break;
            }
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}

可以看到InputManager管理的不仅仅是触摸输入,各种输入设备的信息他都可以获取到。我们不细看如何处理消息的了,直接到派发消息的过程。

InputReader读取到数据之后,通过mQueuedListener(这其实是InputDispatcher对象)来唤醒InputDispatcher来派发事件。InputDispatcherThread是一个Looper线程,基于native的Looper实现了Handler消息处理模型,如果有input事件到来就被唤醒,去处理事件,处理完毕后继续睡眠放弃CPU使用权,等待唤醒。我们来看看InputDispactherThread:

//InputDispatcher.cpp
bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}


void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        AutoMutex _l(mLock);
        //被唤醒,来处理Input消息
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }
        //...
    } // release lock

    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    //进入睡眠,等待input事件。
    mLooper->pollOnce(timeoutMillis);
}

InputDispatcherThread的派发逻辑在dispatchOnceInnerLocked(),里面有很多事件分支,我们关注到TYPE_MOTION,也就是触摸事件分支:

//InputDispatcher.cpp
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    switch(mPendingEvent->type){
        case EventEntry::TYPE_CONFIGURATION_CHANGED:
        case ...:
        case EventEntry::TYPE_MOTION:{
            done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
            break;
        }
        //...
    }
}

通过dispatchMotionLocked()来处理触摸事件:

//InputDispatcher.cpp
bool InputDispatcher::dispatchMotionLocked(
    nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
   	//...
    // Identify targets.
    Vector<InputTarget> inputTargets;
    bool conflictingPointerActions = false;
    int32_t injectionResult;
    if (isPointerEvent) {
        //1. 找到目标window
        injectionResult = findTouchedWindowTargetsLocked(currentTime,
                                                         entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
    } else {
        injectionResult = findFocusedWindowTargetsLocked(currentTime,
                                                         entry, inputTargets, nextWakeupTime);
    }
    setInjectionResultLocked(entry, injectionResult);
    //2. 派发事件
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

可以看到,触摸事件首先会通过 findTOuchedWindowTargetsLocked 找到目标 Window,然后再通过 dispatchEventLocked进行消息派发。这下就和WindowManangerService注册Window的功能挂钩了,可以通过判断触摸事件的位置以及窗口属性来确定将事件发送到哪个窗口Window

触摸事件模型.jpg

我们来看到这个寻找窗口的方法findTOuchedWindowTargetsLocked():

//InputDispatcher.cpp
//如果是触摸事件,有pointer_event,进入这里派发
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
                                                        const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
                                                        bool* outConflictingPointerActions) {
    //...
    sp<InputWindowHandle> newTouchedWindowHandle;
    bool isTouchModal = false;
    // Traverse windows from front to back to find touched window and outside targets.
    //1. 遍历windows,找到目标
    size_t numWindows = mWindowHandles.size();
    for (size_t i = 0; i < numWindows; i++) {
        sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
        const InputWindowInfo* windowInfo = windowHandle->getInfo();
        if (windowInfo->displayId != displayId) {
            continue; // wrong display
        }
        int32_t flags = windowInfo->layoutParamsFlags;
        if (windowInfo->visible) {
            if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
                isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
                                         | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
                //2. 如果触摸的点在这个窗口上,记录一下
                if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                    newTouchedWindowHandle = windowHandle;
                    break; // found touched window, exit window loop
                }
            }
        }
    }
}

mWindowHandles表示着所有窗口,根据点击位置与Z轴特性等进行具体确定。这个mWindowHandles是在WMS.addWindow()的时候,通过InputMonitor间接地调用InputDispatcher::setInputWindows来设置的。所以每次窗口变化,InputDispatcher都能获知最新的状态,找到窗口后,InputDispatcher::dispatchMotionLocked()最后通过dispatchEventLocked()进行了事件派发:

//InputDispatcher.cpp
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
    
    pokeUserActivityLocked(eventEntry);

    for (size_t i = 0; i < inputTargets.size(); i++) {
        const InputTarget& inputTarget = inputTargets.itemAt(i);
		//拿到InputChannel连接
        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            //Connection中包含了双方的socket的fd
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            //进入派发
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        } 
    }
}

最后,通过prepareDispatchCycleLocked()进入了消息入队处理:

//InputDispatcher.cpp
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
	//...
    //把消息入队
    enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}


void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
    bool wasEmpty = connection->outboundQueue.isEmpty();

    // Enqueue dispatch entries for the requested modes.
    //根据不同的情况进行入队
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_IS);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);

    // If the outbound queue was previously empty, start the dispatch cycle going.
    //全部事件都入队完成后,派发任务,通知更新
    if (wasEmpty && !connection->outboundQueue.isEmpty()) {
        startDispatchCycleLocked(currentTime, connection);
    }
}

消息入队:

//InputDispatcher.cpp
void InputDispatcher::enqueueDispatchEntryLocked(
    const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
    int32_t dispatchMode) {
    //1. new 一个新的派发事件
    DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, // increments ref
                                                     inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
                                                     inputTarget->scaleFactor);
    //2.根据event的不同进行处理
    //最后入队
    connection->outboudQueue.enqueueAtTail(dispatchEntry);
}

所有消息入队之后,InputDispatcher通过startDispatchCycleLocked()启动connection的事件发送,将队列中的任务逐个发送:

//InputDispatcher.cpp
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {
    //如果是按键:
    status = connection->inputPublisher.publishKeyEvent();
    //如果是触摸
    status = connection->inputPublisher.publishMotionEvent();
}

InputDispatcher->Connection->inputPublisher->InputChannel.sendMessage()->socket.send()

接下来就到了我们之前跟踪到的InputChannel的socket通信了。输入事件从服务端,通过socket发送给了客户端。需要注意的是,APP进程也可能会有多个Socket,因为一个Activity或者Dialog等都会拥有一个Window,而这个Window最终都会通过WMS进行注册,同时在WMS中生成一个WindowState,以及用于输入事件传递的InputChannel。一个APP进程会有多个Window,自然就会有多个InputChannel。每个InputChannel为一个Window服务。

3. 客户端Window的InputChannel接收从服务端传来的输入事件

3.1 注册输入事件的监听器

fd打开通信信道.png

我们回顾到ViewRootImpl的setView()中,除了让WMS.addToDisplay()添加window并建立inputchannel,接着还注册了一个WindowInputEventReceiver:

//ViewRootImpl
public void setView(...){
    res = mWindowSession.addToDisplay();
    if (mInputChannel != null) {
        if (mInputQueueCallback != null) {
            mInputQueue = new InputQueue();
            mInputQueueCallback.onInputQueueCreated(mInputQueue);
        }
        //建立了一个输入事件监听器。
        mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                                                           Looper.myLooper());
    }
}

我们看到这个WindowInputEventReceiver的初始化,它的父类是InputEventReceiver:

//InputEventReceiver
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
	//传入的是这个window添加时创立好的InputChannel
    mInputChannel = inputChannel;
    mMessageQueue = looper.getQueue();
    //调用了native方法
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                              inputChannel, mMessageQueue);

    mCloseGuard.open("dispose");
}

private static native long nativeInit(WeakReference<InputEventReceiver> receiver,
                                      InputChannel inputChannel, MessageQueue messageQueue);

在这里,我们就可以注意到,将APP进程主线程的Looper的消息队列MessageQueue的引用往下传递了,可以猜到,后续有消息,会加入到MessageQueue中去。到了native层进行初始化:

//android_view_InputEventReceiver.cpp
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,jobject inputChannelObj, jobject messageQueueObj) {
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env, receiverWeak, inputChannel, messageQueue);
    //1. NativeInputEventReceiver::initialize();
    status_t status = receiver->initialize();
    return reinterpret_cast<jlong>(receiver.get());
}

status_t NativeInputEventReceiver::initialize() {
    //2. setFdEvent()
    setFdEvents(ALOOPER_EVENT_INPUT);
    return OK;
}

void NativeInputEventReceiver::setFdEvents(int events) {
    if (mFdEvents != events) {
        mFdEvents = events;
        int fd = mInputConsumer.getChannel()->getFd();
        if (events) {
            //将socket客户端的fd添加到主线程的消息池
            mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
        } else {
            mMessageQueue->getLooper()->removeFd(fd);
        }
    }
}

我们还记得一开始添加window的时候创建了客户端和服务端的InputChannel。他们都是在system_server进程中创建的。socket服务端fd保存到了WMS的WindowState的mInputChannel。而socket客户端fd则通过binder通信,传回给了APP进程的ViewRootImpl的mInputChannel。两端都通过格子的Looper监听对端的写操作,一旦对端写入数据,我端收到数据,马上回调响应。例如屏幕有输入,通过socket传到了APP进程,立刻将消息发送到主线程Looper,进行后续的InputEvent分发。

3.2 回调处理InputEvent

APP端事件处理流程

关于回调:

  • 服务端socket收到客户端的消息后,回调的是inputDispatcher.handleReceiveCallback()

  • 客户端socket收到服务端的消息后,回调的是NativeInputEventReceiver.handleEvent()

我们主要关注APP进程如何接受消息。首先在注册监听器的时候,native层通过mMessageQueue->getLooper()->addFd()添加了socket的fd,这部分做了什么呢?如何让looper可以接收到socket消息呢?我们看到Looper的addFd()方法,这是native层的:

//Looper.cpp
int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) {
    return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data);
}

int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {

    // acquire lock
    AutoMutex _l(mLock);

    Request request;
    request.fd = fd;
    request.ident = ident;
    request.events = events;
    request.seq = mNextRequestSeq++;
    request.callback = callback;
    request.data = data;
    if (mNextRequestSeq == -1) mNextRequestSeq = 0; // reserve sequence number -1

    struct epoll_event eventItem;
    request.initEventItem(&eventItem);

    //epoll机制
    int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
    //...
    return 1;
}

当接收到消息之后,会通过LooperCallback回调。Looper根据fd来找到对应的监听器,一个APP进程可能有多个Window,就会有多个InputEventReceiver,所以需要查找。最后调用到其handleEvent来处理对应事件:

//android_view_InputEventReceiver.cpp
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {

    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        //消费事件
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
	//如果是输出事件
    if (events & ALOOPER_EVENT_OUTPUT) {
        //...
    }
    return 1;
}

consumeEvents()中进行消费事件,就是进一步读取事件,封装成java层的对象,传递给java层,进行相应的回调处理:

//android_view_InputEventReceiver.cpp
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
                                                 bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    //...
    for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;
        //1. 获取事件
        status_t status = mInputConsumer.consume(&mInputEventFactory,consumeBatches, frameTime, &seq, &inputEvent, &displayId);
        //...
        //根据事件类型,包装在
        switch (inputEvent->getType()) {
            case AINPUT_EVENT_TYPE_KEY:
                //如果是按键类型:
                inputEventObj = android_view_KeyEvent_fromNative(env,static_cast<KeyEvent*>(inputEvent));
                break;
            case AINPUT_EVENT_TYPE_MOTION: {
                //如果是触摸类型
                MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                break;
            }
                //...
        }
        //回调处理,通过反射来调用Java层的方法,进行回调
        if (inputEventObj) {
            env->CallVoidMethod(receiverObj.get(),
                                gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj,
                                displayId);
            env->DeleteLocalRef(inputEventObj);
        }
    }
}

最后,触摸事件也就被封装成了InputEvent,通过反射回调到InputEventReceiver的dispatchInputEvent进行处理。最后,也就通过ViewPostImeInputState来到了View,进行触摸事件分发。

4. 总结

4.1 触摸事件分发流程

我们知道InputManagerService不仅能捕获触摸InputEvent,还可以捕获各种输入设备的事件,我们这里以触摸事件为例,梳理流程

  • 点击屏幕
  • InputManagerService的InputReader线程通过EventHub捕获输入事件
  • 经过处理后将消息发给InputDispatcher线程
  • InputDispatcher找到触摸的目标窗口,通过InputChannel的 socket将事件发送给客户端的主线程Looper
  • 主线程Looper通过socket的fd,找到对应的窗口,将事件交给这个window
  • 具体window处理具体事件。例如Activity中的某个Button被按下了。

InputManager完整模型.jpg

4.2 Socket 与 binder 的区别

我认为有几点:

  1. 假设使用binder通信,一个APP进程有多个window,为每个window都开启一个binder线程进行监听,binder的内存映射空间可能很快就会到达4M,使得其他必须要binder通信的功能受到影响
  2. binder虽然是“一次拷贝”但还是在内核态、用户态切换了多次,binder的优势在于减少了一次数据拷贝.但触摸事件传递的数据很小,并不需要考虑对减少数据拷贝次数的优化,而且减少了切换内核态的次数,更高效。
  3. Socket可以实现异步通知,只需要客户端服务端各一个线程即可,是全双工,而binder是单工通信,如果要实现异步,服务端和客户端各要两个线程。如此看来,为了满足全双工异步通知,socket需要的线程明显少于binder,所以socket更为高效.
  4. 此外,socket基于tcp,还保证了消息的有序性、可靠性。
  5. 像AMS这种与APP进程就用的是binder通信,因为中间可能要通过Intent来传递parcel数据,这个数据虽然不超过1M,但相对于触摸消息来说已经非常大了,如果也用socket,将会进行两次的数据拷贝,所以它选择用binder通信,通过内存映射来完成只需要一次的”数据拷贝“

5. 参考文章

  1. 十分钟了解Android触摸事件原理(InputManagerService)

  2. Android Input(五)-InputChannel通信

  3. Android Input子系统为什么要使用socket,而不是binder?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/381029.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

03 Android基础--fragment

03 Android基础--fragment什么是fragment&#xff1f;fragment生命周期&#xff1f;动态的fragment与静态的fragmentfragment常用的两个类与APIFragment与Activity通信什么是fragment&#xff1f; 碎片&#xff0c;一个activity中可以使用多个fragment&#xff0c;可以把activi…

应用模型开发指南上新介绍

Module、HAP、Ability、AbilitySta-ge、Context……您是否曾经被这些搞不懂又绕不开的知识点困扰&#xff1f; 现在&#xff0c;全新的《应用程序包基础知识》及《应用模型开发指南》为您答疑解惑&#xff01; 这里有您关注的概念解析、原理机制阐述&#xff0c;也有丰富的…

gitlab+idea回退代码并提交到新分支

目录结构前言idea创建新分支查看代码提交记录使用IntelliJ IDEA获取使用Git Bash Here获取代码回退到指定版本回退执行命令行使用IntelliJ IDEA实现使用Git Bash Here实现回退完成验证idea提交指定版本代码验证分支代码推动成功前言 IntelliJ IDEA GitLab开发过程中需将代码回…

ajax调用restful接口

HTTP动词对应操作POST新增信息GET获取信息PUT更新信息DELETE删除信息一、POST-----新增信息 1. 后台接口 PostMapping(value "/save") public String save(RequestBody(required true) Emp emp){System.err.println(emp.toString());// 将数据信息存库empService.…

Android开发面试【金三】——启动优化

前言 一下子来到了&#xff0c;面试的高潮季。金三银四的三月份&#xff1b;在我们Android开发的众多面试中&#xff0c;扑面而来的超多面试题难道很多程序员。 Android的性能优化&#xff0c;主要是从以下几个方面进行优化的&#xff1a; 稳定&#xff08;内存溢出、崩溃&am…

安全认证--JWT介绍及使用

安全认证--JWT介绍及使用1.无状态登录原理1.1.什么是有状态&#xff1f;1.2.什么是无状态1.3.如何实现无状态1.4.JWT1.4.1.简介1.4.2.数据格式2.编写JWT工具2.1.添加JWT依赖2.2.载荷对象2.3.工具2.4.测试2.4.1.配置秘钥2.4.2.测试类2.5项目源码1.无状态登录原理 有状态登录和无…

G1D54-CRF

一、CRF的输入X是什么&#xff1f;是构造的特征吗&#xff1f; 如此&#xff0c;CRF的x只用于状态函数吗&#xff1f; CRF的例子解释调用代码 机器之心 知乎忆榛 此处线性链条件随机场的特征函数形式被统一了&#xff1f; BilstmCRF&#xff0c;强烈推荐&#xff01;&#x…

AM402和SV660N、IS620N运动控制

软件&#xff1a;InoProShop(V1.7.3) 1、添加EtherCAT伺服从站 2、PLC运动控制程序和ETHERCAT在一个任务中。 3、编码器脉冲设置。 注意电机转速值是以秒还是分钟计量单位。 SV660N IS620N 4、设置电机停机方式。使用sin停机效果比较圆滑&#xff0c;默认梯形。 5、库管理器…

广和通携手联发科技正式发布基于MediaTek T830 平台5G模组FG370的可快速落地FWA解决方案

2月28日&#xff0c;全球领先的物联网无线通信解决方案和无线通信模组提供商广和通正式宣布&#xff1a;新一代5G模组FG370已率先实现量产&#xff0c;并于2023世界移动通信大会&#xff08;MWC Barcelona 2023&#xff09;期间携手联发科技正式发布基于FG370的FWA解决方案&…

十三、MyBatis的缓存

缓存&#xff1a;cache 缓存的作用&#xff1a;通过减少IO的方式&#xff0c;来提高程序的执行效率。 mybatis的缓存&#xff1a;将select语句的查询结果放到缓存&#xff08;内存&#xff09;当中&#xff0c;下一次还是这条select语句的话&#xff0c;直接从缓存中取&#xf…

数字信号复习题纲

数字信号复习题纲一、希尔伯特变换器&#xff08;:heavy_check_mark: &#xff09;1. 什么是希尔伯特变换器&#xff1f;2. 试证明信号通过希尔伯特变换器后的输出二、能量信号的自相关函数、卷积运算与能量谱&#xff08;:heavy_check_mark:&#xff09;1. 能量信号2. 试证明自…

webpack配置完全指南

前言 对于入门选手来讲&#xff0c;webpack 配置项很多很重&#xff0c;如何快速配置一个可用于线上环境的 webpack 就是一件值得思考的事情。其实熟悉 webpack 之后会发现很简单&#xff0c;基础的配置可以分为以下几个方面&#xff1a; entry 、 output 、 mode 、 resolve …

深入理解Storm 之 TridentStrom

从Demo讲起: FixedBatchSpout spout new FixedBatchSpout(new Fields("sentence"), 3, new Values("the cow jumped over the moon"),new Values("the man went to the store and bought some candy"),new Values("four score and seven …

新库上线 | CnOpenData中国债券市场债券信息数据

中国债券市场债券信息数据 一、数据简介 债券是政府、企业、银行等债务人为筹集资金&#xff0c;按照法定程序发行并向债权人承诺于指定日期还本付息的有价证券。债券购买者或投资者与发行者之间是一种债权债务关系&#xff0c;债券发行人即债务人&#xff0c;投资者&#xff…

关于 python 的异常使用说明 (python 的文件和异常)

文章目录异常1. 处理异常 ZeroDivisionError 异常2. 使用 try-except 代码块3. 使用异常避免崩溃4. else 代码块5. 处理 FileNotFoundError 异常6. 分析文本7. 失败时一声不吭异常 pyhong 使用被异常成为异常的特殊对象来管理程序执行期间发生的错误。 每当发生让 python 不知所…

【计算机网络:自顶向下方法】Chapter5 网络层:控制平面

本系列文章为笔者在学习b站中科大郑烇老师的计算机网络课程时&#xff08;郑老师讲得很清晰&#xff01;&#xff01;&#xff09;&#xff0c;结合课程PPT与《计算机网络&#xff1a;自顶向下方法》&#xff08;第七版&#xff09;所作的学习笔记&#xff0c;部分图片源自课程…

gitee 奇安信代码卫士使用

注册 gitee 账号后&#xff0c;push 一个项目&#xff0c;或者 fork 一个别人的项目&#xff0c;这里 fork 了一个 java-sec-code 靶场&#xff0c;使用的是个人版&#xff0c;像是低配版的 fortify 在项目的 服务 项下&#xff0c;选择奇安信代码卫士 创建分析 新建分析&…

【Java|golang】2373. 矩阵中的局部最大值

给你一个大小为 n x n 的整数矩阵 grid 。 生成一个大小为 (n - 2) x (n - 2) 的整数矩阵 maxLocal &#xff0c;并满足&#xff1a; maxLocal[i][j] 等于 grid 中以 i 1 行和 j 1 列为中心的 3 x 3 矩阵中的 最大值 。 换句话说&#xff0c;我们希望找出 grid 中每个 3 x …

操作系统笔记、面试八股(一)—— 进程、线程、协程

文章目录1. 进程、线程、协程1.1 进程1.1.1 进程间的通信方式1.1.2 进程同步方式1.1.3 进程的调度算法1.1.4 优先级反转1.1.5 进程状态1.1.6 PCB进程控制块1.1.7 进程的创建和撤销过程1.1.8 为什么要有进程1.2 线程1.2.1 为什么要有线程1.2.2 线程间的同步方式1.3 协程1.3.1 什…

创建Firebase项目并接入Firebase推送: Firebase Cloud Messaging (FCM)

1.FCM简介&#xff1a;Firebase Cloud Messaging (FCM) 是一种跨平台消息传递解决方案&#xff0c;可供您可靠地传递消息&#xff0c;而且还是免费的服务。支持 Android&#xff0c;IOS,Web,Flutter,Unity.消息类型可以使用 FCM 向客户端发送两种类型的消息&#xff1a;通知消息…