专栏首页汪毅雄的专栏Android Event在Framework层的处理
原创

Android Event在Framework层的处理

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代码吧

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

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的事件读取

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
}

 InputReader#loopOnce

void InputReader::loopOnce() {
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);   
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }
}

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

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

getListener()->notifyKey(&args);

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

InputReader构造函数

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构造函数

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方法中。

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。

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的中介。

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处理,也就是如下的代码。

public void setInputWindows(InputWindowHandle[] windowHandles) {
        nativeSetInputWindows(mPtr, windowHandles);
    }
static void nativeSetInputWindows(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobjectArray windowHandleObjArray) {
    im->setInputWindows(env, windowHandleObjArray);
}
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处理。

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方法,我们可以看到这么一段代码:

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

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

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层的一些主要内容就是这些了

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

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深入理解 Android 消息机制原理

    本文讲述的是 Android 的消息机制原理,从 Java 到 Native 代码进行了梳理,并结合其中使用到的Epoll模型予以介绍。

    汪毅雄
  • 机器学习之SVM原理

    相信了解机器学习的同学都知道,SVM的“完美强迫症”使得其在各大模型中,几乎是一个“统治性”的地位。但是也不是那么绝对啦,SVM比较耗时,因此不适合那些超大样本...

    汪毅雄
  • 怎么理解凸优化及其在SVM中的应用

    凸优化理论广泛用于机器学习中,也是数学规划领域很重要的一个分支,当然也是很复杂的。本文总结一下我获取的资料和个人在一些难点上的理解。

    汪毅雄
  • 从gem5到ASIP,如何打造一款自己的交换芯片模拟器?

    长期以来,在设计芯片时经常遇到这样的困惑,采用传统流程设计某种类型的芯片时周期很长,某些模块的特点至少等到进行FPGA验证阶段才能分析其性能,如果不合适,还需要...

    网络交换FPGA
  • 教程 | 详解支持向量机SVM:快速可靠的分类算法

    选自Monkey Learn 作者:Bruno Stecanella 参与:李泽南、李亚洲 当处理文本分类问题时,你需要不断提炼自己的数据集,甚至会尝试使用朴素...

    机器之心
  • docker镜像部署zookeeper

    由截图可知,服务器使用的镜像版本低于zookeepe客户端使用的版本,版本不兼容,导致客户端无法正确连接。

    3号攻城狮
  • 你了解matlab局部函数吗?

    什么是局部函数呢?局部函数就是在某个局部范围内起作用的函数,超出作用范围,将不能被使用。通过添加局部函数,可以避免创建和管理单独的函数文件,可使代码编写一气呵成...

    艾木樨
  • Python之Scrapy框架当当网口红爬虫

    今天小编给大家带来的是使用 Python 的 scrapy 框架快速写一个“当当网”的"口红"商品的名称,价格,评论人数,链接的爬虫,存储数到 json 文件,...

    用户6825444
  • Matlab基本语法7

    基本编程技巧 脚本m文件和函数m文件,脚本是一系列命令、语句的简单组合。脚本文件中的变量都是全局变量,程序运行后,这些变量保存在matlab的基本工作空间内,一...

    anytao
  • Carbon:交互式反汇编工具

    Cerbero Suite是为x86/x64设计的一款交互式反汇编工具。最初的目的是为了让我们的用户能够检查内存转储中的代码以及shellcode。如今,市面上...

    FB客服

扫码关注云+社区

领取腾讯云代金券