美文网首页
View·InputEvent事件投递源码分析(一)

View·InputEvent事件投递源码分析(一)

作者: 幺鹿 | 来源:发表于2017-02-27 15:19 被阅读695次

    总结

    本文从源码角度,描述了如下4个阶段:

    • 从底层硬件产生的触摸事件,并将事件传递到InputEventReceiver中。
    • InputEventReceiver沟通ViewRootImpl,将事件计入ViewRootImpl的事件队列中。
    • ViewRootImpl通过多个Stage职责对象构成职责链,来按序处理事件。
    • 不同的Stage对象将触摸事件投递到不同的对象中(触摸板、导航栏、实体按键或视图)。

    概述

    这里的事件是指来源于硬件的事件,诸如:屏幕的按压、触摸(屏幕解锁),实体按键的按压(调整音量),甚至于实体按键的组合使用(截屏)。

    事件分类

    // 公共基础类输入事件。
    // 提供了获取输入设备,修改事件来源,深拷贝,序列化,事件重用的功能
    public abstract class InputEvent implements Parcelable {
    
    }
    
    // 用于反馈:键和按钮事件的对象。
    public class KeyEvent extends InputEvent implements Parcelable {
    
    }
    
    // 用于报告运动(鼠标,笔,手指,轨迹球)事件的对象。
    // 运动事件可以保持绝对或相对运动和其他数据,取决于设备的类型。
    public final class MotionEvent extends InputEvent implements Parcelable {
    }
    

    依据事件的分类描述,则对屏幕的触摸、滑动事件都应由MotionEvent处理。事实也是如此:

    // android.view.View
    public boolean dispatchTouchEvent(MotionEvent event) {
    }
    

    事件产生 —— 通过命令创造

    MotionEvent事件为例,先尝试分析 MotionEvent对象是如何创建的。提到创建对象必然逃不开new关键字,所以全文搜索下不难找到下面代码片段。

    因为MotionEvent存在大量的创建与释放,所以在这里构建了一个大小为MAX_RECYCLED (10)的池,以便于复用。

        static private MotionEvent obtain() {
            final MotionEvent ev;
            synchronized (gRecyclerLock) {
                ev = gRecyclerTop;
                if (ev == null) {
                    return new MotionEvent();
                }
                gRecyclerTop = ev.mNext;
                gRecyclerUsed -= 1;
            }
            ev.mNext = null;
            ev.prepareForReuse();
            return ev;
        }
    
       /**
         * 回收MotionEvent,以供稍后调用者重复使用。
         * 调用此函数后,您不能再次触摸事件(因为其已被释放)。
         */
        @Override
        public final void recycle() {
            super.recycle();
    
            synchronized (gRecyclerLock) {
                if (gRecyclerUsed < MAX_RECYCLED) {
                    gRecyclerUsed++;
                    mNext = gRecyclerTop;
                    gRecyclerTop = this;
                }
            }
        }
    

    如果你打开编辑器并且进入到 MotionEvent类中,你会发现该类有多个obtain方法(如下图)。�

    考虑到 MotionEvent的无参数obtain()方法是私有的,并且其他的obtain(xxx)方法都在内部引用了无参数obtain()方法。所以外部一定需要调用obtain(xxx)的方法,并且该参数一定不是MotionEvent

    MotionEvent

    所以排除上图中的倒数两个方法,并对其它Android推荐的方法(倒数4、5已不被推荐依次进行全文查找。

    最终查找结果如下:

    public class Input {
       
        private void sendMove(int inputSource, float dx, float dy) {
            long now = SystemClock.uptimeMillis();
            injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, dx, dy, 0.0f);
        }
    }
    
        private void injectMotionEvent(int inputSource, int action, long when, float x, float y, float pressure) {
            final float DEFAULT_SIZE = 1.0f;
            final int DEFAULT_META_STATE = 0;
            final float DEFAULT_PRECISION_X = 1.0f;
            final float DEFAULT_PRECISION_Y = 1.0f;
            final int DEFAULT_DEVICE_ID = 0;
            final int DEFAULT_EDGE_FLAGS = 0;
            MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, DEFAULT_SIZE,
                    DEFAULT_META_STATE, DEFAULT_PRECISION_X, DEFAULT_PRECISION_Y, DEFAULT_DEVICE_ID,
                    DEFAULT_EDGE_FLAGS);
            event.setSource(inputSource);
            InputManager.getInstance().injectInputEvent(event,
                    InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
        }
    

    没什么好说的,继续跟进 InputManager

    public final class InputManager {
        private final IInputManager mIm;
       
        public boolean injectInputEvent(InputEvent event, int mode) {
            if (event == null) {
                throw new IllegalArgumentException("event must not be null");
            }
            if (mode != INJECT_INPUT_EVENT_MODE_ASYNC
                    && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
                    && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
                throw new IllegalArgumentException("mode is invalid");
            }
    
            try {
                return mIm.injectInputEvent(event, mode);
            } catch (RemoteException ex) {
                return false;
            }
        }
    }
    

    到底问题的关键变成了mImmIm的类型是IInputManager

    public static InputManager getInstance() {
            synchronized (InputManager.class) {
                if (sInstance == null) {
                    IBinder b = ServiceManager.getService(Context.INPUT_SERVICE);
                    sInstance = new InputManager(IInputManager.Stub.asInterface(b));
                }
                return sInstance;
            }
        }
    

    看来IInputManagerInputManager的远程代理对象,他们之间通过Binder通讯。

    IInputManager:用代码模拟屏幕点击、触摸事件

    事件产生 —— 通过屏幕触摸

    底层硬件调用

    你对设备的触摸事件,将通过底层方法进行调用。因为目前我对 Native 部分的知识比较匮乏,所以就不对具体调用过程展开分析了。此处就将底层会处理相关的事件,并生成MotionEvent的对象并调用InputEventReceiver#dispatchInputEvent()方法作为论据,为下一步的分析提供支撑。

    InputEventReceiver是事件的源头

    public abstract class InputEventReceiver {
        // 被 native 方法调用
        @SuppressWarnings("unused")
        private void dispatchInputEvent(int seq, InputEvent event) {
            mSeqMap.put(event.getSequenceNumber(), seq);
            onInputEvent(event);
        }
    }
    

    ViewRootImpl 绝佳的中介

    ViewRootImpl 绝佳的中介,它既联系了WMS、Window、PhoneWindow、View这条线,同时也联系了InputEvent。所以它具备将InputEvent派发到指定的View的能力。

    public final class ViewRootImpl implements ViewParent,
            View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
    
        void enqueueInputEvent(InputEvent event,
                               InputEventReceiver receiver, int flags, boolean processImmediately) {
            adjustInputEventForCompatibility(event);
            QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
    
            // 始终按顺序将输入事件排队,而不管其时间戳。
            // 我们这样做是因为应用程序或IME可以注入关键事件以响应触摸事件,
            // 并且我们要确保注入的键按照它们被接收的顺序被处理,
            // 并且我们不能相信注入的事件的时间戳是单调的。
            QueuedInputEvent last = mPendingInputEventTail;
            if (last == null) {
                mPendingInputEventHead = q;
                mPendingInputEventTail = q;
            } else {
                last.mNext = q;
                mPendingInputEventTail = q;
            }
            mPendingInputEventCount += 1;
            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                    mPendingInputEventCount);
    
            if (processImmediately) {
                doProcessInputEvents();
            } else {
                scheduleProcessInputEvents();
            }
        }
    
        private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
                InputEventReceiver receiver, int flags) {
            QueuedInputEvent q = mQueuedInputEventPool;
            if (q != null) {
                mQueuedInputEventPoolSize -= 1;
                mQueuedInputEventPool = q.mNext;
                q.mNext = null;
            } else {
                q = new QueuedInputEvent();
            }
    
            q.mEvent = event;
            q.mReceiver = receiver;
            q.mFlags = flags;
            return q;
        }
    
    }
    

    QueuedInputEvent理解成封装了InputEventInputEventReceiver的封装类即可。

    另外processImmediately类为true,那么enqueueInputEvent会立即调用doProcessInputEvents。否则会加入到消息队列中,然后按顺序处理。当然无论以何种方式,最终都会调用到doProcessInputEvents

    输入事件的处理

    public final class ViewRootImpl implements ViewParent,
            View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
        private void deliverInputEvent(QueuedInputEvent q) {
            // ... 省略无关紧要的代码
            InputStage stage;
            if (q.shouldSendToSynthesizer()) {
                stage = mSyntheticInputStage;
            } else {
                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
            }
    
            if (stage != null) {
                stage.deliver(q);
            } else {
                finishInputEvent(q);
            }
        }
    }
    

    看到事件分发时,InputStage类作为基类它实现了职责链的模板,并且stage.deliver(q)方法实现了启动了职责链的处理流程。

    职责链番外篇 ——职责链模板之职责对象 “输入阶段”

    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;
            }
    
            // ... 省略无关代码
    
            public final void deliver(QueuedInputEvent q) {
                if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
                    forward(q);
                } else if (shouldDropInputEvent(q)) {
                    finish(q, false);
                } else {
                    apply(q, onProcess(q));
                }
            }
    
            protected void finish(QueuedInputEvent q, boolean handled) {
                q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
                if (handled) {
                    q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
                }
                forward(q);
            }
    
            protected void forward(QueuedInputEvent q) {
                onDeliverToNext(q);
            }
    
            protected void apply(QueuedInputEvent q, int result) {
                if (result == FORWARD) {
                    forward(q);
                } else if (result == FINISH_HANDLED) {
                    finish(q, true);
                } else if (result == FINISH_NOT_HANDLED) {
                    finish(q, false);
                } else {
                    throw new IllegalArgumentException("Invalid result: " + result);
                }
            }
    
            protected void onDeliverToNext(QueuedInputEvent q) {
                if (DEBUG_INPUT_STAGES) {
                    Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
                }
                if (mNext != null) {
                    mNext.deliver(q);
                } else {
                    finishInputEvent(q);
                }
            }
        }
    

    既然基类InputStage提供了职责链的模板,也提供了一系列onProcess、forward、finish、apply方法,其目的也不言而喻:提供子类进行扩展的便捷。

    
    // ----------------- InputStage的子类 ----------------------------
    // 将预先输入事件提供给视图层次结构。
    final class ViewPreImeInputStage extends InputStage {}
    // 执行事后输入事件的早期处理。
    final class EarlyPostImeInputStage extends InputStage {}
    // 将后期输入事件提供给视图层次结构。
    final class ViewPostImeInputStage extends InputStage {}
    // 从未处理的输入事件执行新输入事件的合成。
    final class SyntheticInputStage extends InputStage {}
    // 用于实现支持输入事件的异步和无序处理的输入流水线级的基类。
    abstract class AsyncInputStage extends InputStage {}
    // ----------------- AsyncInputStage的子类----------------------------
    // 将预先输入事件提供给 NativeActivity。
    final class NativePreImeInputStage extends AsyncInputStage
                implements InputQueue.FinishedInputEventCallback {}
    // 将预先输入事件提供给视图层次结构。
    final class ImeInputStage extends AsyncInputStage
                implements InputMethodManager.FinishedInputEventCallback {}
    // 将事后输入事件提交到 NativeActivity
    final class NativePostImeInputStage extends AsyncInputStage
                implements InputQueue.FinishedInputEventCallback {}
    

    职责链番外篇 ——职责链模板之拼装职责链并执行事件处理

    ViewRootImpl中已经定义了很多Stage职责,那么这些职责由是在什么时候被拼装成链式调用的呢?

    public final class ViewRootImpl implements ViewParent,
            View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
    
        InputStage mFirstInputStage;
        InputStage mFirstPostImeInputStage;
        InputStage mSyntheticInputStage;
        // ... 省略无关的代码
        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                    // ... 省略无关的代码
                    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;
    
        }
    
    }
    

    最终构造成如下的职责链:mSyntheticInputStage --> viewPostImeStage --> nativePostImeStage --> earlyPostImeStage --> imeStage --> viewPreImeStage --> viewPreImeStage


    Stage对象的详解

    SyntheticInputStage

    综合性的事件处理阶段,该类主要轨迹球、操作杆、导航面板及未捕获的事件使用键盘进行处理。

        final class SyntheticInputStage extends InputStage {
            @Override
            protected int onProcess(QueuedInputEvent q) {
                q.mFlags |= QueuedInputEvent.FLAG_RESYNTHESIZED;
                if (q.mEvent instanceof MotionEvent) {
                    final MotionEvent event = (MotionEvent) q.mEvent;
                    final int source = event.getSource();
                    if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                        // 轨迹球
                        mTrackball.process(event);
                        return FINISH_HANDLED;
                    } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
                        // 操作杆
                        mJoystick.process(event);
                        return FINISH_HANDLED;
                    } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
                            == InputDevice.SOURCE_TOUCH_NAVIGATION) {
                        // 导航面板
                        mTouchNavigation.process(event);
                        return FINISH_HANDLED;
                    }
                } else if ((q.mFlags & QueuedInputEvent.FLAG_UNHANDLED) != 0) {
                    // 未捕获的事件,交由键盘去处理
                    mKeyboard.process((KeyEvent) q.mEvent);
                    return FINISH_HANDLED;
                }
                // 继续转发事件
                return FORWARD;
            }
    }
    

    ViewPostImeInputStage

    视图输入处理阶段,主要处理按键、轨迹球、手指触摸及一般性的运动事件,触摸事件的分发对象是View。

    processPointerEvent方法是对触摸事件的预处理,在View执行拖拽时将会使用到预处理后的值。

    final class ViewPostImeInputStage extends InputStage {
            @Override
            protected int onProcess(QueuedInputEvent q) {
                if (q.mEvent instanceof KeyEvent) {
                    // 处理按键事件
                    return processKeyEvent(q);
                } else {
                    final int source = q.mEvent.getSource();
                    if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                        // 处理手指触摸事件
                        return processPointerEvent(q);
                    } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                        // 处理轨迹球事件
                        return processTrackballEvent(q);
                    } else { 
                        // 处理一般性的运动事件
                        return processGenericMotionEvent(q);
                    }
                }
            }
    
            private int processPointerEvent(QueuedInputEvent q) {
                final MotionEvent event = (MotionEvent)q.mEvent;
    
                mAttachInfo.mUnbufferedDispatchRequested = false;
                boolean handled = mView.dispatchPointerEvent(event);
                if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                    mUnbufferedInputDispatch = true;
                    if (mConsumeBatchedInputScheduled) {
                        scheduleConsumeBatchedInputImmediately();
                    }
                }
                return handled ? FINISH_HANDLED : FORWARD;
            }
    }
    

    所以经由processPointerEvent分析可知,你对屏幕上的某个按钮的点击事件,将总是先调用按钮的dispatchTouchEvent 的方法。

    先提出上面这句论断,但也别忽视上面论断中的几个待确认的疑点:

    • mView是什么(是根视图?还是焦点触发的视图)?
    • dispatchPointerEvent是如何派发事件的?

    这些疑点我们另开一篇文章进行描述,这里先不打断原有的流程,我们接着继续分析。

    NativePostImeInputStage

    本地方法处理阶段,则构建可延迟的重用队列,此时执行操作将会异步回调结果。

    final class NativePostImeInputStage extends AsyncInputStage
                implements InputQueue.FinishedInputEventCallback {
    
            @Override
            protected int onProcess(QueuedInputEvent q) {
                if (mInputQueue != null) {
                    mInputQueue.sendInputEvent(q.mEvent, q, false, this);
                    return DEFER;
                }
                return FORWARD;
            }
    }
    

    EarlyPostImeInputStage

    输入法早期处理阶段。

    final class EarlyPostImeInputStage extends InputStage {
    
            @Override
            protected int onProcess(QueuedInputEvent q) {
                if (q.mEvent instanceof KeyEvent) {
                    return processKeyEvent(q);
                } else {
                    final int source = q.mEvent.getSource();
                    if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                        return processPointerEvent(q);
                    }
                }
                return FORWARD;
            }
    
    private int processPointerEvent(QueuedInputEvent q) {
                final MotionEvent event = (MotionEvent) q.mEvent;
    
                // Translate the pointer event for compatibility, if needed.
                if (mTranslator != null) {
                    mTranslator.translateEventInScreenToAppWindow(event);
                }
    
                // Enter touch mode on down or scroll.
                final int action = event.getAction();
                if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) {
                    ensureTouchMode(true);
                }
    
                // Offset the scroll position.
                if (mCurScrollY != 0) {
                    event.offsetLocation(0, mCurScrollY);
                }
    
                // Remember the touch position for possible drag-initiation.
                if (event.isTouchEvent()) {
                    mLastTouchPoint.x = event.getRawX();
                    mLastTouchPoint.y = event.getRawY();
                    mLastTouchSource = event.getSource();
                }
                return FORWARD;
            }
    }
    

    ImeInputStage

    输入法事件处理阶段,处理一些输入法字符等。如果对输入的内容无法识别,则继续往下转发。

    final class ImeInputStage extends AsyncInputStage
                implements InputMethodManager.FinishedInputEventCallback {
    
            @Override
            protected int onProcess(QueuedInputEvent q) {
                if (mLastWasImTarget && !isInLocalFocusMode()) {
                    InputMethodManager imm = InputMethodManager.peekInstance();
                    if (imm != null) {
                        final InputEvent event = q.mEvent;
                        int result = imm.dispatchInputEvent(event, q, this, mHandler);
                        if (result == InputMethodManager.DISPATCH_HANDLED) {
                            return FINISH_HANDLED;
                        } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
                            // IME 无法处理,则往下转发
                            return FORWARD;
                        } else {
                             // 会执行异步回调
                            return DEFER;
                        }
                    }
                }
                return FORWARD;
            }
    }
    

    ViewPreImeInputStage

    视图预处理输入法事件阶段,将输入法的事件派发到视图的树。

    final class ViewPreImeInputStage extends InputStage {
    
            @Override
            protected int onProcess(QueuedInputEvent q) {
                if (q.mEvent instanceof KeyEvent) {
                    return processKeyEvent(q);
                }
                return FORWARD;
            }
    
            private int processKeyEvent(QueuedInputEvent q) {
                final KeyEvent event = (KeyEvent) q.mEvent;
                if (mView.dispatchKeyEventPreIme(event)) {
                    return FINISH_HANDLED;
                }
                return FORWARD;
            }
        }
    }
    

    NativePreImeInputStage

    本地方法预处理输入法事件阶段,可用于实现类似adb 输入的功能。

    final class NativePreImeInputStage extends AsyncInputStage
                implements InputQueue.FinishedInputEventCallback {
            public NativePreImeInputStage(InputStage next, String traceCounter) {
                super(next, traceCounter);
            }
    
            @Override
            protected int onProcess(QueuedInputEvent q) {
                if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
                    mInputQueue.sendInputEvent(q.mEvent, q, true, this);
                    return DEFER;
                }
                return FORWARD;
            }
    
            @Override
            public void onFinishedInputEvent(Object token, boolean handled) {
                QueuedInputEvent q = (QueuedInputEvent) token;
                if (handled) {
                    finish(q, true);
                    return;
                }
                forward(q);
            }
        }
    }
    

    经过{综合性处理阶段}{视图处理阶段}{本地处理阶段},接着从{早起输入法处理阶段}{输入法阶段}输入法视图预处理阶段最后到{输入法本地预处理阶段}

    当然按照正常情况肯定是在职责链靠前的阶段,被处理的机会越大。(这不是废话么:-P)。我们最关心的视图的触摸事件的派发的阶段就在{视图处理阶段}中。

    结束语

    下一篇文章将会分析,如何将 inputEvent 事件触达到 dispatchTouchEvent 。
    View·从 InputEvent 到 dispatchTouchEvent 源码分析(二)


    参考资料:

    相关文章

      网友评论

          本文标题:View·InputEvent事件投递源码分析(一)

          本文链接:https://www.haomeiwen.com/subject/nkjzwttx.html