0. Android 输入事件分发概述

当用户触屏后,InputReader 从驱动读取一个输入事件加入到队列,InputDispatcher 从队列中读取一个输入事件准备分发。

如果该输入事件是一个触摸事件,则先查找最上层可接受该 Touch 的窗口。然后通过 Socket 发送给对应的 InputConsumer。

InputConsumer 会发送给相应的 InputEventReceiver 实现类。该实现类中分发给每一个 InputStage 责任链,InputStage 实现类会最终处理该输入事件。

参考「InputChannel and InputDispatcher in Android」。

Gityuan 的「Input 系统—事件处理全过程」有个整体框架图如下:

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a90cde26-e35a-43f8-b902-21cf166b342e/Untitled.png

1. 查找接收 Touch 的窗口

一个触摸事件该不该发给一个窗口,取决于该窗口的可触摸区域是否包含该触摸的坐标,且没有被别的窗口的可触摸区域盖住。

该逻辑实现在 findTouchedWindowAtLocked() 函数中,它会遍历所有的 InputWindowHandle,然后调用 windowInfo->touchableRegionContainsPoint(x, y) 检查可触摸区域是否包含该触摸点坐标。如果满足则返回。

// frameworks/native/services/inputflinger/InputDispatcher.cpp
sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId,
        int32_t x, int32_t y, bool addOutsideTargets, bool addPortalWindows) {
    // Traverse windows from front to back to find touched window.
    const std::vector<sp<InputWindowHandle>> windowHandles = getWindowHandlesLocked(displayId);
    for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
        const InputWindowInfo* windowInfo = windowHandle->getInfo();
        if (windowInfo->displayId == displayId) {
            int32_t flags = windowInfo->layoutParamsFlags;
            if (windowInfo->visible) {
                if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
                    bool isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
                            | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
                    if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                        ...
                        // Found window.
                        return windowHandle;
                }}
                ...
    }}}
    return nullptr;
}

这里为什么会叫可触摸区域(Touchable Region),而不是窗口大小,是因为可触摸区域可以和显示窗口不一样,甚至可以由多个矩形组成(一个显示窗口只能是一个矩形)。

InputWindowHandle 持有 InputWindowInfo 对象,InputWindowInfo 有成员 touchableRegion。它是 Region 类对象,注意是在 framework/native/include/ui/Region.h 中声明的 Region 类。

// frameworks/native/include/input/InputWindow.h
struct InputWindowInfo {
    ...
    Region touchableRegion;
    void addTouchableRegion(const Rect& regin);
    bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
    ...
}

class InputWindowHandle : public RefBase {
public:
    inline const InputWindowInfo* getInfo() const {
        return &mInfo;
    }
    ...
protected:
    InputWindowInfo mInfo;
};

// frameworks/native/include/ui/Region.h
class Region : public LightFlattenable<Region>
{
public:
    inline bool contains(int x, int y) const;
    ...
    // STL-like iterators
    typedef Rect const* const_iterator;
    const_iterator begin() const;
    const_iterator end() const;
    ...
    // mStorage is a (manually) sorted array of Rects describing the region
    // with an extra Rect as the last element which is set to the
    // bounds of the region. However, if the region is
    // a simple Rect then mStorage contains only that rect.
    Vector<Rect> mStorage;
};

Region 类的成员 mStorage 是一个 Vector<Rect> 对象,从这里也可以知道可触摸区域由多个矩形组成的。

2. 可触摸区域的计算

InputWindowInfo 结构体中的 touchableRegion 其实是从 Java 层过来的,是 Java InputWindowHandle 类的 touchableRegion 成员。

// frameworks/base/core/java/android/view/InputWindowHandle.java
public final class InputWindowHandle {
    ...
    public final Region touchableRegion = new Region();
    ...
}

这个 touchableRegion 是在 UpdateInputForAllWindowsConsumer 的 updateInputWindows() 方法中遍历 DisplayContent 的所有 WindowState 时计算的。