Android事件分发溯源详解

简介: 前两天华仔给我出了一道难题,我们俩研究了小半天,借着这个契机正好回顾了一下Android事件分发的相关知识点,于是有了这篇文章。Android事件分发机制大家都非常熟悉,大部分文章对这个过程的描述都是开始于Activity,但是事件是怎么传到Activity的?这里就涉及到几个重要的部分:Window,WMS,ViewRoot和DecorView。如果要理解事件分发的源头,就需要搞明白他们之间的关系,所以我们先来看看它们到底有什么关系?

前言


前两天华仔给我出了一道难题,我们俩研究了小半天,借着这个契机正好回顾了一下Android事件分发的相关知识点,于是有了这篇文章。

Android事件分发机制大家都非常熟悉,大部分文章对这个过程的描述都是开始于Activity,但是事件是怎么传到Activity的?

这里就涉及到几个重要的部分:Window,WMS,ViewRoot和DecorView。

如果要理解事件分发的源头,就需要搞明白他们之间的关系,所以我们先来看看它们到底有什么关系?


Window


Window是我们比较熟悉的,那么它是如何创建的?

我们来看Activity的attach函数:


@UnsupportedAppUsage
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
    attachBaseContext(context);
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(mWindowControllerCallback);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    ...
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    ...
    mWindowManager = mWindow.getWindowManager();
    ...
}
复制代码


我这里只展示一部分关键代码。当我们的activity创建完成后会执行attach,这是可以看到创建了PhoneWindow,同时也设置了WindowManager。

注意mWindow.setCallback(this);这行代码,Activity本身实现了Window.Callback接口:


public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, ... {
复制代码

这里将activity赋值给window的callback,在后面的流程中会发挥作用。


DecorView


DecorView是整个布局的最顶端的view,也就根布局。它很容易与ViewRoot搞混,ViewRoot其实不是View,后面再来说它。

DecorView是如何创建的,这一切要从setContentView说起:


public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
复制代码


可以看到执行了window的setContentView,它的源码:


@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        ...
    }
复制代码


可以看到一开始就执行installDecor,这里就是初始化DecorView:


private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
        }
    }
复制代码


可以看到先通过generateDecor创建了DecorView:


protected DecorView generateDecor(int featureId) {
        Context context;
        ...
        return new DecorView(context, featureId, this, getAttributes());
    }
复制代码


创建时将Window也传入了,所以DecorView中保存了一份Window的引用。

然后回到installDecor代码中,又执行了generateLayout,这里创建了mContentParent:


protected ViewGroup generateLayout(DecorView decor) {
        ...
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        return contentParent;
    }
复制代码


可以看到这个mContentParent就是ID_ANDROID_CONTENT,所以它才是真正装载我们通过setContentView所设置的布局那个ViewGroup。所以这个层级应该是:

DecorView -> mContentParent -> 实际布局


ViewRoot


通过上面可以看出,Window的创建是在attach环节,而DecorView则是在create环节。目前虽然创建了DecorView,但是还没有真正添加到Window中,而且ViewRoot还没有创建出来,这两步实际上是一起的,下面来看一下。

Activity创建完成后会通知AMS,AMS处理一些事务后会通知Activity显示,这样就会执行ActivityThreadhandleResumeActivity()


@Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }
        } else if (!willBeVisible) {
            ...
        }
        ...
    }
复制代码


这里会通过WindowManageraddView函数将DecorView添加到屏幕上。WindowManager的实现类是WindowManagerImpl,它的函数代码如下:


@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
复制代码


实际上是执行了WindowManagerGlobaladdView


public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            ...
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                ...
            }
        }
    }
复制代码


这里我们看到创建了ViewRootImpl,这就是ViewRoot。然后将DecorView也传入了,这样ViewRoot就持有了DecorView。

那么ViewRoot到底是什么?

我们可以把看看成是管理DecorView的一个类,比如它初始化的时候得到了一个WindowSession:


public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
    }
复制代码


通过WindowSession可以与WMS进行通信实现一些窗口信息的传递。

另外在它的setView中还创建了一个WindowInputEventReceiver


public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                ...
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();
                }
                ...
                try {
                    ...
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                            mTempControls);
                    ...
                } catch (RemoteException e) {
                    ...
                } finally {
                    ...
                }
                ...
                if (inputChannel != null) {
                    ...
                    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper());
                    ...
                }
            }
        }
    }                    
复制代码


这个就是用来接收事件的,下面我们来顺着这个看看事件是如何分发到view的。


事件源头


上面创建WindowInputEventReceiver时,可以看到传入了一个InputChannel,它创建之后又传入了WindowSession,即WMS。InputChannel就是底层通知上层事件的核心,系统服务捕获到屏幕事件后,会通过它通知到上层,也就是WindowInputEventReceiver

所以WindowInputEventReceiver是整个事件的源头:


final class WindowInputEventReceiver extends InputEventReceiver {
        ...
        @Override
        public void onInputEvent(InputEvent event) {
            ...
            if (processedEvents != null) {
                if (processedEvents.isEmpty()) {
                    // InputEvent consumed by mInputCompatProcessor
                    finishInputEvent(event, true);
                } else {
                    for (int i = 0; i < processedEvents.size(); i++) {
                        enqueueInputEvent(
                                processedEvents.get(i), this,
                                QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
                    }
                }
            } else {
                enqueueInputEvent(event, this, 0, true);
            }
        }
        ...
    }
复制代码


事件进入它的onInputEvent后会执行enqueueInputEvent:


void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        ...
        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }
复制代码


这里通过判断立即执行还是延迟处理,结果差不多,来看立即执行的代码:


void doProcessInputEvents() {
        // Deliver all pending input events in the queue.
        while (mPendingInputEventHead != null) {
            ...
            deliverInputEvent(q);
        }
        ...
    }
