android控件系统:输入事件在控件树中的传递(1)

前言:百度上搜索了一下关于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中的流程结束了,接下来将输入事件派发给控件树了;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

renshuguo123723

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值