Andorid触摸事件分发机制(4)之ViewRootImpl

Android视图加载流程(3)之ViewRootImpl的UI刷新机制

前三篇文章分别整理了View,ViewGroup和Activity的事件分发过程,我们今天来讲最后一篇ViewRootImpl(PS:ViewRootImpl也是视图加载很关键的类)

ViewRootImpl事件分发

前几篇我们是介绍了View,ViewGroup和Activtiy的事件分发,很多人以为就此结束了,肯定不是!毕竟触摸事件最先触发的是底层硬件,而不是我们的Activity。

一个触摸行为先通过底层硬件来传递捕获,交给ViewRootImpl,接着事件传递给DecorView,DecorView交给PhoneWindow,PhoneWindow在交给Activity,然后才是我们上几篇讲的事件分发了。

硬件->ViewRootImpl->DecorView->PhoneWindow->Activity

源码解读:

Step1 ViewRootImpl

WMS(Window Manager Service)接到消息后,会调用以下方法

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

  1. InputEvent:输入事件的基类,它有两子类:KeyEvent(键盘输入事件),MotionEvent(屏幕触摸事件)
  2. InputEventReceiver:为应用程序提供一个接收者来接收输入事件。也就是用来接收输入事件->交给ViewRootImpl的dispatchInputEvent去分发

最后为Hanlder发送标志为MSG_DISPATCH_INPUT_EVENT消息~

Step2 ViewRootImpl

现ViewRootImpl在UI线程中。

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

接收消息的时候后拆分下数据,传给enqueueInputEvent

Step3 ViewRootImpl

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

传入的InputEvent很快的就加入了一个队列QueueInputEvent中,且QueueInputEvent相当于一个链表。

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

上面的代码简单理解就是将QueuedInputEvent接到队列的尾部

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

processImmediately是判断是否为同步异步,我们此时正在UI线程中,就是同步,即为true。我们先看下异步处理的情况,调用scheduleProcessInputEvents()

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

final class ViewRootHandler extends Handler {
    ...
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
             ...
            case MSG_PROCESS_INPUT_EVENTS: {
                mProcessInputEventsScheduled = false;
                doProcessInputEvents();
            } 
            break;
            ...
        }
    }
}

Hanlder发送标志为MSG_PROCESS_INPUT_EVENTS消息~。

我们可以发现,异步最终调用的方法与同步相同 都是调用doProcessInputEvents();

Step3 ViewRootImpl

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

这里InputStage则是一个实现处理输入事件责任的阶段,它是一个基类,也就是说InputStage提供一系列处理输入事件的方法,也可以转发给其他事件处理,而具体的处理则是看它的实现类。每种InputStage可以处理一定的事件类型,比如AsyncInputStage、ViewPreImeInputStage、ViewPostImeInputStage等。当一个InputEvent到来时,ViewRootImpl会寻找合适它的InputStage来处理。
InputStage的处理情况为,会先调用deliver开始处理

理解图

理解图

最终的事件分发处理则是在apply方法里的onProcess方法。

Step4 ViewRootImpl

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

@Override
protected int onProcess(QueuedInputEvent q) {
    if (q.mEvent instanceof KeyEvent) {
        return processKeyEvent(q);
    } else {
        // If delivering a new non-key event, make sure the window is
        // now allowed to start updating.
        handleDispatchWindowAnimationStopped();
        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) {
    final MotionEvent event = (MotionEvent)q.mEvent;

    mAttachInfo.mUnbufferedDispatchRequested = false;
    //mView即为DecorView
    boolean handled = mView.dispatchPointerEvent(event);
    ...
    return handled ? FINISH_HANDLED : FORWARD;
}

mView(DecorView)继承自FrameLayout。我们发现DecorView,FrameLayout,ViewGroup并没有dispatchPointerEvent方法,所以此方法肯定在View中。

Step5 View

public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

传入的MotionEvent经过判断是否为触摸事件~触摸即调用dispatchTouchEvent(event)

Step6 DecorView

DecorView有重写dispatchTouchEvent(event)

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

如果有Callback则用Callback的dispatchTouchEvent(ev)否则直接使用super.dispatchTouchEvent(ev);

这个Callback从何而来呢?Callback为Window里的一个接口

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

有调用必定有实现的地方!

Step7 Activity

public class Activity extends ContextThemeWrapper
        implements  Window.Callback {
        
     final void attach(...) {
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
    }
}

果然Activity实现了Window下的Callback,且在attach方法中,新建PhoneWindow的同时也设置好了Callback!

若想详细了解attach此步,请进
Android视图加载流程(1)之SetContent( )的Step3

从以上我们也可以看出cb.dispatchTouchEvent也就是Activty的dispatchTouchEvent。刚好可以跟上一章文章对应起来~

总结 Summary


PS:本文整理自以下文章,若有发现问题请致邮 caoyanglee92@gmail.com
Hohohong ViewRootImpl源码分析事件分发

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容