复制代码


进入deliverInputEvent


private void deliverInputEvent(QueuedInputEvent q) {
        ...
        try {
            ...
            InputStage stage;
            if (q.shouldSendToSynthesizer()) {
                stage = mSyntheticInputStage;
            } else {
                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
            }
            ...
            if (stage != null) {
                handleWindowFocusChanged();
                stage.deliver(q);
            } else {
                finishInputEvent(q);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
复制代码


可以看到通过stage进行了deliver,这个stage是什么?

setView的最后有这么一段代码:


// 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;
复制代码


可以看到是一个套一个,我们重点来看ViewPostImeInputStage这个:


final class ViewPostImeInputStage extends InputStage {
        ...
        @Override
        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);
                }
            }
        }
        ...
        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;
        }
        ...
    }
复制代码


InputStage的deliver最终会通过onProcess来区分事件处理(这块就不细说了,没意义),其中我们最关心的事件交给了processPointerEvent来处理。在这里可以看到执行了mView.dispatchPointerEvent(event),这个View就是我们提到的DecorView。这样我们总算找到了源头,下面看看是怎么传递下去的。


事件传递


dispatchPointerEvent这个函数是View的一个函数,源码:


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


到了我们熟悉的dispatchTouchEvent了,这样直接就将事件分发到各个View了?并不是,来看看DecorView中这块是如何处理的:


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


可以看到它通过Window获取了callback,然后执行了callback的dispatchTouchEvent

不知道大家还是否记得我们一开始分析Window的创建的时候提到过(不记得可以回到文章开始看一下),Activity本身实现了Window.Callback接口,并设置给了window的callback。所以这里的callback其实就是Activity,这样事件就传递到了Activity:


public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
复制代码


Activity中之间将事件传递给了Window,调用了它的superDispatchTouchEvent函数,实际上是PhoneWindow的实现:


@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
复制代码


这样就又传递回DecorView了:


public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}
复制代码


在DecorView中执行了super.dispatchTouchEvent(event);,它的父类就是ViewGroup,这样就进入了我们熟悉的ViewGroup分发事件的环节了。


setCallBack


通过上面的分析,我们彻底理解了事件是怎么传递到Activity,然后又如何分发到View上的。

但是这里有一个需要注意的点,就是Window的setCallBack函数是对外的,我们可以设置一个自定义的CallBack,但是这样导致Activity这个CallBack被挤掉,结果就是Activity无法接收到事件。

那么事件还能不能分发下去了呢?我们来看看:


getWindow().setCallback(new Window.Callback() {
    ...
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        return false;
    }
    ...
});
复制代码


这里不论我们返回true还是false,事件都不会分发下去。根据上面分析我们知道,在Activity中是调用了getWindow().superDispatchTouchEvent(event);才让事件继续分发的。所以这里我们也可以加上这样的代码,当然最好是调用Activity的dispatchTouchEvent,保证流程的完整。


总结


经过上面的分析,我们知道事件传递路径大致是

ViewRoot -> DecorView -> Activity(Window.CallBack) -> Window -> DecorView -> ViewGroup -> ...

后面就是我们熟悉的事件分发流程。



目录
相关文章
|
9月前
|
XML Android开发 数据格式
Android 了解View的事件分发详解
Android 了解View的事件分发详解
53 0
|
Android开发
Android Touch事件分发(源码分析)
Android一文让你轻松搞定Touch事件分发 源码分析 Activity事件分发机制 Activity.dispatchTouchEvent()源码 Activity.onTouchEvent()源码 Activity源码总结 ViewGroup事件分发机制 ViewGroup.dispatchTouchEvent()源码 ViewGroup.onInterceptTouchEvent()源码 ViewGroup.onTouchEvent()源码 ViweGroup源码总结 View的事件分发机制 View.dispatchTouchEvent()源码
149 0
Android Touch事件分发(源码分析)
|
Android开发
Android一文让你轻松搞定Touch事件分发(下)
实例 创建实例 创建MyViewGroup继承ViewGroup 创建MyView继承View 创建TouchActivity继承Activity 创建布局文件 MLog.logEvent() 点击页面,看效果 点击Activity(白色区域) 点击ViewGroup(黄色区域) 点击View(蓝色区域) 结果分析 事件分发和处理 Activity处理和分发 Activity处理 运行结果 Activity分发 ViewGroup拦截处理和分发 ViewGroup拦截处理 运行结果 结果分析 运行结果 ViewGroup分发 View处理和分发 View处理 运行结果 结果分析 View分发
96 0
Android一文让你轻松搞定Touch事件分发(下)
|
调度 Android开发
Android一文让你轻松搞定Touch事件分发(上)
前言 名词了解 什么是事件 事件流 什么是事件分发 思路梳理 ViewGroup View 涉及事件分发的方法 方法的简单用途解析 拥有上述方法的类 事件分发流程
317 0
Android一文让你轻松搞定Touch事件分发(上)
|
Android开发
说一说Android事件分发中的requestDisallowInterceptTouchEvent
我们知道在事件分发过程中是存在一个拦截机制的 onInterceptTouchEvent 当它返回true则不向下分发事件,否则向下分发。 但是在这个过程中,还有一个参与者:requestDisallowInterceptTouchEvent,这个函数直接影响事件的拦截。我们今天就来说一说这个这个函数是如何影响事件分发的。
368 0
|
Android开发 容器
史上最好的Android事件分发文章
史上最好的Android事件分发文章
史上最好的Android事件分发文章
|
Android开发
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(二)
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(二)
152 0
|
Android开发 开发者
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(一)
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(一)
204 0
|
Android开发 容器
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(三)
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(三)
121 0
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(三)
|
Android开发 容器
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(二)
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(二)
106 0