Android 事件机制解析(上)

1、背景

Android的触摸事件(MotionEvent)的传递机制还是有些复杂,内容会有点多,所以分为上下两篇来讲解。
《Android MetionEvent事件解析(上)》分析事件的分发流程,当清楚了事件分发流程后,我们需要结合一些实例进一步熟悉Android的事件传递机制,所以在《Android MetionEvent事件解析(下)》会提出几个问题,并通过源码进行分析。
分析事件的分发流程分为两个阶段进行分析。
1. 第一阶段:ViewRootImpl -> Activity
2. 第二阶段:Activity -> View

2、ViewRootImpl -> Activity

ViewRootImpl用于管理View的事件分发,界面绘制等工作。所以触摸事件的接收应该是从这个类开始。ViewRootImpl中定义了一个成员变量

WindowInputEventReceiver mInputEventReceiver;

WindowInputEventReceiver是ViewRootImpl中的内部类,该类继承自InputEventReceiver。通过类名就可知这是一个输入事件接收器。在创建WindowInputEventReceiver对象时会调用Native方法nativeInit()进行初始化

/**
* Creates an input event receiver bound to the specified input channel.
*
* @param inputChannel The input channel.
* @param looper The looper to use when invoking callbacks.
*/
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
    ...
    // 通过Native方法进行初始化
    // 创建一个指向WindowInputEventReiver对象的弱引用,并传递到Native层
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
            inputChannel, mMessageQueue);
    ...
}


Native层通过WindowInputEventReceiver对象的弱引用来进行相应的回调。在InputEventReceiver中有一个函数,通过注释我们知道该函数是Native层回调的,这个就是我们应用程序接收到事件的入口 
// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event);
}

找到了事件的接收源头后,接下来就来分析事件如何传递到Activity.dispatchTouchEvent()的
在onInputEvent(event)函数中调用了enqueueInputEvent(event, true)函数进行处理

@Override
public void onInputEvent(InputEvent event) {
    enqueueInputEvent(event, this, 0, true);
}

enqueueInputEvent ( )主要从缓存池中获取一个已被回收的QueuedInputEvent对象q,并把q添加到一个用于存放QueuedInputEvent对象的队列中

void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    // 从缓存池中获取一个QueuedInputEvent对象
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);


    // 把q添加到队列,mPendingInputEventHead为队列的头部
    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;

    if (processImmediately) {
        // processImmediately为true,所以执行doProcessInputEvents
        doProcessInputEvents();
    } else {
        scheduleProcessInputEvents();
    }
}

void doProcessInputEvents() {
    // 遍历队列,并通过deliverInputEvent()方法处理每个QueuedInputEvent对象
    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) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent");
    try {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
        }

       //检测ime相关module是否需要处理该输入事件,比如back键,是需要先
       //让IME处理,这个时候需要先交给mFirstPostImeInputStage处理
        InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        if (stage != null) {
            // InputStage是一个抽象类,这里需要确认stage对象指向那个实现类
            // 并调用deliver()进行处理事件
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

这里需要介绍一下InputStage的作用,InputStage是一个抽象列,从该类派生出很多的子类,那从InputStage派生出来的子类都是做什么的呢?
InputStage是责任链中的一个节点,系统中有很多种类型的责任链节点,下载我们假定我们已经知道把QueuedInputEvent传递到VIew的处理是由ViewPostImeInputStage节点来处理,在onProgress( )方法中处理

@Override
protected int onProcess(QueuedInputEvent q) {
    if (q.mEvent instanceof KeyEvent) {
        // 处理物理按键的点击操作(电源键、放回键等)
        return processKeyEvent(q);
    } else {

        handleDispatchWindowAnimationStopped();
        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;
    // mView为DecorView,开始分发触摸事件
    boolean handled = mView.dispatchPointerEvent(event);
    if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
        mUnbufferedInputDispatch = true;
        if (mConsumeBatchedInputScheduled) {
            scheduleConsumeBatchedInputImmediately();
        }
    }
    return handled ? FINISH_HANDLED : FORWARD;
}

public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        // DecorView重写了该方法
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // 获取mCallback,并进行回调,这个mCallback就是在前面设定指向Activity对象
    // 就这样Activity.dispatchTouchEvent()接收到了触摸事件
    final Callback cb = getCallback();
    return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
            : super.dispatchTouchEvent(ev);
}

MotionEvent事件是如何传递到Activity.dispatchTouchEvent()的,已经分析完毕,下面继续分析MotionEvent从Activity.dispatchTouchEvent()到View流程。

3、Activity -> View

