目录
【view】- 测量流程
【view】- 布局流程
【view】- 绘制流程
【view】- setContentView方法和UI绘制流程(源码分析)
简介
前面4篇文章对setContentView执行流程,UI绘制流程进行的讲解,基本上可以知道UI是经过怎样的过程呈现在我们的眼前,但是对于一些触摸操作引起的界面变换,不管是UI切换,动画等等。那么这些是离不开触摸事件分发的。如果不了解触摸事件分发机制,在做一些触摸事件处理的时候就会刚觉很迷茫和无从下手。这篇文章带大家一起了解触摸事件分发机制。
分发
当我们手指触摸手机屏幕的时候,手机硬件会采集到触摸信号,然后通过转换传递给系统,再由系统传递给应用,应用接收到后对事件进行分发。
分析
分析触摸事件分发,总得有个起点,不可能从触摸事件最开始处分析,我们得找一个比较合适的分析点。其实我们可以从Activity(PhoneWindow)开始,因为触摸事件会先传递给Activity,然后传递给window,再由window分发给View组件。
Activity-> window -> View组件
Activity
可能很多人都知道,触摸事件首先会调用dispatchTouchEvent(MotionEvent ev)方法,那么是谁将事件传递给它的呢?会不会有其它方法比它更早调用呢?下面一一解答。
查看系统源码
/frameworks/base/core/jni/android_view_InputEventReceiver.cpp
定位到consumeEvents方法,组装触摸事件并通知给Java层。
case AINPUT_EVENT_TYPE_MOTION: {
...
MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
*outConsumedBatch = true;
}
inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
break;
}
通知给Java层
if (inputEventObj) {
...
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj,
displayId);
...
} else {
...
}
看一下有关的注册信息
int register_android_view_InputEventReceiver(JNIEnv* env) {
...
jclass clazz = FindClassOrDie(env, "android/view/InputEventReceiver");
gInputEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz,
"dispatchInputEvent", "(ILandroid/view/InputEvent;I)V");
...
}
并通过InputEventReceiver(WindowInputEventReceiver)的dispatchInputEvent()进行处理,这里就返回到我们常见的Java世界了。WindowInputEventReceiver是ViewRootImpl中的一个内部类。而在dispatchInputEvent()方法中会调用onInputEvent(InputEvent event, int displayId)方法。
public void onInputEvent(InputEvent event, int displayId) {
enqueueInputEvent(event, this, 0, true);
}
在 enqueueInputEvent(event, this, 0, true)方法中会执行doProcessInputEvents()。
void doProcessInputEvents() {
while (mPendingInputEventHead != null) {
...
long eventTime = q.mEvent.getEventTimeNano();
long oldestEventTime = eventTime;
if (q.mEvent instanceof MotionEvent) {
MotionEvent me = (MotionEvent)q.mEvent;
if (me.getHistorySize() > 0) {
oldestEventTime = me.getHistoricalEventTimeNano(0);
}
}
mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
deliverInputEvent(q);
}
...
}
处理时间参数,然后调用deliverInputEvent(q)方法。
private void deliverInputEvent(QueuedInputEvent q) {
...
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
...
if (stage != null) {
handleWindowFocusChanged();
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
递归每一个InputStage进行处理,看一下mFirstPostImeInputStage和mFirstInputStage,mSyntheticInputStage初始化。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStag
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStag
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStag
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;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
看一下ViewPostImeInputStage类的onProcess(QueuedInputEvent q)方法。
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);
}
}
}
调用processPointerEvent(q)方法
private int processPointerEvent(QueuedInputEvent q) {
...
boolean handled = mView.dispatchPointerEvent(event);
...
}
读过前面文章的朋友应该知道mView是DecorView。看一下DecorView中的dispatchPointerEvent(event)方法。DecorView没有实现dispatchPointerEvent方法,但是继承View类实现了。
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
调用DecorView的dispatchTouchEvent(event)方法。
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
mWindow是那里来的呢,可以看一下【view】- setContentView方法和UI绘制流程(源码分析)这篇文章。mWindow是Activity中的传家的Window实例。而在执行Activity的attach方法时,会设置回调。
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setCallback(this);
this就是Activity实例对象。于是就传递到了Activity的dispatchTouchEvent(MotionEvent ev)方法中。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow()获取mWindow,调用superDispatchTouchEvent(ev)。mWindow是PhoneWindow类型。
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
mDecor是顶层布局DecorView实例。调用DecorView中的superDispatchTouchEvent(MotionEvent event)方法。
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
调用父类dispatchTouchEvent方法,这时就会调用到ViewGroup中的dispatchTouchEvent(MotionEvent ev)方法。接下来就是View之间的传递的分发了。
总结
经过上面的分析:
触摸事件由底层调用ViewRootImpl $WindowInputEventReceiver中的dispatchInputEvent方法。
然后调用DecorView中的dispatchPointerEvent方法,这时候并没有在View之间进行传递,而是先把触摸事件传递给Activity,通过Window.Callback调用Activity的dispatchTouchEvent(MotionEvent ev)方法。
在Activity又调用PhoneWindow中的superDispatchTouchEvent(MotionEvent event)方法。
在PhoneWindow中调用DecorView中的superDispatchTouchEvent(MotionEvent event),然后进行View间的触摸事件分发。
所以有了下面:
Activity -> window -> view组件
网友评论