【Android开发】事件是如何分发到Activity的?

【Android开发】事件是如何分发到Activity的?_第1张图片

目录

  • 前言
  • Window
  • DecorView
  • ViewRoot
  • 事件源头
  • 事件传递
  • setCallBack
  • 总结

前言

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

Android事件分发机制大家都非常熟悉(不熟悉的可以参考我的另外一篇博客《细说Android的事件分发机制》),大部分文章对这个过程的描述都是开始于Activity,但是事件是怎么传到Activity的?

这里就涉及到几个重要的部分:

  • Window
  • WMS
  • ViewRoot
  • DecorView

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

Window

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

我们来看Activity的attach函数:

@UnsupportedAppUsage
final void attach(...) {
    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 -> …

后面就是我们熟悉的事件分发流程。可以参考我的另外一篇博客《细说Android的事件分发机制》

你可能感兴趣的:(android开发,Android进阶之路,android,事件分发)