Android 输入事件一撸到底之源头活水(1)

前言

Android 不只是展示静态页面,更多的是与用户的交互。用户通过触摸屏幕与App互动,提供了更好的用户体验。而在App层我们需要接收屏幕的触摸事件进行相应的逻辑操作,本系列文章将分析App层输入事件兜兜转转的一生。
本系列分为三篇文章讲述:

1、Android 输入事件一撸到底之源头活水(1)
2、Android 输入事件一撸到底之DecorView拦路虎(2)
3、Android 输入事件一撸到底之View接盘侠(3

image.png

通过本篇文章,你将了解到:

1、输入事件从哪分发到App层
2、输入事件分发责任链
3、Touch/Key事件处理
4、Root View 接收事件

输入事件从哪分发到App层

输入事件从底层输入

屏幕触摸产生的事件由底层处理,最后分发到对应的Window,我们需要找到底层与Window的桥梁。找到InputEventReceiver类:

InputEventReceiver.java
    private void dispatchInputEvent(int seq, InputEvent event) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        //处理输入事件
        onInputEvent(event);
    }

底层封装输入事件为:InputEvent。
InputEventReceiver 为抽象类,其子类为ViewRootImpl 内部类WindowInputEventReceiver:

WindowInputEventReceiver.java
    @Override
    public void onInputEvent(InputEvent event) {
        ...
        if (processedEvents != null) {
            ...
        } else {
            //加入输入队列
            enqueueInputEvent(event, this, 0, true);
        }
    }

onInputEvent 为重写InputEventReceiver 方法。

App与底层建立联系

上面分析了底层输入事件最终传送给了ViewRootImpl 里的WindowInputEventReceiver,底层怎么就知道传给了它呢?
来看看ViewRootImpl类:

ViewRootImpl.java
    InputChannel mInputChannel;
    WindowInputEventReceiver mInputEventReceiver;
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        ...
        if ((mWindowAttributes.inputFeatures
                & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            //建立输入通道
            mInputChannel = new InputChannel();
        }
        try {
            ...
            //InputChannel与WindowManagerService建立联系,也就是以后输入事件发送给这个Window
            res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                    mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                    mTempInsets);
            setFrame(mTmpFrame);
        } catch (RemoteException e) {
           ...
        }
        if (mInputChannel != null) {
            if (mInputQueueCallback != null) {
                mInputQueue = new InputQueue();
                mInputQueueCallback.onInputQueueCreated(mInputQueue);
            }
            //将输入通道与接收端绑定起来
            //mInputEventReceiver 作为接收端,底层数据通过mInputChannel 发送接收端
            //当前Looper为主线程Looper,Looper里有MessageQueue
            mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                    Looper.myLooper());
        }
    }

来看看mWindowSession.addToDisplay(xx)方法:

Session.java
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
                            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
                            Rect outStableInsets, Rect outOutsets,
                            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
                            InsetsState outInsetsState) {
        //mService 为WindowManagerService 对象
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
                outInsetsState);
    }

再来看看WindowManagerService addWindow 方法:

WindowManagerService.java
    public int addWindow(xx) {
        ...
        final boolean openInputChannels = (outInputChannel != null
                && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);
        }
        ...
    }

最终调用:

WindowState.java
    void openInputChannel(InputChannel outInputChannel) {
        ...
        //注册输入通道
        mWmService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());
    }

注册上层传递过来的channel,这样子该channel与Window建立了联系。
既然建立了联系,再来看看如何channel如何出传递数据给上层呢?
来看InputEventReceiver构造函数:

InputEventReceiver.java
    public InputEventReceiver(InputChannel inputChannel, Looper looper) {
        ...
        mInputChannel = inputChannel;
        mMessageQueue = looper.getQueue();
        //将MessageQueue传递给底层
        //当底层有数据时将会发送到该队列里
        //最终消息循环取出执行
        //调用InputEventReceiver 的dispatchInputEvent方法
        //因此执行 dispatchInputEvent 方法时已经在主线程
        mReceiverPtr = nativeInit(new WeakReference(this),
                inputChannel, mMessageQueue);

        mCloseGuard.open("dispose");
    }

