MotionEvent之来龙去脉
1. 输入事件分 类
- KeyEvent
- MotionEvent
2. InputManagerService 系统服务
流程概述:
1.当用户输入操作(触摸/按键/鼠标),在驱动层会受到信号后,会将信号写入到输入设备节点,从而产生内核事件
2.IMS接受到内核时间后,进过处理和封装后包装成KeyEvent/MotionEvent
3.IMS共WMS讲给对应的window来消费输入事件-
InputManagerService 组成结构以及工作原理
1.结构:
1)InputManagerService.- NativeInputManager
2.1)EventHub 监听输入启动,添加到epoll中
2.2)InputManager
// 生产者消费者模式, 两个线程
2.2.1)InputReaderThread && InputReader
从EventHub,监听设备节点,创建epoll对象, epoll_wait 从管道中读取出事件, 唤醒inputReader
2.2.2)InputDispatcherThread && InputDispatcher
接收来自InputReader的输入时间,然后分发到合适的window中去
其中InputReaderThread, InputDispatcherThread 运行在SystermServer进程中的
- NativeInputManager
InputChannel 对socket对的封装,通过这种方式进入快进程, 一般server和client端各一个, server端的最终是注册到InputManagerService中,并且在InputDispatcherThread转发调用,client端则是通过注册到InputEventReceiver中通过epoll机制,接收InputManagerService发送过来的信息。并且转发出来
[图片上传失败...(image-220752-1554567102902)]
3. framwork层Activity, view, window 三者产生关系的时机
- Activity,ViewRootImpl, Window, WindowManagerService
- Activity与Window的关系
- 在ActivityThread中Activity attatch的时候创建Window,Window的实现类为PhoneWindow
ActivityThread.java
class ActivityThread{
private Activity performLaunchActivity(ActivityRecord r, Intent intent){
// 省略。。。
// 执行Activity的Attach方法
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
// 省略。。。
}
public void handleResumeActivity(IBinder token, ...){
// 省略 ...
// 执行activity的生命周期onResumeActivity()
final ActivityClientdeRecord r = performResumeActivity(token, finalStateRequest, reason);
// 省略 ...
// 调用Activity的方法,最终实现将window上addView, 详见Activity.java##makeVisible()方法
r.activity.makeVisible();
}
}
Activity.java
class Activity{
void attach(Context context, ActivityThread aThread, ....){
// [1] 创建window
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
// window 中设置了WindowManager, 其中 context.getSystemService(xxx)
//返回的是WindowManagerImpl,在WindowManagerImpl中持有WindowManagerGlobal成员,这个是用于与WMS进行通信
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
}
void setContentView(int id){
// phoneWindow setContentView
// [2] 设置window 里的DecorView
getWindow().setContentView(id);
}
// window manager
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
}
interface Window.Callback{
public boolean dispatchTouchEvent(MotionEvent event);
}
class PhoneWindow extends Window{
// [2.1] 初始化window里的decor
void setContentView(int id){
// new DecorView, 并且将mDecor赋值
installDecor();
}
}
WindowManagerGlobal.java
class WindowManagerGlobal{
void addView(View view, ....){
root = new RootViewImpl(context, display);
mViews.add(view);
mRoot.add(root);
//
root.setView(view, );
}
}
// ViewRootImpl 从当的角色是管理和执行view的生命周期, 以及时间机制
class ViewRootImpl{
void setView(){
// 1. 创建一个InputChannel
mInputChannel = new InputChannel();
// 2.这里将InputChannel添加到InputManangerService中产紧固件socketpair, 用来接受和发送事件
mWindowSession.addToDisplay(mWindow, ..., mInputChannel);
// 3.set up input pipline
CharSequence counterSuffix = attrs.getTitle();
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);
}
}
这里InputStage由一个KeyEvent链表结构组成一个队列,
NativePreImeInputStage-> ViewPreImeInputStage -> EarlyPostImeInputStage->ViewPostImeInputStage->SyntheticInputStage
class ViewRootImpl{
private void deliverInputEvent(QueuedInputEvent q){
// 省略
InputStage stage;
// 只有在设置为FLAG_UNHANDLED 情况下直接交给mSyntheticInputStage 处理
// 只在##dispatchUnhandledInputEvent()方法中产生
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
// 所以一般为 mFirstPostImeInputStage 或者 mFirstInputStage
// 区别在于 看是否要跳过ime的处理, 如果跳过,采用EarlyPostImeInputStage 处理
// 否则从责任链的 NativePreImeInputStage 开始处理
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
// 省略
if (stage != null) {
handleWindowFocusChanged();
// 交给以上对应的步骤进程处理
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
void scheduleConsumeBatchedInput() {
if (!mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = true;
//
mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
mConsumedBatchedInputRunnable, null);
}
}
final class ConsumeBatchedInputRunnable implements Runnable {
@Override
public void run() {
//
doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
}
}
void doConsumeBatchedInput(long frameTimeNanos) {
if (mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = false;
if (mInputEventReceiver != null) {
if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
&& frameTimeNanos != -1) {
// 请求绘制下一阵的处理batch事件
scheduleConsumeBatchedInput();
}
}
doProcessInputEvents();
}
}
}
android_view_inputEventReceiver.cpp
static jlong nativeInit(*env, ...., inputChannelObj, messageQueueObj){
// 获取native 的InputChannel
sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(inputChannelObj);
// 2. 获取native Messsagequeue
sp<MessageQueue> messsageQueue = android_os_MesssageQueue_getMessageQeueue(messageQueueObj);
// 3 创建NativeInputEventReceiver
sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(inputchanel, mmessage)
receiver->initialize();
}
NativeInputEventReceiver::initialize(){
// 注册ALOOPER_EVENT_INPUT 到epoll中
setFdEvent(ALOOPER_EVENT_INPUT)
}
// 从InputConsummer中获取channel的fd, 然后调用Looper addFd 注册到epoll中去,这里的looper对应的是主线程即UI线程里的looper, 添加epoll监控
// 因为NativeInputEventReceiver继承了LooperCallback, 所有当epoll机制里面,pollOnce的时候,有对应的事件消息,就会调用 handleEvent 处理
NativeInputEventReceiver::setFdEvent(){
if (mFdEvents != events) {
mFdEvents = events;
int fd = mInputConsumer.getChannel()->getFd();
if (events) {
mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
} else {
mMessageQueue->getLooper()->removeFd(fd);
}
}
}
// 此处接收到输入的事件消息处理, 最终通过consumeEvents方法处理
NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
if (events & ALOOPER_EVENT_INPUT) {
// 交给consumeEvents 处理
status_t status = consumeEvents (env, false /*consumeBatches*/, -1, NULL);
}
}
NativeInputEventReceiver::consumeEvents(JNIEnv* env,...){
// ...
for(;;){
// 注意此处mInputConsumer是在NativeInputEvnetRecevier的初始化列表中创建
//1. mInputConsumer(inputChannel), 里面持有InputChannel
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent);
// 2.调用java中 InputEventReceiver.java##dispatchInputEvent() 方法
if(inputEventObj){
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
}
}
}
InputTransport.cpp
InputConsumer::consume(..){
while(cond){
// 可以知道最终是通过Channel去处理消息
status_t result = mChannel->receiveMessage(&mMsg);
if(result){
consumeBatch(x)
}
}
}
InputChannel::receiveMessage(InputMessage* msg){
// 从缓冲区里读取消息
}
梳理一下整个input事件的前因后果
1、ViewRootImpl 在setView的时候会new WindowInputEventReceiver()
- InputEventReceiver 在初始化的时候调用nativeInit 创建一个NativeInputEventReceiver,注册到主线程的Looper, epoll中处理,同时NativeInputEventReceiver继承LoopCallback回调函数。在pollonce,当有相应的Input事件的时候.
3.每次receiver端的在有事件产生时事件触发NativeInputEventReceiver的handleEvent, 继而 InputConsumer::consume
4.最终会调用InputEventReceiver##dispatchInputEvent,最终enqueueInputEvent, 然后进入责任链调用中
5.在责任链中ViewPostImeInputStage##processPointerEvent## mView.dispatchPointerEvent(event);其中此处的view
就是DecorView, 继而就是后续的dispatchTouchEvent(), onInterceptTouchEvent(), onTouchEvent()
涉及系统层的服务有哪些:
1.InputManagerService
2.WindowManagerService, 其中WindowManagerService在SystemService初始化过程中持有InputManagerService对象
在ViewRootImpl中setView中,会将InputChannel添加到WMS中并创建SocketPair用于处理接收和发送
关注3点:
- Activity 中new了一个PhoneWindow
- Activity 中实现了window的callback接口
3)Window 设置了WindowManager(实现类WindowManagerImpl)
4、framework输入系统结构
[图片上传失败...(image-3ed534-1554567102902)]
5.InputEvent如何从InpuntReceiver最终传递到子view中去的
在经过InpuntReceiver state链的处理之后,
如果是TouchEvent的话
在链ViewPostImeInputStage中processPointerEvent方法中会调用
DecorView中基类View中的dispatchPointerEvent 方法如下图
[图片上传失败...(image-29d550-1554567102902)]
6. 在ViewGroup 和View之间传递过程
class ViewGroup{
public void dispathTouchEvent(){
// 1.对ACTION_DOWN事件的特殊处理
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 如果是ACTION_DOWN事件
//1.清除前一个事件序列的相关信息,这些信息保存在名为mFirstTouchTarget的TouchTarget对象中,
//2.TouchTarget是一个链表,里面保存了应当处理该事件的View链
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 2.拦截处理
//2.1 disallowIntercept 全局标志禁止拦截,
//2.2 onInterceptTouchEvent 根据具体情况判断是否需要拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 1.这里disallowIntercept 是否允许事件被拦截
if (!disallowIntercept) {
2.经过intercept机制进一步作判断
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
// 如果被拦截,或者已经有目标VIew在处理,执行普通的时间Dispatch
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 取消标志的判断
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// split表达的意思是当前ViewGroup是否支持分割motionEvent到不同的view中
// 这里setMotionEventSplittingEnabled 设置,这个一般用于多点触摸
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
if(!canceled && ! intercepted){
// 3. 事件既不是取消时间,又没有拦截
// 3.1 事件为ACTION_DOWN/支持多点/hover
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE){
// 重新计算新的newTouchTarget
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
//下面所做的工作,就是找到可以接收这个事件的子元素
final View[] children = mChildren;
//是否使用自定义的顺序来添加控件, 这里可以重写getChildDrawingOrder方法
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
//如果是用了自定义的顺序来添加控件,那么绘制的View的顺序和mChildren的顺序是不一样的
//所以要根据getChildDrawingOrder取出真正的绘制的View
//自定义的绘制,可能第一个会画到第三个,和第四个,第二个画到第一个,这样里面的内容和Children是不一样的
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
//如果child不可以接收这个触摸的事件,或者触摸事件发生的位置不在这个View的范围内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//获取新的触摸对象,如果当前的子View在之前的触摸目标的列表当中就返回touchTarget
//子View不在之前的触摸目标列表那么就返回null
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
//如果新的触摸目标对象不为空,那么就把这个触摸的ID赋予它,这样子,
//这个触摸的目标对象的id就含有了好几个pointer的ID了
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//如果子View不在之前的触摸目标列表中,先重置childView的标志,去除掉CACEL的标志
resetCancelNextUpFlag(child);
//调用子View的dispatchTouchEvent,并且把pointer的id 赋予进去
//如果说,子View接收并且处理了这个事件,那么就更新上一次触摸事件的信息,
//并且为创建一个新的触摸目标对象,并且绑定这个子View和Pointer的ID
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//这里给mFirstTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
//如果newTouchTarget为null,就代表,这个事件没有找到子View去处理它,
//那么,如果之前已经有了触摸对象(比如,我点了一张图,另一个手指在外面图的外面点下去)
//那么就把这个之前那个触摸目标定为第一个触摸对象,并且把这个触摸(pointer)分配给最近添加的触摸目标
if(newTouchTarget ==null && mFirsttouchTarget != null){
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
// 如果没有目标
if(mFirstTouchTarget == null){
// 这里会交给View的dispatchTouchEvent处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}else{
//遍历TouchTargt树,分发事件
TouchTarget target = mFirstTouchTarget;
while (target != null) {
// .....
// 往下递归下去分发事件,
dispatchTransformedTouchEvent();
predecessor = target;
target = next;
}
}
}
public bool onInterceptTouchEvent(){
// 重写
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
}
class View{
public boolean dispatchTouchEvent(){
//最前面这一段就是判断当前事件是否能获得焦点,
// 如果不能获得焦点或者不存在一个View,那我们就直接返回False跳出循环
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
// ...
//Android用一个32位的整型值表示一次TouchEvent事件,低8位表示touch事件的具体动作,比如按下,抬起,滑动,还有多点触控时的按下,抬起,这个和单点是区分开的,下面看具体的方法:
//1 getAction:触摸动作的原始32位信息,包括事件的动作,触控点信息
//2 getActionMasked:触摸的动作,按下,抬起,滑动,多点按下,多点抬起
//3 getActionIndex:触控点信息
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
//当我们手指按到View上时,其他的依赖滑动都要先停下
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//重点,优先级:
// 1. 处理onTouch 事件
// 2. 处理onTouchEvent时间
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
public bool onTouchEvent(){
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return ;
}
//如果一个view过小,不容易点击,通过扩大点击区域实现更好的交互。可以暂且叫为代理,代理处理该事件。
//一般不怎么用到,所以可以忽略
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
switch (action) {
case MotionEvent.ACTION_UP:
//指的是,如果view包裹在一个scrolling View中,可能会进行滑动处理,所以设置了一个prePress的状态
//大致是等待一定时间,然后没有被父类拦截了事件,则认为是点击到了当前的view,从而显示点击态
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
//取消LongPress的检测
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
//执行点击,回调onClickListener的onClick,也就是我们经常使用的setOnClickListener中的操作
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//
performClick();
}
}
}
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_MOVE:
}
}
}
ViewGroup三个核心的问题
- 是否拦截
- 检查子view,并且往下传递
- 如何处理事件
View 处理事件
当我们对View进行一个点击,可以分为以下三个状态
1、PrePress(姑且叫为预按),这个状态是我们的view被加入到一个scrolling view中才会存在。具体的意义是,举个简单的例子,当我们将手放在listView的一item上的时候,由于当前还不知道用户是要点击item还是想滑动listview,所以先将view设为PrePress状态;
2、Press状态,用户将手放到view上面,如果不是1(上述)的状态,就里面设置为Press状态。那么先进入了PrePress,那么将会触发一个检测,也即CheckForTap(),默认时间是100ms,如果超过了100ms,将由PrePress进入到Press状态;
3、LongPress状态,这个状态由Press状态过度过来,如果用户在一个view上停留超过一定的时间(默认为500ms),将进入该状态
总结
在onTouchEvent的
MotionEvent.ACTION_UP中会判断是否执行PerformClick。因此onTouchEvent的优先于PerformClick。
DISABLED下view任然可能消费事件,CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE。
如button的Clickable为true,而textview的Clickable为false。
onclick会发生的前提是当前view可点击—即收到up和down的事件(必要不充分)。