View事件分发

调用dispatchTouchEvent方法分发事件
调用onInterceptTouchEvent拦截事件
调用onTouchEvent来处理事件


应用层

关键方法和成员变量:

  • dispatchTouchEvent( ) / handled (boolean类型,作为dispatchTouchEvent内部变量和返回结果)

  • onInterceptTouchEvent( )

  • dispatchTransformedTouchEvent( ) / mFirstTouchTarget

  • onTouchEvent( )





onTouchListener优先级比onTouchEvent要高,onTouchEvent内有onClick的逻辑,也可以说onTouchListener优先级比onClick要高

不管有没有拦截事件,如果没有消费DOWN事件,则mFirstTarget = null,则接下来的MOVE和UP,都会走intercepter=true(由于intercepted = true;导致不会进入mFirstTouchTarget赋值逻辑,即mFirstTouchTarget=null,走super.dispatchTouchEvent,即自身onTouchEvent逻辑)逻辑,所以走的是DecorView的onTouchEvent( ),即Activity的dispatchTouchEvent返回false,继续走Activity的onTouchEvent( )

哪个view的onTouchEvent( )消费了事件,则接下来的MOVE和UP都会走到这个view


mFirstTouchTarget 有什么作用

非多点触控:mFirstTouchTarget链表退化成单个TouchTarget对象。

多点触控,目标相同:同样为单个TouchTarget对象,只是pointerIdBits保存多个pointerId信息。

多点触控,目标不同:mFirstTouchTarget成为链表。

  • 1.为什么要把mFirstTouchTarget设计成链表
    是用于记录多点触控情况下,多目标控件的派分逻辑。

  • 2.记录目标的TouchTarget的pointerIdBits又起到什么作用。
    配合mFirstTouchTarget,使多点触控时,同个目标可以对多个触控点进行合理的处理逻辑。

  • 3.按下A,再按下A(多点触控),为什么释放后A的点击事件只会触发一次。
    ACTION_DOWN被A消耗,ACTION_POINTER_DOWN也被A消耗,此时相当于A是2个触控点的目标元素。
    当释放任意一个触控点时,对应的事件是ACTION_POINTER_UP而不是ACTION_UP,导致不产生点击事件。

  • 4.按下A,按下VG(空白区域),为什么先释放A,却无法触发A的点击事件,继续释放VG,又会触发A的点击事件。
    这里属于ACTION_POINTER_DOWN事件
    ACTION_POINTER_DOWN无法找到目标时视为ACTION_DOWN目标接收派分

  • 5.按下VG(空白区域),再点击A,B无响应。
    这里属于ACTION_POINTER_DOWN事件
    当点击VG空白位置时,由于不存在消耗ACTION_DOWN的子控件,导致mFirstTouchTarget为空。任何后续事件的派分,都会由于拦截标记intercepted = true而被拦截,包括多点触控ACTION_POINTER_DOWN事件。


FrameWork层

1.1 硬件与内核部分

当屏幕被触摸,Linux内核会将硬件产生的触摸事件包装为Event存到/dev/input/event[x]目录下。
这样做的目的是将输入事件封装为通用的Event,供后续处理。

1.2 SystemServer部分

当系统启动时,在SystemServer进程会启动一系列系统服务,如AMS,WMS等。
其中还有一个就是我们管理事件输入的InputManagerService。
在其内部,会启动一个读线程,也就是InputReader,它会从系统也就是/dev/input/目录拿到任务,并且分发给InputDispatcher线程,然后进行统一的事件分发调度。

1.3 跨进程通信传递给App

App中的Window与InputManagerService之间的通信实际上使用的InputChannel,InputChannel是一个pipe,底层实际是通过socket进行通信。
在Activity启动时会调用ViewRootImpl.setView()。在ViewRootImpl.setView()过程中,也会同时注册InputChannel

最终在SystemServer进程中,WindowManagerService根据当前的Window创建了SocketPair用于跨进程通信,同时并对App进程中传过来的InputChannel进行了注册。这之后,ViewRootImpl里的InputChannel就指向了正确的InputChannel, 作为Client端,其fd与SystemServer进程中Server端的fd组成SocketPair, 它们就可以双向通信了。

App进程的主线程就会监听socket客户端,当收到消息(输入事件)后,回调NativeInputEventReceiver.handleEvent()方法,最终会走到InputEventReceiver.dispachInputEvent方法。(终于拿到输入事件)

2.1 事件回传到ViewRootImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//InputEventReceiver.java
private void dispatchInputEvent(int seq, InputEvent event) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event); 
}

//ViewRootImpl.java ::WindowInputEventReceiver
final class WindowInputEventReceiver extends InputEventReceiver {
    public void onInputEvent(InputEvent event) {
       enqueueInputEvent(event, this0true); 
    }
}

//ViewRootImpl.java
void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    adjustInputEventForCompatibility(event);
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;

    if (processImmediately) {
        doProcessInputEvents(); 
    } else {
        scheduleProcessInputEvents();
    }
}

ViewRootImpl不仅负责界面的绘制,同时负责事件的传递。

2.2 第一次责任链分发

接下来走到doProcessInputEvents中,其中涉及到事件分发中的第一次责任链分发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void doProcessInputEvents() {
    ...
    // Deliver all pending input events in the queue.
    while (mPendingInputEventHead != null) {
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        deliverInputEvent(q);
    }
    ....
}

private void deliverInputEvent(QueuedInputEvent q) {
    InputStage stage;
    ....
    //stage赋值操作
    ....
    if (stage != null) {
        stage.deliver(q);
    } else {
        finishInputEvent(q);
    }
}

1.QueuedInputEvent是一种输入事件,链表结构,遍历传递给InputStage。
2.InputStage是处理输入的责任链,在调用deliver时会遍历责任链传递事件。

2.3 组装责任链

回到ViewRootImpl.setView方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
// Set up the input pipeline.
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);

mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
....
}
}

其中ViewPostImeInputStage:视图输入处理阶段,比如按键、手指触摸等运动事件,我们熟知的view事件分发就发生在这个阶段。

事件到达应用端的主线程,会通过ViewRootImpl进行一系列InputStage来处理事件。这个阶段其实是对事件进行一些简单的分类处理,比如视图输入事件,输入法事件,导航面板事件等等。

我们的View触摸事件就发生在ViewPostImeInputStage阶段。ViewRootImpl中的mView就是DecorView。