Andorid触摸事件分发机制(4)之ViewRootImpl

Android视图加载流程(3)之ViewRootImpl的UI刷新机制

前三篇文章分别整理了View,ViewGroup和Activity的事件分发过程,我们今天来讲最后一篇ViewRootImpl(PS:ViewRootImpl也是视图加载很关键的类)

ViewRootImpl事件分发

前几篇我们是介绍了View,ViewGroup和Activtiy的事件分发,很多人以为就此结束了,肯定不是!毕竟触摸事件最先触发的是底层硬件,而不是我们的Activity。

一个触摸行为先通过底层硬件来传递捕获,交给ViewRootImpl,接着事件传递给DecorView,DecorView交给PhoneWindow,PhoneWindow在交给Activity,然后才是我们上几篇讲的事件分发了。

硬件->ViewRootImpl->DecorView->PhoneWindow->Activity

源码解读:

Step1 ViewRootImpl

WMS(Window Manager Service)接到消息后,会调用以下方法

public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
    SomeArgs args = SomeArgs.obtain();
    args.arg1 = event;
    args.arg2 = receiver;
    Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
    msg.setAsynchronous(true);
    mHandler.sendMessage(msg);
}

此方法传入两个参数:InputEvent和InputEventReceiver

  1. InputEvent:输入事件的基类,它有两子类:KeyEvent(键盘输入事件),MotionEvent(屏幕触摸事件)
  2. InputEventReceiver:为应用程序提供一个接收者来接收输入事件。也就是用来接收输入事件->交给ViewRootImpl的dispatchInputEvent去分发

最后为Hanlder发送标志为MSG_DISPATCH_INPUT_EVENT消息~

Step2 ViewRootImpl

现ViewRootImpl在UI线程中。

final class ViewRootHandler extends Handler {
    ...
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
             ...
            case MSG_DISPATCH_INPUT_EVENT: {
                SomeArgs args = (SomeArgs)msg.obj;
                InputEvent event = (InputEvent)args.arg1;
                InputEventReceiver receiver = (InputEventReceiver)args.arg2;
                enqueueInputEvent(event, receiver, 0, true);
                args.recycle();
            } 
            break;
            ...
        }
    }
}

接收消息的时候后拆分下数据,传给enqueueInputEvent

Step3 ViewRootImpl

void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    adjustInputEventForCompatibility(event);
    
    //part01
    //将当前输入事件加入队列中排列等候执行
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
    //输入事件添加进队列后,加入输入事件的默认尾部
    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    //队列计数
    mPendingInputEventCount += 1;
    ...
    //part02
    //processImmediately则是判断是同步还是异步,前面我们在handler中调用的,因为是在UI线程,肯定是同步的,所以传递了参数是true,如果是异步,则调用到scheduleProcessInputEvents()
    if (processImmediately) {
        doProcessInputEvents();
    } else {
        scheduleProcessInputEvents();
    }
}

传入的InputEvent很快的就加入了一个队列QueueInputEvent中,且QueueInputEvent相当于一个链表。

part01
QueueInputEvent
private static final class QueuedInputEvent {
    ...
    public QueuedInputEvent mNext;//指向下一个事件
    public InputEvent mEvent;
    public InputEventReceiver mReceiver;
    ...
}

接着我们看方法obtainQueuedInputEvent

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接到队列的尾部

part02
if (processImmediately) {
    doProcessInputEvents();
} else {
    scheduleProcessInputEvents();
}

processImmediately是判断是否为同步异步,我们此时正在UI线程中,就是同步,即为true。我们先看下异步处理的情况,调用scheduleProcessInputEvents()

private void scheduleProcessInputEvents() {
    if (!mProcessInputEventsScheduled) {
        mProcessInputEventsScheduled = true;
        Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);
        msg.setAsynchronous(true);
        mHandler.sendMessage(msg);
    }
}

final class ViewRootHandler extends Handler {
    ...
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
             ...
            case MSG_PROCESS_INPUT_EVENTS: {
                mProcessInputEventsScheduled = false;
                doProcessInputEvents();
            } 
            break;
            ...
        }
    }
}

Hanlder发送标志为MSG_PROCESS_INPUT_EVENTS消息~。

