前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >View·InputEvent事件投递源码分析(一)

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

作者头像
幺鹿
发布2018-08-21 15:41:41
1.5K0
发布2018-08-21 15:41:41
举报
文章被收录于专栏:Java呓语Java呓语

总结

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

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

概述

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

事件分类

代码语言:javascript
复制
// 公共基础类输入事件。
// 提供了获取输入设备,修改事件来源,深拷贝,序列化,事件重用的功能
public abstract class InputEvent implements Parcelable {

}

// 用于反馈:键和按钮事件的对象。
public class KeyEvent extends InputEvent implements Parcelable {

}

// 用于报告运动(鼠标,笔,手指,轨迹球)事件的对象。
// 运动事件可以保持绝对或相对运动和其他数据,取决于设备的类型。
public final class MotionEvent extends InputEvent implements Parcelable {
}

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

代码语言:javascript
复制
// android.view.View
public boolean dispatchTouchEvent(MotionEvent event) {
}

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

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

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

代码语言:javascript
复制
    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已不被推荐依次进行全文查找。

最终查找结果如下:

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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是事件的源头

代码语言:javascript
复制
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的能力。

代码语言:javascript
复制
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

输入事件的处理

代码语言:javascript
复制
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)方法实现了启动了职责链的处理流程。

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

代码语言:javascript
复制
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方法,其目的也不言而喻:提供子类进行扩展的便捷。

代码语言:javascript
复制
// ----------------- 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职责,那么这些职责由是在什么时候被拼装成链式调用的呢?

代码语言:javascript
复制
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

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

代码语言:javascript
复制
    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执行拖拽时将会使用到预处理后的值。

代码语言:javascript
复制
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

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

代码语言:javascript
复制
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

输入法早期处理阶段。

代码语言:javascript
复制
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

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

代码语言:javascript
复制
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

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

代码语言:javascript
复制
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 输入的功能。

代码语言:javascript
复制
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 。

参考资料:

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.02.27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
  • 概述
  • 事件分类
  • 事件产生 —— 通过命令创造
  • 事件产生 —— 通过屏幕触摸
    • 底层硬件调用
      • InputEventReceiver是事件的源头
        • ViewRootImpl 绝佳的中介
          • 输入事件的处理
            • 职责链番外篇 ——职责链模板之职责对象 “输入阶段”
              • 职责链番外篇 ——职责链模板之拼装职责链并执行事件处理
                • Stage对象的详解
                  • SyntheticInputStage
                  • ViewPostImeInputStage
                  • NativePostImeInputStage
                  • EarlyPostImeInputStage
                  • ImeInputStage
                  • ViewPreImeInputStage
                  • NativePreImeInputStage
              • 结束语
              相关产品与服务
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档