Android事件分发机制——ViewRootImpl篇(前传)

我们都知道View事件的分发顺序是Activity—>Window—>View。

//Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //调用PhoneWindow.superDispatchTouchEvent()
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

//PhoneWindow
@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
    	//mDecor即为DecorView
        return mDecor.superDispatchTouchEvent(event);
    }

//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
		//调用父类ViewGroup的dispatchTouchEvent进行事件分发
        return super.dispatchTouchEvent(event);
    }

由于DecorView extends FrameLayout,而DrameLayout extends ViewGroup,所以最终事件分发从ViewGroup.dispatchTouchEvent开始,如果对以上跳转不清楚可以参考Android窗口机制(一)——Window,PhoneWindow,DecorView理解

但是Activity的事件来源在哪里呢?答案和ViewRootImpl有很大的关系。

首先,事件的根本来源是来自于Native层的嵌入式硬件,然后会经过InputEventReceiver接受事件交给ViewRootImpl,然后将事件传递给DecorView,最终DecorView再交给Activity,整个体系的事件分发顺序为:
事件传递顺序
我们从ViewRootImpl的dispatchInputEvent方法入手。

    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:

  • InputEvent:输入事件的基类,它有两个子类,分别是KeyEvent和MotionEvent对应键盘输入事件和屏幕触摸事件;
  • InputEventReceiver:为应用程序提供了一个低级的机制来接收输入事件,也就是用来接收输入事件的,然后交给ViewRootImpl的dispatchInputEvent去分发。
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;
            }
        }

转发到UI线程后,调用到enqueueInputEvent:

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

可以看到enqueueInputEvent将当前的输入事件加入队列中,QueuedInputEvent相当于一个链表,可以看到里面成员变量有next,用来链接下一个成员。

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;
    }

紧接着我们继续分析:

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

processImmediately则是判断是同步还是异步,前面我们在handler中调用的,因为是在UI线程,肯定是同步的,所以传递了参数是true,如果是异步,则调用到scheduleProcessInputEvents()。

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

case MSG_PROCESS_INPUT_EVENTS:
	mProcessInputEventsScheduled = false;
	//最终还是调用doProcessInputEvents()处理
	doProcessInputEvents();
	break;

最终还是调用doProcessInputEvents()处理:

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);
        }
    }

在ViewRootImpl中,有一系列类似于InputStage的子类,它是一个抽象类,其deliver方法会处理一个输入事件,处理完成之后会调用finishInputEvent方法。

它有很多子类,对应具体的InputStage,每种InputStage可以处理一定的事件类型,比如AsyncInputStage、SyntheticInputStage、NativePostImeInputStage、ViewPreImeInputStage、ViewPostImeInputStage等。

其子类实现了InputStage的一些抽象方法,比如onProcess、onDeliverToNext、processKeyEvent、processPointerEvent、processTrackballEvent、processGenericMotionEvent,在不同的情况下onProcess、onDeliverToNext等方法就会被回调。

当一个InputEvent到来时,ViewRootImpl会寻找合适它的InputStage来处理,InputStage会先调用deliver()开始处理。
InputState处理流程
最终的事件分发处理则是在apply方法里的onProcess方法中。

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

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;
	//调用mView.dispatchPointerEvent()
	boolean handled = mView.dispatchPointerEvent(event);
	//...
	return handled ? FINISH_HANDLED : FORWARD;
}

其实在processKeyEvent、processPointerEvent、processTrackballEvent、processGenericMotionEvent方法中都有一句很关键的一句代码。

View mView;
mView.dispatchKeyEvent(event)//按键事件
mView.dispatchPointerEvent(event)
mView.dispatchTrackballEvent(event)
mView.dispatchGenericMotionEvent(event)

那这里的mView到底是什么呢?如果大家不是很清楚,可以看一下我之前的一篇博文
Window窗口机制(三)——WindowManager,ViewRootImpl,View理解
就了解了,其实这里的mView就是我们的DecorView,因为ViewRootImpl通过setView方法将DecorView添加到PhoneWindow中。

由于DecorView没有上述的mView.的几个方法,所以向其父类寻找,最终在View中可以看到其身影。

//View
public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
        	//判断是触摸方法,调用dispatchTouchEvent()
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

由于这里多态的存在,会调用到View的子类DecorView.dispatchTouchEvent。

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

可以看出DecorView最终会调用cb.dispatchTouchEvent方法,那么这个Callback是什么?其实这个Callback就是当前的Activity。

//Activity
public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {

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

//Activity.attach
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        attachBaseContext(context);
 
        mFragments.attachHost(null /*parent*/);
 
        mWindow = new PhoneWindow(this);
        //setCallback
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);

到这里,点击事件就传入了Activity,之后的流程也就是文章一开始分析的一样啦。

总结

ViewRootImpl事件分发流程图
ViewRootImpl事件分发流程图

接下来事件在View和ViewGroup中是如何传递的,可以参考以下博文Android事件分发机制——View(一)

发布了100 篇原创文章 · 获赞 44 · 访问量 12万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览