前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Event在Framework层的处理

Android Event在Framework层的处理

原创
作者头像
汪毅雄
修改2019-10-14 22:31:36
1.1K0
修改2019-10-14 22:31:36
举报
文章被收录于专栏:汪毅雄的专栏汪毅雄的专栏

Android的事件有好几类,我们遇到最多的就是Touch事件。这部分和其他模块非常相似,系统有一个核心的Service来接收这些事件,通过IPC把事件分发用户进程,也就是相应的注册者,这部分虽然相似但也有不同。

1、为什么直接采用共享内存 --- 个人理解

通常情况Android的IPC采用的都是Binder,而在这里采用的是共享内存(Shared Memory)。为什么这么设计呢,实现过View的Touch事件的人都知道,touch的事件是非常频繁的,且要求实时性很高。而Binder是在共享内存基础上,加了一层安全性高、支持C/S的保护壳,所以它更加的heavy。对于频繁的内存操作,其效率不及共享内存。

那它是怎么实现C/S的呢?它采用的是pipe。pipe按道理不应该有2次数据拷贝吗?是的,但是如果传递只是一个字符,那就无关紧要了。能这么做是,事件处理的共享内存数据管理不涉及和内存的交互。也就是说共享内存和pipe实际是活在两个世界,它们的连接是通过2次单个字符数据的拷贝,这样效率就会比binder高不少。

2、总体架构

事件采集:按键、touch、mouse这些硬件通过驱动把事件写入Linux的/dev/input目录下,不同的设备会在里面存入为event0、event1等这样的格式。

预处理:清理Android无用数据,保存有用数据。

WMS分发:有权利监听各种的事件,一般都是当前resume的view。哪个view是resume的呢?写的上一篇文章中《 Android View和Window的关系 》有提到,这个由WMS控制,因此有一个WMS分发的过程。

用户进程处理:也就是各种view的event dispatch。

3、InputManagerService监听事件

事件Service也就是InputManagerService和其他Service启动一样,系统会率先孵化,在其start方法中,它首先会调用nativeStart,而在nativeStart中,我们直接看c代码吧

代码语言:javascript
复制
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    status_t result = im->getInputManager()->start();
}

 这一步调用了InputManager的start方法。

InputManager.cpp # start

代码语言:javascript
复制
status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher",
            PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);
    result = mReaderThread->run("InputReader",
            PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE);
}

 这一步,InputManager开启了两个线程,一个Reader线程、一个Dispatch线程。顾名思义,一个读取、一个分发。读取是读取event节点中的数据,分发则是把事件发送给对应进程对应的接收者。

3.1 native的事件读取

代码语言:javascript
复制
bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
}

 InputReader#loopOnce

代码语言:javascript
复制
void InputReader::loopOnce() {
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);   
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }
}

 可以看到其会通过EventHub去读去event节点中发生的事件次数,如果大于0,则执行processEventsLocked

代码语言:javascript
复制
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            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;
            }
        }
    }
}

 这是对事件的第一步处理,遍历所有的事件,然后根据事件的类型做出相应的处理,普通的event会执行processEventForDeviceLocked把事件交给相应的deviceId。接下来的堆栈如下:

processEventForDeviceLocked

process

InputMapper->process

InputMapper->processKey

代码语言:javascript
复制
getListener()->notifyKey(&args);

 这里它会调用getListener的来通知事件,而这里的listener则是在InputReader构造的时候的时候传入的Dispatcher。

InputReader构造函数

代码语言:javascript
复制
InputReader::InputReader(const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& policy,
        const sp<InputListenerInterface>& listener) :
        mContext(this), mEventHub(eventHub), mPolicy(policy),
        mGlobalMetaState(0), mGeneration(1),
        mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
        mConfigurationChangesToRefresh(0)

 InputManager构造函数

代码语言:javascript
复制
InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
}

 通过这两个构造函数可以清楚的看到,InputDispatch在InputReader注册了一个事件回调接口。所以Reader收到事件后经过一定的处理后,最终处理者还是dispatcher。

3.2 native的事件分发

接上一步,dispatcher收到回调后,会经过一系列堆栈后,最后走到dispatchKeyLocked方法中。

代码语言:javascript
复制
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    //判断条件,如文章所写。。。
    Vector<InputTarget> inputTargets;
    int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
            entry, inputTargets, nextWakeupTime);
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
        return false;
    }
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

 在这个方法中,要执行到以上代码,需经过一系列判断

1、检查事件是否重复

2、检查是否满足INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER

3、给一个拦截事件的机会,比如说对于home键WMS的优先处理

4、判断是否丢弃事件

如果以上都不return,则会走如上的代码中的findFocusedWindowTargetLocked。

代码语言:javascript
复制
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
        const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
    if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) {
        injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
        goto Failed;
    }
    reason = checkWindowReadyForMoreInputLocked(currentTime,
            mFocusedWindowHandle, entry, "focused");

    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
    addWindowTargetLocked(mFocusedWindowHandle,
            InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
            inputTargets);
    return injectionResult;
}

 看上面代码,我们可以看到它这么执行:

1、检查权限

2、检查是否正在处理中

3、交给当前focus的handle处理

我们在这个方法先停一会儿,看看InputDispatcher是怎么获取到这个mFocusedWindowHandle呢?

4、WMS的分发处理

显然,事件的处理者肯定是那些resume的view,所以开发人员在设计的时候,也肯定会在resume的时候把InputDispatcher传递进来。