我们可以发现,异步最终调用的方法与同步相同 都是调用doProcessInputEvents();

Step3 ViewRootImpl

void doProcessInputEvents() {
    //循环取出队列中的输入事件
    while (mPendingInputEventHead != null) {
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        ...
        mPendingInputEventCount -= 1;
        ...
        //分发处理
        deliverInputEvent(q);
    }

    //处理完所有输入事件,清除标志
    if (mProcessInputEventsScheduled) {
        mProcessInputEventsScheduled = false;
        mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
    }
}

可以看到该方法是用来循环获取队列中的输入事件

接着我们调用方法deliverInputEvent(q)

private void deliverInputEvent(QueuedInputEvent q) {
    //校验输入事件
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
    }

    InputStage stage;
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;
    } else {
        stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
    }

    if (stage != null) {
        stage.deliver(q);//调用处理
    } else {
        finishInputEvent(q);
    }
}

这里InputStage则是一个实现处理输入事件责任的阶段,它是一个基类,也就是说InputStage提供一系列处理输入事件的方法,也可以转发给其他事件处理,而具体的处理则是看它的实现类。每种InputStage可以处理一定的事件类型,比如AsyncInputStage、ViewPreImeInputStage、ViewPostImeInputStage等。当一个InputEvent到来时,ViewRootImpl会寻找合适它的InputStage来处理。
InputStage的处理情况为,会先调用deliver开始处理

理解图

Andorid触摸事件分发机制(4)之ViewRootImpl_第1张图片
理解图

最终的事件分发处理则是在apply方法里的onProcess方法。

Step4 ViewRootImpl

对于点击事件来说,InputState的子类ViewPostImeInputStage可以处理它,我们看下ViewPostImeInputStage的onProcess

@Override
protected int onProcess(QueuedInputEvent q) {
    if (q.mEvent instanceof KeyEvent) {
        return processKeyEvent(q);
    } else {
        // If delivering a new non-key event, make sure the window is
        // now allowed to start updating.
        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);
        }
    }
}

我们只看下触摸事件,它调用方法processPointerEvent(q)

private int processPointerEvent(QueuedInputEvent q) {
    final MotionEvent event = (MotionEvent)q.mEvent;

    mAttachInfo.mUnbufferedDispatchRequested = false;
    //mView即为DecorView
    boolean handled = mView.dispatchPointerEvent(event);
    ...
    return handled ? FINISH_HANDLED : FORWARD;
}

mView(DecorView)继承自FrameLayout。我们发现DecorView,FrameLayout,ViewGroup并没有dispatchPointerEvent方法,所以此方法肯定在View中。

Step5 View

public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

传入的MotionEvent经过判断是否为触摸事件~触摸即调用dispatchTouchEvent(event)

Step6 DecorView

DecorView有重写dispatchTouchEvent(event)

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final Callback cb = getCallback();
    return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
            : super.dispatchTouchEvent(ev);
}

如果有Callback则用Callback的dispatchTouchEvent(ev)否则直接使用super.dispatchTouchEvent(ev);

这个Callback从何而来呢?Callback为Window里的一个接口

public interface Callback {
    ...
    public boolean dispatchKeyEvent(KeyEvent event);
    ...
    public boolean dispatchTouchEvent(MotionEvent event);
}

有调用必定有实现的地方!

Step7 Activity

public class Activity extends ContextThemeWrapper
        implements  Window.Callback {
        
     final void attach(...) {
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
    }
}

果然Activity实现了Window下的Callback,且在attach方法中,新建PhoneWindow的同时也设置好了Callback!

若想详细了解attach此步,请进
Android视图加载流程(1)之SetContent( )的Step3

从以上我们也可以看出cb.dispatchTouchEvent也就是Activty的dispatchTouchEvent。刚好可以跟上一章文章对应起来~

总结 Summary

Andorid触摸事件分发机制(4)之ViewRootImpl_第2张图片

PS:本文整理自以下文章,若有发现问题请致邮 [email protected]
Hohohong ViewRootImpl源码分析事件分发

你可能感兴趣的:(Andorid触摸事件分发机制(4)之ViewRootImpl)