View体系详解(3)
前言:看了大概一个月SystemUI的相关源码,里面关于自定义View的知识比较多,迫使自己要去了解以前不太懂的显示子系统的知识,以前只知道一些粗略的view知识,如它是用来显示具体画面的,它的载体是window,它可以复写事件处理函数去处理某些点击事件,自定义view要实现onMeasure, onLayout, onDraw等,但是一直比较模糊,只是知道个大概,经过一阵子的源码和博客的阅读,对view体系有了许多新认识和领悟,因此记录下来。计划分以下几部分:
View体系详解(1):View体系总览
View体系详解(2):自定义View流程以及系统相关行为
View体系详解(3):View事件处理机制
*写的不好请理解,由于自己知识水平和技术经验所限,不可避免有错漏的地方,恳请指正。***
View事件处理机制
input事件对应的类是InputEvent,它是基类,具体的扩展类为KeyEvent和MotionEvent,即按键事件和运动事件,按键一般是点击相关,场景比较简单,比如电源键和音量键,而motion事件一般是用户在屏幕上(触摸等)的和有运动轨迹(鼠标移动等)的操作,场景会比较多样复杂。
在View体系详解(2):自定义View流程以及系统相关行为 中,我们知道在调用WindowManager的addView后,会在WMS中创建WindowState,并且创建InputChannel,注册InputReceicer,从而可以在对应window产生了input事件后,可以接收到。 从宏观角度讲,当我们点击或者触摸产生后,会有电压之类的变化,产生中断,驱动程序处理完成后传递到EventHub,然后唤醒systemserver的InputReader线程读取,再由InputDispatcher线程去分发,然后根据当前事件产生的类型,以及所属的window,最后派发到对应上层的ViewRootImpl类的注册的接收器InputReceiver中,从这里才开始派发流程。这些流程有个概念就行,那么现在看看InputReceiver这个抽象类的入口函数:
// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq); //保存此次接受到的input事件
onInputEvent(event); //开始派发
}
ViewRootImpl中的WindowInputEventReceiver中复写了onInputEvent:
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
由此可见,input事件的在队列中等待处理的,这是一个链表队列,有几个变量需要知道:
// Pool of queued input events.
private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10;
private QueuedInputEvent mQueuedInputEventPool; //InputEvent对象的回收池
private int mQueuedInputEventPoolSize; //当前回收池用掉的容量
--------------------------------------------------------------------------
QueuedInputEvent mPendingInputEventHead; //链表头
QueuedInputEvent mPendingInputEventTail; //链表尾
int mPendingInputEventCount; //链表中对象的数量
然后再看看入队操作:
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); // 尝试从回收池中复用空对象,没有则新建一个返回
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) { //链表尾为空,说明链表为空,则让链表头指向此event,链表尾同理
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else { //否则链表尾指向该事件,即从尾部插入
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
if (processImmediately) {
doProcessInputEvents(); //马上处理,从native层上来的事件都走这
} else {
scheduleProcessInputEvents(); //稍后处理
}
}
处理过程:
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
while (mPendingInputEventHead != null) { //链表头不为空,说明队列中有待处理的input事件
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext; //链表头指向队列中的第二个input事件
if (mPendingInputEventHead == null) { //如果指向为null,说明队列中的事件全部处理完了,链表尾也置null
mPendingInputEventTail = null;
}
q.mNext = null; //本次处理的事件从链表中脱离出来,方便待会回收
mPendingInputEventCount -= 1;
deliverInputEvent(q); //开始派发事件
}
}
至此开始进入派发流程,前面都是对于队列的操作,安卓中像这种回收复用对象的处理方式很多,如handler机制中的Message也是这样,避免了大量的new和destroy对象的操作,节省了内存操作,对于频繁的操作来说是可以提升性能的。
派发流程是责任链设计模式,一旦input事件被这条链上的任一对象处理了,就中断input事件的传递,然后返回。在setView()的时候,最后会创建一系列的InputStage对象,然后组成一个责任链:
// Set up the input pipeline.
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);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
开始派发事件的函数:
private void deliverInputEvent(QueuedInputEvent q) {
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (stage != null) {
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
一般是从mFristInputStage中开始传递,其他特殊情况先不考虑,我也暂时没碰到过,那么从责任链我们知道,mFirstInputStage指向的是NativePreImeStage对象,调用的deliver函数为父类的,并未复写:
/**
* Delivers an event to be processed.
*/
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
apply(q, onProcess(q)); //走这,调用子类的onProcess,使其有机会去处理,如果没处理,那么就传递给责任链上的下一个对象,直到遍历完
}
}
该对象的onProcess函数:
@Override
protected int onProcess(QueuedInputEvent q) {
if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
mInputQueue.sendInputEvent(q.mEvent, q, true, this);
return DEFER;
}
return FORWARD;
}
这是往native传递keyevent事件,我们先不看,假设它没被处理,那么返回的是FORWARD,这表明这个对象不处理此事件,传递给下一个,这样最后到了ViewPostImeInputStage对象,又走一遍流程,所以我们主要看ViewPostImeInputStage对象的onProcess函数能否处理掉此次事件:
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) { //如果是keyevent
return processKeyEvent(q);
} else { //是motionevent
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);
}
}
}
从这里开始,进入到我们熟悉的View树遍历环节了。这里着重讲processPointerEvent(q)这个函数,因为我们用的最多,如果是keyevent,遍历逻辑是类似的。
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
boolean handled = mView.dispatchPointerEvent(event);
return handled ? FINISH_HANDLED : FORWARD;
}
可以看到这里调用了顶层View的dispatchPointerEvent()进入view树的遍历了,如果返回了true,说明找到了事件的处理者,那么我们就返回FINISH_HANDLED,这样就完成了此次input事件的处理,这个稍后谈。从这回到了我们的View里面:
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) { //是否为touch事件是底层根据手势等决定的,我们一般情况下认为是
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
由于这里是顶层View,所以我们要找ViewGroup中的复写的dispatchTouchEvent():
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) { //如果允许拦截事件
intercepted = onInterceptTouchEvent(ev); //决定是否拦截本次touch事件
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null, //从这里往子view派发事件,直到有人返回true
TouchTarget.ALL_POINTER_IDS);
}
return handled;
}
注意到我们可以复写onInterceptTouchEvent函数去拦截从顶层view开始派发的input事件,这个可以根据业务场景复写,现在我们不考虑拦截的情况,让派发流程进行下去。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (child == null) {
handled = super.dispatchTouchEvent(event); //没有子view,那就自己处理
} else {
handled = child.dispatchTouchEvent(event); //交给子view去处理
}
event.setAction(oldAction);
return handled;
}
终于到子view去处理了,这里就有熟悉的身影了:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
final int actionMasked = event.getActionMasked();
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { //先调用onTouch,如果处理了就返回true
result = true;
}
if (!result && onTouchEvent(event)) { //再调用onTouchEvent,这里前提是onTouch没被复写或者返回了false
result = true;
}
}
return result;
}
---------------------------------------------------------------------------------------------
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP: //手指抬起
//省略
performClick(); //如果手指抬起来,复写的click事件处理监听会被调用
case MotionEvent.ACTION_DOWN:
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) { //可以复写此函数然后阻断longclick被调用,表明view被按下
break;
}
checkForLongClick(0, x, y); //按下去,那么有可能是长按事件,判断是长按的话,复写的onLongClick会被调用
case MotionEvent.ACTION_CANCEL: //cancel事件我们就不处理
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE: //move事件是手指在屏幕上划动
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
这里就明确了,我们平常调用的api,诸如setOnClickEventListener等,是怎么被调用到我们复写的函数了,就是在派发过程中派发到了具体某个case,触发的。这里也揭示了我们监听的优先级,即onTouch > onTouchEvent > onClick > performButtonActionOnTouchDown > onLongClick。至此,就完成了view树的整个遍历了。贴上一张图方便理解:
由于篇幅关系,很多代码省略了,以后如果遇到某些情况,还是得仔细看看源码怎么回事,这里还是介绍一个较为详细的inputevent是如何派发的,它们之间的优先级是什么,此处也只是java层的framework代码,至于如何从eventhub中读取到事件,事件怎么知道要派发给哪个窗口等逻辑,此处均不涉及。
这里假设我们复写了onTouchEvent事件,然后返回了true,那么ViewRootImpl那边会怎样呢?之前就说过,一旦onProcess的dispatchPointerEvent返回了true,那么我们就会返回FINISH_HANDLED,最后调用finishInputEvent:
private void finishInputEvent(QueuedInputEvent q) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (q.mReceiver !=null) {
boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) !=0;
q.mReceiver.finishInputEvent(q.mEvent, handled); //告诉底层派发的线程,该事件处理完毕
}else {
q.mEvent.recycleIfNeededAfterDispatch();
}
recycleQueuedInputEvent(q); //回收该事件的对象
}
这样,一次完整的事件派发就结束了。
总结:
1.input事件的处理是责任链模式,让每个对象和view都能遍历到,一旦产生了处理者,那么就交给这个处理者去处理,不再传递下去。
2.input事件的处理优先级是代码中的调用顺序决定的,onTouch如果不处理,那么onTouchEvent才会被调用,然后如果有子view,那么就又优先让子view去遍历一遍,直到找到处理者,当然我们也可以复写某些函数去阻断和处理input事件。