Looper相关请移步:Android事件驱动Handler-Message-Looper解析

输入事件分发责任链

既然输入事件已经分发到App对应的Window上,那么来看看如何处理这些事件的,从WindowInputEventReceiver onInputEvent(xx)方法看起。该方法调用了ViewRootImpl 的enqueueInputEvent(xx)方法:

ViewRootImpl.java
    void enqueueInputEvent(InputEvent event,
                           InputEventReceiver receiver, int flags, boolean processImmediately) {
        //将InputEvent构造为QueuedInputEvent 对象
        //QueuedInputEvent 实际上就是封装了InputEvent的链表,这里当做有头指针和尾指针的队列
        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
        QueuedInputEvent last = mPendingInputEventTail;
        if (last == null) {
            //队列为空,那么队头指针和队尾指针指向同一个节点
            mPendingInputEventHead = q;
            mPendingInputEventTail = q;
        } else {
            //将节点插入队尾
            last.mNext = q;
            //移动队尾指针
            mPendingInputEventTail = q;
        }
        //队列里节点个数计数+1
        mPendingInputEventCount += 1;
        if (processImmediately) {
            //立即处理
            doProcessInputEvents();
        } else {
            //延时处理,通过Handler发送
            scheduleProcessInputEvents();
        }
    }

    void doProcessInputEvents() {
        //while 循环 处理队列中所有的节点
        while (mPendingInputEventHead != null) {
            //取出队头节点,并移动队头指针
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            if (mPendingInputEventHead == null) {
                mPendingInputEventTail = null;
            }
            q.mNext = null;

            mPendingInputEventCount -= 1;
            ...
            //继续传递该节点
            deliverInputEvent(q);
        }
        ...
    }

    private void deliverInputEvent(QueuedInputEvent q) {
        ...
        //输入责任链,此处运用了设计模式之一的:责任链
        //简单说就是定义了一个接收者处理链,该链节点有先后顺序,并且前一节点持有
        //下一个节点的引用,当前一个节点不处理请求
        //那么会分发给下一个节点
        InputStage stage;
        //确定输入链的开始位置,也就是从链中某个节点开始
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            //touch事件走mFirstPostImeInputStage
            //key事件走mFirstInputStage
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }
        ...
        if (stage != null) {
            handleWindowFocusChanged();
            //责任链处理事件
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }

上面提到了责任链,输入事件交给了责任链处理,那么责任链在哪建立的,它有哪些节点呢?
首先来看看InputStage,该类为抽象类:

    abstract class InputStage {
        //记录下一个节点
        private final InputStage mNext;
        //节点处理的状态
        //需要转发给下一个节点
        protected static final int FORWARD = 0;
        //当前节点已处理完成
        protected static final int FINISH_HANDLED = 1;
        //当前节点标记完成,但未处理
        protected static final int FINISH_NOT_HANDLED = 2;
        public InputStage(InputStage next) {
            //每次构造时都指定其后一个节点
            mNext = next;
        }
        //一些处理方法,有些被子类重写
    }

继续来分析InputStage 子类,还是回头看看ViewRootImpl setView(xx)方法:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                ...
                // Set up the input pipeline.
                CharSequence counterSuffix = attrs.getTitle();
                //定义了责任链节点
                mSyntheticInputStage = new SyntheticInputStage();
                //输入法之后的View处理,下一个节点指向mSyntheticInputStage
                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
                //输入法之后的本地处理,下一个节点指向viewPostImeStage
                InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
                //早些的输入法之后的处理,下一个节点指向nativePostImeStage
                InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
                //输入法处理,下一个节点指向earlyPostImeStage
                InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                        "aq:ime:" + counterSuffix);
                //输入法之前的View处理,下一个节点指向imeStage
                InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
                //输入法之前的本地处理,下一个节点指向viewPreImeStage
                InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                        "aq:native-pre-ime:" + counterSuffix);
                
                //记录节点到成员变量里
                mFirstInputStage = nativePreImeStage;
                mFirstPostImeInputStage = earlyPostImeStage;
            }
        }
    }