这里先给出事件在Activity和View之间的传递大致过程,下面会分析Activity、ViewGroup、View三个类的dispatchTouchEvent( )方法的实现细节。
这里写图片描述
先来看Activity.dispatchTouchEvent( )的实现,实现还是比较简单

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 如果是DOWN事件,则回调onUserInteraction()
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }

    // 把事件分发给DecorView处理,返回true代表DecorView消费了事件
    if (getWindow().superDispatchTouchEvent(ev)) { 
        return true;
    }

    // DecorView不处理,则由Activity.onTouchEvent()处理
    return onTouchEvent(ev);
}

getWindow().superDispatchTouchEvent(ev) 会传递到DecorView,而DecorView继承FrameLayout,事件最终传递到FrameLayout.dispatchTouchEvent( ),下方的流程图是ViewGroup.dispatchTouchEvent( )的大致流程
这里写图片描述
我们跟着上方的流程图去阅读以下ViewGroup.dispatchTouchEvent( )的源码,这样会比较清晰些

public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {     
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // down事件为一个事件周期的开头,所以进行初始化操作
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 检查事件是否被拦截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 允许拦截,则通过onInterceptTouchEvent()的返回值看是否需要拦截
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                // 当前View设置了不允许拦截标识
                intercepted = false;
            }
        } else {
            // 不是down事件,且没有接受该事件的对象(即mFirstTouchTarget为null)则拦截
            intercepted = true;
        }
        ...

        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!intercepted) {
            ...   
            // 是否是Down事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                ...
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    final ArrayList<View> preorderedList = buildOrderedChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;

                    // 遍历全部子View
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        // 获取子View
                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(childIndex);
                        ...
                        // 判断子View(child)能否接收该事件
                        // 判断依据:子View的状态为VISIBLE,且触发事件的位置在子View的范围内
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }

                        // 查看child在这之前有没有接收过事件,如果接收过,则newTouchTarget就不会为null
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        // 通过dispatchTransformedTouchEvent()把事件ev传递给子View(child)
                        // 如果返回true,说明事件被child处理掉,创建一个新的Target,并加入到Target链表中
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            。。。(省略参数记录)
                            // 通过addTouchTarget()创建一个Target对象,加入到链表中
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            // 注意:这里设置标志位,下面会用到这个标志位
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                    }
                }

                // 如果没有找到可以接受MotionEvent的Target,那就使用最先加入Target链表中的Target为事件接收对象
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        if (mFirstTouchTarget == null) {
            // 如果没有找到Target来接收事件,那么把事件交由ViewGroup来处理
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {

            // 遍历Target链表
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                // 如果已把事件传递给Target,那么这里就不进行分发了
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    ...
                }
                predecessor = target;
                target = next;
            }
        }
       ...
    }
    return handled;
}

接下来分析View.dispatchTouchEvent( ),下方的流程图是View.dispatchTouchEvent( )的实现逻辑
这里写图片描述
对着上方的流程图分析源码

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    boolean result = false;
    ...
    if (onFilterTouchEventForSecurity(event)) {
        // 这里的逻辑很简单,逻辑如下:
        // 如果onTouch()返回true,则不执行onTouchEvent()
        // 如果返回false,则执行onTouchEvent()
        // 注意:onTouch()方法是通过View.setOnTouchListener()设置的回调
        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;
        }
    }
    ...
    return result;
}

onTouchEvent()内部实现了点击事件的判断,这里就不分析了。

4、总结

触摸事件的分发流程现在已经分析完毕,这里总结一下:
首先事件从ViewRootImpl传递到Activity.dispatchTouchEvent(),事件到达Activity后,把事件传递给DecorView,而DecorView是FrameLayout布局,所以事件传递到ViewGroup.dispatchTouchEvent(),如果ViewGroup不消费事件(即ViewGroup.dispatchTouch( )返回false),则由Activity.onTouchEvent( )来处理。
在ViewGroup.dispatchTouchEvent( )中遍历子View,找到可接收该事件的View,并传递给子View。即调用View.dispatchTouchEvent( )方法,如果View.dispatchTouchEvent( )返回false(即不消费事件),则事件由ViewGroup.onTouchEvent( )来处理。
在View.dispatchTouchEvent( )内部,如果子View添加了Touch事件的监听,则执行onTouch( )方法,如果该方法返回false,继续执行onTouchEvent( ),如果还返回false说明子View不消费事件,事件就会被回传到ViewGroup,让ViewGroup选择下一个View,如果都没有View接受该事件,则由ViewGroup处理,这时会onTouchEvent( )来处理,如果onTouchEvent( )返回false(即ViewGroup也不消费事件),则由回传到Activity.onTouchEvent( )处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值