【view】- 触摸事件分发(1)

目录

【view】- 测量流程
【view】- 布局流程
【view】- 绘制流程
【view】- setContentView方法和UI绘制流程(源码分析)
【view】- 触摸事件分发(2)

简介

前面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组件

参考

快速了解Android6.0系统触摸事件工作原理——InputManagerService
ANR机制以及问题分析

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

推荐阅读更多精彩内容