可以看出定义责任链时,其顺序已经指定了,用图表示:


Android 输入事件一撸到底之源头活水(1)_第1张图片
image.png

责任链已经建立完毕,接下来看具体的责任链里节点处理。

Touch/Key事件处理

当前我们常接触到的事件分为两类,Touch(触摸事件)、Key(按键事件),相对应的InputEvent.java 有两个子类:


Android 输入事件一撸到底之源头活水(1)_第2张图片
image.png

在确定责任链开始位置时:

stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;

Touch事件选择EarlyPostImeInputStage
Key事件选择NativePreImeInputStage

经过责任链节点流转,最终交由责任链节点:ViewPostImeInputStage 处理

ViewPostImeInputStage
    protected int onProcess(QueuedInputEvent q) {
        //key 事件处理
        if (q.mEvent instanceof KeyEvent) {
            return processKeyEvent(q);
        } else {
            final int source = q.mEvent.getSource();
            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                //Touch 事件处理
                return processPointerEvent(q);
            } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                return processTrackballEvent(q);
            } else {
                return processGenericMotionEvent(q);
            }
        }
    }

可以看出,Touch/Key 事件在此处分流了。
processKeyEvent(xx)

    private int processKeyEvent(QueuedInputEvent q) {
        final KeyEvent event = (KeyEvent)q.mEvent;
        ...
        //mView 为ViewRootImpl 持有的View,表示该Window的Root View
        //此处将KeyEvent 交给Root View处理
        if (mView.dispatchKeyEvent(event)) {
            return FINISH_HANDLED;
        }
        ...
    }

processPointerEvent(xx)

    private int processPointerEvent(QueuedInputEvent q) {
        //将InputEvent 转为MotionEvent
        final MotionEvent event = (MotionEvent)q.mEvent;
        mAttachInfo.mUnbufferedDispatchRequested = false;
        mAttachInfo.mHandlingPointerEvent = true;
        //mView 为ViewRootImpl 持有的View,表示该Window的root View
        //此处将MotionEvent 交给root View处理
        boolean handled = mView.dispatchPointerEvent(event);
        ...
        return handled ? FINISH_HANDLED : FORWARD;
    }

不论是KeyEvent还是MotionEvent,都交给了Window Root View处理,分别调用了View dispatchKeyEvent、dispatchPointerEvent 方法,那Root View从哪来的呢?

Root View 接收事件

在之前的文章:Window/WindowManager 不可不知之事
提到过View需要添加到Window里才能展示出来,而添加View到Window的方法:

public void addView(View view, ViewGroup.LayoutParams params);

当使用WindowManager.addView(View view, ViewGroup.LayoutParams params)时,该view即是Window的Root View。
来看看一些常用的Root View
Activity Root View
Activity 使用DecorView 作为Root View
Dialog Root View
Dialog 使用DecorView 作为Root View
PopupWindow Root View
PopupWindow 使用PopupDecorView 作为Root View(PopupDecorView 为PopupWindow内部类)
Toast Root View
Toast 默认加载

View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);

v 作为其默认的Root View
当然也可以更改其默认的Root View:

toast.setView(View rootView)

更多Dialog/PopupWindow/Toast 相关请移步:Dialog PopupWindow Toast 你还有疑惑吗

总结

通过上述分析可知,输入事件从底层传到App层,经过责任链处理,最后分发给Root View,这过程用图表示:


Android 输入事件一撸到底之源头活水(1)_第3张图片
image.png

你可能感兴趣的:(Android 输入事件一撸到底之源头活水(1))