上一篇文章中,我们知道当一个view resume的时候,WMS会调用addWindow方法。而在addWindow方法中,有一个很重要的代码,就是调用InputMonitor的updateInputWindowLw方法。InputMonitor的角色是WMS和InputDispatcher的中介。

代码语言:javascript
复制
public void updateInputWindowsLw(boolean force) {
        if (inDrag) {
            final InputWindowHandle dragWindowHandle = mService.mDragState.mDragWindowHandle;
            if (dragWindowHandle != null) {
                addInputWindowHandleLw(dragWindowHandle);
            } 
        }
        if (inPositioning) {
            final InputWindowHandle dragWindowHandle = mService.mTaskPositioner.mDragWindowHandle;
            if (dragWindowHandle != null) {
                addInputWindowHandleLw(dragWindowHandle);
            } 
        }
        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
                if (addInputConsumerHandle
                        && inputWindowHandle.layer <= mService.mInputConsumer.mWindowHandle.layer) {
                    addInputWindowHandleLw(mService.mInputConsumer.mWindowHandle);
                }
                if (addWallpaperInputConsumerHandle) {
                    if (child.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER) {
                        addInputWindowHandleLw(mService.mWallpaperInputConsumer.mWindowHandle);
                    }
                }
                addInputWindowHandleLw(
                        inputWindowHandle, child, flags, type, isVisible, hasFocus, hasWallpaper);
            }
        }
        if (addWallpaperInputConsumerHandle) {
            addInputWindowHandleLw(mService.mWallpaperInputConsumer.mWindowHandle);
        }
        mService.mInputManager.setInputWindows(mInputWindowHandles);
    }

 这一步是把所有的可以接收事件的view的Handle通过addInputWindowHandlerLw加到mIntputWindowHandles这个成员数组变量中。最后调用setInputWindows,把数组交给native处理,也就是如下的代码。

代码语言:javascript
复制
public void setInputWindows(InputWindowHandle[] windowHandles) {
        nativeSetInputWindows(mPtr, windowHandles);
    }
代码语言:javascript
复制
static void nativeSetInputWindows(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobjectArray windowHandleObjArray) {
    im->setInputWindows(env, windowHandleObjArray);
}
代码语言:javascript
复制
void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray) {
    Vector<sp<InputWindowHandle> > windowHandles;
        jsize length = env->GetArrayLength(windowHandleObjArray);
        for (jsize i = 0; i < length; i++) {
            sp<InputWindowHandle> windowHandle =
                    android_server_InputWindowHandle_getHandle(env, windowHandleObj);
                windowHandles.push(windowHandle);
        }
    mInputManager->getDispatcher()->setInputWindows(windowHandles);
}

 可以看到,WMS在addWindow的时候,最终会把所有的Window往InputManagerService塞,然后再交给native的InputDispatcher处理。

代码语言:javascript
复制
void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
        Vector<sp<InputWindowHandle> > oldWindowHandles = mWindowHandles;
        mWindowHandles = inputWindowHandles;
        sp<InputWindowHandle> newFocusedWindowHandle;
        for (size_t i = 0; i < mWindowHandles.size(); i++) {
            const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
            if (!windowHandle->updateInfo() || windowHandle->getInputChannel() == NULL) {
                mWindowHandles.removeAt(i--);
                continue;
            }
            if (windowHandle->getInfo()->hasFocus) {
                newFocusedWindowHandle = windowHandle;
            }
            if (windowHandle == mLastHoverWindowHandle) {
                foundHoveredWindow = true;
            }
        }
        if (!foundHoveredWindow) {
            mLastHoverWindowHandle = NULL;
        }
        if (mFocusedWindowHandle != newFocusedWindowHandle) {
            mFocusedWindowHandle = newFocusedWindowHandle;
        }
}

 而InputDispatcher的处理也比较容易理解,遍历所有的WindowHandle,把最上层hasFocus的取出来,保存在上一步所说的mFocusedWindowHandle中,这样整个流程就通了。

主要的一些步骤就是:

1、InputManagerService启动

2、唤起InputReader和InputDispatcher,InputReader去不断轮询Event节点。

3、ViewRootImpl调用setView后,把当前Focus的Window信息交给WMS,再由WMS转交给IMS,最终交给InputDispatcher。

4、InputReader收到Event后,回调给InputDispatcher处理,再回调给InputMonitor,最后交给相应的view。

文章一开始提到,事件处理的IPC采用的是共享内存+pipe,这部分在哪儿体现呢?

5、管道的建立

我们回到WMS的addWindow方法,我们可以看到这么一段代码:

代码语言:javascript
复制
final boolean openInputChannels = (outInputChannel != null
                    && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
            if  (openInputChannels) {
                win.openInputChannel(outInputChannel);
            }
代码语言:javascript
复制
void openInputChannel(InputChannel outInputChannel) {
        String name = makeInputChannelName();
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
}
代码语言:javascript
复制
public static InputChannel[] openInputChannelPair(String name) {
        return nativeOpenInputChannelPair(name);
    }

 可以看到,这边先为通道起名,然后调用native方法打开一个通道。在native端,它会调用InputTransport中的方法。

代码语言:javascript
复制
status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    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));

    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);

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

 这块可以很清晰的看得到,分别设置了缓存区大小,然后开启了两个channel,而channel中的mFd实际上则是socket编号!

事件处理在Framework层的一些主要内容就是这些了

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、为什么直接采用共享内存 --- 个人理解
  • 2、总体架构
  • 3、InputManagerService监听事件
  • 4、WMS的分发处理
  • 5、管道的建立
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档