前言:百度上搜索了一下关于android控件系统的文章,发现只有《深入理解andorid卷3》中的"深入理解控件系统"那个章节对android控件系统的描述较为全面,但那篇文章是以android4.0的framework源码进行剖析的,过于老旧,最新的关于android控件系统的剖析文章很少。于是我以android8.0的源码对控件系统重新解构了一下,这应该是目前为止最全面的解析了;
这次剖析借鉴了《深入理解andorid卷3》中的"深入理解控件系统"章节,有很多代码解释就直接从那边摘录了过来,难点部分,加入了自己的注释和理解;
控件系统,其实可以分为两个部分,一个部分是控件树的绘制,第二个部分是输入事件在控件树中的传递;
这次我们分析输入事件在控件树中的传递;
本次剖析分4个章节来进行,分别是:
①输入事件在ViewRootImpl中的处理;
②按键事件的处理;
③触摸事件的处理;
④输入事件在控件树中传递的知识体系图;
要想了解android的控件系统,就必须要了解ViewRootImpl,它是一个控件树的根,一个控件树只有一个根,控件树的绘制和输入事件在控件树中的传递都由它负责;
我们在研究输入事件在控件树中的传递时,只要是剖析ViewRootImpl.java,ViewGroup.java,View.java,这三个类的源码;
好了,前言就这样了,开始分析吧!!!
本节分析的是:输入事件在ViewRootImpl的处理
输入事件的派发,是从窗口开始派发的,这里暂时过滤掉窗口派发,只研究,输入事件是如何从窗口传递到指定的控件的;
控件树中的输入事件派发是由ViewRootImpl为起点,沿着控件树一层一层传递给目标控件,最终再回到ViewRootImpl的一个环形过程;
在ViewRootImpl.setView()中,新的窗口被创建之后,ViewRootImpl使用WMS分配的InputChannel以及当前线程的Looper一起创建了InputEventReceiver的子类WindowInputEventReceiver的一个实例,并将其保存在ViewRootImpl.mInputEventReceiver成员之中,这标志着从设备驱动到本窗口的输入事件通道的正式成立。至此每当有输入事件到来之时,ViewRootImpl都可以通过WindowInputEventReceiver.onInputEvent()回调得到这个事件并进行处理;
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
.........................................
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
}
final class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event, int displayId) {
//将事件入队
enqueueInputEvent(event, this, 0, true);
}
}
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
/*将InputEvent,receiver,flags封装成QueuedInputEvent,QueuedInputEvent是
输入事件在ViewRootImpl中的存在形式,并且QueuedInputEvent是一个链表形式的数据结构,
每个输入事件都链接在了一起,ViewRootImpl将会沿着链表从头到尾地逐个处理输入事件*/
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
/*将mPendingInputEventHead指向q,由于q本身是一个链表形式,所以它可以检索到所有的输入事件*/
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
if (processImmediately) {
//直接在当前线程中开始对输入事件的处理工作
doProcessInputEvents();
} else {
//将处理事件的请求发送给主线程的Handler,随后进行处理
scheduleProcessInputEvents();
}
}
点评:
①ViewRootImpl中存在着一个输入事件队列mPendingInputEventHead;
②输入事件在队列中以QueuedInputEvent的形式存在,QueuedInputEvent保存了输入事件的队列,接收事件的InputEventReceiver,以及一个next成员用于指向下一个QueuedInputEvent;
分析doProcessInputEvents()代码:
void doProcessInputEvents() {
//遍历整个输入事件队列,逐个处理这些事件
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
//完成单个事件的整个处理流程
deliverInputEvent(q);
}
}
继续分析deliverInputEvent():
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);
}
}
点评:deliverInputEvent()方法中有个InputStage,它是什么呢?它是一个抽象类,表示的输入事件的处理逻辑。输入事件可以进行分类,可以分成按键事件,触摸事件,轨迹球事件等,基于此,InputStage也就有了很多的相应的子类;
各种输入事件的声明如下:
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;
点评:上述代码,是一个典型的装饰模式,装饰模式在这里面的理解就是动态的添加功能,我们根据代码的逻辑来了解下这个模式;
先从下面这段开始:
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
这段代码是用来判断,输入事件是否要传递给输入法处理的;看下shouldSkipIme方法:
public boolean shouldSkipIme() {
if ((mFlags & FLAG_DELIVER_POST_IME) != 0) {
return true;
}
return mEvent instanceof MotionEvent
&& (mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
|| mEvent.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER));
}
点评:从源码中可以看出,如果:
①mFlags中存在FLAG_DELIVER_POST_IME标签;
②mEvent的类型是MotionEvent,并且输入事件的类型是触摸或者编码;
满足上面两种情况的一种,即表示要输入事件要跳过输入法,即输入事件不传递给输入法处理;
假设:我们要跳过输入法,那么stage = mFirstPostImeInputStage,而在前面的声明中我们知道mFirstPostImeInputStage = earlyPostImeStage,
earlyPostImeStage = new EarlyPostImeInputStage(new NativePostImeInputStage(new ViewPostImeInputStage(new SyntheticInputStage()), "aq:native-post-ime:" + counterSuffix));
继而,我们来分析mFirstPostImeInputStage.deliver(q)方法;
分析流程如下:
①分析mFirstPostImeInputStage的deliver方法也就是分析earlyPostImeStage的deliver方法;
②earlyPostImeStage继承自InputStage,它的deliver也在InputStage中;
InputStage.java
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));
}
}
点评:deliver中的关键代码是:apply(q, onProcess(q));
③先看onProcess(q):它表示对事件的加工,每种事件类型的加工都不同,所以,每个InputStage的子类都重写了InputStage的onProcess方法;
EarlyPostImeInputStage.java
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);
}
}
return FORWARD;
}
④onProcess的返回值表示了是否继续执行下一步操作,FORWARD表示继续向下一步执行;
怎么理解这句话呢?
上面我们说过,EarlyPostImeInputStage的构建运用了装饰模式,装饰模式可以动态的添加功能,这个动态添加的新功能就是它的构造参数NativePostImeInputStage的功能,NativePostImeInputStage的功能在它的onProcess方法中,所以,EarlyPostImeInputStage.onProcess返回值,表示的就是,是否需要执行NativePostImeInputStage的onProcess方法;
⑤假设EarlyPostImeInputStage.onProcess方法的返回值是FORWARD,再回到apply(q, onProcess(q))方法中,即apply(q, FORWARD),看下apply()方法:
InputStage.java
protected void apply(QueuedInputEvent q, int result) {
if (result == FORWARD) {
//继续向前推进
forward(q);
} else if (result == FINISH_HANDLED) {
//结束
finish(q, true);
} else if (result == FINISH_NOT_HANDLED) {
//结束
finish(q, false);
} else {
throw new IllegalArgumentException("Invalid result: " + result);
}
}
protected void forward(QueuedInputEvent q) {
onDeliverToNext(q);
}
protected void onDeliverToNext(QueuedInputEvent q) {
if (mNext != null) {
/*mNext就是它的构造参数,这里指的是NativePostImeInputStage,QueuedInputEvent事件传递给NativePostImeInputStage*/
mNext.deliver(q);
} else {
finishInputEvent(q);
}
}
点评:分析到了这里,QueuedInputEvent事件传递到了NativePostImeInputStage的deliver方法中,分析NativePostImeInputStage的deliver方法,和分析earlyPostImeStage的deliver方法的流程是一致的;
装饰模式的好处,就是将每个功能点进行了封装,可以动态的添加功能;
所以,QueuedInputEvent事件的传递流程应该是这样的:
EarlyPostImeInputStage-->NativePostImeInputStage-->ViewPostImeInputStage-->SyntheticInputStage;
每种输入事件的不同处理逻辑都在它们各自重写的onProcess方法中,所以重点是要研究InputStage各个子类的onProcess方法;
⑥这里主要研究的是ViewPostImeInputStage的onProcess方法,该方法是对输入事件真正的处理;
ViewPostImeInputStage.java
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 {
//处理其他Motion事件,如游戏手柄等等
return processGenericMotionEvent(q);
}
}
}
点评:根据InputEvent的子类类型或者Source的不同,分别用4种方法处理4种类型的事件;
processKeyEvent(q):派发按键事件,它选择的是基于焦点的派发策略;
processPointerEvent(q):派发触摸事件,它选择的是基于位置的派发策略;
processTrackballEvent(q):派发轨迹球事件,在使用基于焦点的派发策略将事件派发之后,倘若没有任何一个派发目标处理此事件,它将会把事件转化为一个表示方向键的按键事件并添加到ViewRootImpl的输入事件队列中;
processGenericMotionEvent(q):用于派发其他的Motion事件;
看下processKeyEvent和processPointerEvent的代码:
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
}
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
boolean handled = mView.dispatchPointerEvent(event);
return handled ? FINISH_HANDLED : FORWARD;
}
发现:mView.dispatchKeyEvent(event)和mView.dispatchPointerEvent(event),这就是按键事件和触摸事件在控件树中传递的来源啊!!!其中mView.dispatchKeyEvent(event)还有一个分支,是关于输入事件传递输入法的,立马就会看到;
⑦当输入事件被处理了之后,会返回处理结果,比如在ViewPostImeInputStage中,processKeyEvent(q)返回了FINISH_HANDLED后,接下来代码会怎么走呢?我们来分析下这个处理结果的过程;
回到apply(q, onProcess(q))方法中,即apply(q, FINISH_HANDLED),看下apply()方法:
protected void apply(QueuedInputEvent q, int result) {
if (result == FORWARD) {
//继续向前推进
forward(q);
} else if (result == FINISH_HANDLED) {
//结束
finish(q, true);
} else if (result == FINISH_NOT_HANDLED) {
//结束
finish(q, false);
} else {
throw new IllegalArgumentException("Invalid result: " + result);
}
}
protected void finish(QueuedInputEvent q, boolean handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
if (handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
}
forward(q);
}
protected void forward(QueuedInputEvent q) {
onDeliverToNext(q);
}
protected void onDeliverToNext(QueuedInputEvent q) {
if (mNext != null) {
mNext.deliver(q);
} else {
finishInputEvent(q);
}
}
点评:上面的代码只做了两个工作,一是给QueuedInputEvent添加了处理完成的标签,二是调用 mNext.deliver(q),继续将QueuedInputEvent传递给下一个事件处理者,这里的mNext指的是SyntheticInputStage,继续看:
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
ViewDebugManager.getInstance().debugInputDispatchState(q.mEvent, this.toString());
apply(q, onProcess(q));
}
}
protected void forward(QueuedInputEvent q) {
onDeliverToNext(q);
}
protected void onDeliverToNext(QueuedInputEvent q) {
if (mNext != null) {
mNext.deliver(q);
} else {
//完成输入事件的传递
finishInputEvent(q);
}
}
private void finishInputEvent(QueuedInputEvent q) {
boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
//回收输入事件并向InputDispatcher发送反馈
if (q.mReceiver != null) {
q.mReceiver.finishInputEvent(q.mEvent, handled);
} else {
q.mEvent.recycleIfNeededAfterDispatch();
}
/*回收不再有效的QueuedInputEvent实例,下次使用obtainQueuedInputEvent()时可以复用这个实例*/
recycleQueuedInputEvent(q);
}
点评:研究上面的代码的代码流程我们发现,当输入事件QueuedInputEvent被处理了之后,依然继续将它传递给下一个被装饰者,事件传递路径是:EarlyPostImeInputStage-->NativePostImeInputStage-->ViewPostImeInputStage-->SyntheticInputStage,直到传递到SyntheticInputStage为止,此时它的mNext为null,即传递结束,调用finishInputEvent(q)方法,这个方法用于向InputDispatcher发送输入事件处理完毕的反馈,同时也标志着一条输入事件的处理流程的终结;
⑧还记得这段代码吗?
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
我们刚刚研究的输入事件的传递流程是跳过了输入法,即选择了mFirstPostImeInputStage,如果我们要把输入事件传递给输入法,那么它的流程是怎么样的呢?
把输入事件传递给输入法,也就是stage = mFirstInputStage,又因mFirstInputStage = nativePreImeStage,所以mFirstInputStage = new NativePreImeInputStage(new ViewPreImeInputStage(new ImeInputStage(earlyPostImeStage,"aq:ime:" + counterSuffix)),"aq:native-pre-ime:" + counterSuffix);
即输入事件的传递的次序是:NativePreImeInputStage-->ViewPreImeInputStage-->ImeInputStage-->EarlyPostImeInputStage-->NativePostImeInputStage-->ViewPostImeInputStage-->SyntheticInputStage;
先看ViewPreImeInputStage对输入事件的处理:
ViewPreImeInputStage.java
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
}
return FORWARD;
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
//如果返回true,则结束派发工作
if (mView.dispatchKeyEventPreIme(event)) {
return FINISH_HANDLED;
}
return FORWARD;
}
点评:终于走到这里了,mView.dispatchKeyEventPreIme(event)方法就是按键事件在控件树中传递的源泉,后面分析按键事件的派发时,就是分析这个方法;
再看ImeInputStage对输入事件的处理:
protected int onProcess(QueuedInputEvent q) {
if (mLastWasImTarget && !isInLocalFocusMode()) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
final InputEvent event = q.mEvent;
//输入法对输入事件的处理,稍后会详细分析
int result = imm.dispatchInputEvent(event, q, this, mHandler);
if (result == InputMethodManager.DISPATCH_HANDLED) {
return FINISH_HANDLED;
} else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
return FORWARD;
} else {
return DEFER;
}
}
}
return FORWARD;
}
点评:输入法通过InputMethodManager.dispatchInputEvent方法对输入事件进行了处理;
⑨分析了这么久,终于看到了mView.dispatchKeyEventPreIme(event)和mView.dispatchPointerEvent(event)方法,也就意味着输入事件在ViewRootImpl中的流程结束了,接下来将输入事件派发给控件树了;