Android 深入了解 Window 、Activity、 View 三者关系(下)

简介: addView 成功有一个标志就是能够接收触屏事件,通过对 setContentView 流程的分析,可以看出添加 View 的操作实质上是 PhoneWindow 在全盘操作,背后负责人是 WMS,反之 Activity 自始至终没什么参与感。但是我们也知道当触屏事件发生之后,Touch 事件首先是被传入到 Activity,然后才被下发到布局中的 ViewGroup 或者 View(Touch事件分发 了解一下)。那么 Touch 事件是如何传递到 Activity 上的呢?

又回到Activity


       addView 成功有一个标志就是能够接收触屏事件,通过对 setContentView 流程的分析,可以看出添加 View 的操作实质上是 PhoneWindow 在全盘操作,背后负责人是 WMS,反之 Activity 自始至终没什么参与感。但是我们也知道当触屏事件发生之后,Touch 事件首先是被传入到 Activity,然后才被下发到布局中的 ViewGroup 或者 View(Touch事件分发 了解一下)。那么 Touch 事件是如何传递到 Activity 上的呢?


       ViewRootImpl 中的 setView 方法中,除了调用 IWindowSession 执行跨进程添加 View 之外,还有一项重要的操作就是设置输入事件的处理:


ViewRootImpl.setView


    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
          ..
          res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                      getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                      mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                      mAttachInfo.mDisplayCutout, inputChannel,
                      mTempInsets, mTempControls);
          // 注释:设置输入管道。
          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);
        ...
    }


注释:设置了一系列的输入通道。一个触屏事件的发生是由屏幕发起,然后经过驱动层一系列的优化计算通过 Socket 跨进程通知 Android Framework 层(实际上就是 WMS),最终屏幕的触摸事件会被发送到代码中的输入管道中。


这些输入管道实际上是一个链表结构,当某一个屏幕触摸事件到达其中的 ViewPostImeInputState 时,会经过 onProcess 来处理,如下所示:


ViewRootImpl.ViewPostImeInputStage


 final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }
        @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;
            ...
            boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent = false;
            ...
            return handled ? FINISH_HANDLED : FORWARD;
        }
  }


processPointerEvent 在 ViewPostImeInputStage 中别找错了。可以看到在 onProcess 中最终调用了一个 mView的dispatchPointerEvent 方法,mView 实际上就是 PhoneWindow 中的 DecorView,而 dispatchPointerEvent 是被 View.java 实现的,如下所示:


View.dispatchPointerEvent


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


DecorView.dispatchTouchEvent


@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 中 cb.dispatchTouchEvent(ev) 方法,那这个 Callback 是不是 Activity 呢?


Activity.attach


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) {
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        //this:Activity
        mWindow.setCallback(this);
        ...
    }


    Activity 将自身传递给了 PhoneWindow,再接着看 Activity的dispatchTouchEvent。


Activity.dispatchTouchEvent


public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //该方法是用户交互,每当向Activity分派按键、触摸或轨迹球事件时调用。
            //在这里仅用于ACTION_DOWN的判断
            onUserInteraction();
        }
        //返回true
        if (getWindow().superDispatchTouchEvent(ev)) {
            //Activity.dispatchTouchEvent()就返回true,则方法结束。
            //该点击事件停止往下传递&事件传递过程结束
            return true;
        }
        return onTouchEvent(ev);
    }


PhoneWindow.superDispatchTouchEvent


1.@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }


DecorView.superDispatchTouchEvent


public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }


Touch 事件在 Activity 中只是绕了一圈最后还是回到了 PhoneWindow 中的 DecorView 来处理。 剩下的就是从 DecorView 开始将事件层层传递给内部的子 View 中了


ViewGroup. dispatchTouchEvent


   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    }


  到这里就不做多的重复了。感兴趣的可以看看 事件分发机制


小结


       通过 setContentView 的流程,分析了 Activity、Window、View 之间的关系。整个过程 Activity 表面上参与度比较低,Activity持有Window的对象,View在Window上的增删等操作又是通过WindowManager来管理的,而WindowManager又是通过Binder机制获取到的WMS的映射,WMS把View真正显示到屏幕上。


三者关系:


  1. 一个 Activity 中有一个 window,也就是 PhoneWindow 对象,在Activity中调用attach,创建了一个PhoneWindow。


  1. 在 PhoneWindow 中有一个 DecorView,Activity在 调用setContentView 中会将 layout 填充到此 DecorView 中。


  1. 一个应用进程中只有一个 WindowManagerGlobal 对象,因为在 ViewRootImpl 中它是 static 静态类型。


  1. 每一个 PhoneWindow 对应一个 ViewRootImple 对象。


  1. WindowMangerGlobal 通过调用 ViewRootImpl 的 setView 方法,完成 window 的添加过程。


  1. 调用ViewGroup的removeAllView(),先将所有的view移除掉


  1. ViewRootImpl 的 setView 方法中主要完成两件事情:View 渲染(requestLayout)以及接收触屏事件。  


往期回顾


OkHttp使用和源码详解


RecyclerView 绘制流程及Recycler缓存


Glide 缓存机制及源码(二)


Glide 的简单使用(一)

相关文章
|
12天前
|
Android开发 开发者
Android UI设计中,Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等,定义在`styles.xml`。
【6月更文挑战第26天】Android UI设计中,Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等,定义在`styles.xml`。要更改主题,首先在该文件中创建新主题,如`MyAppTheme`,覆盖所需属性。然后,在`AndroidManifest.xml`中应用主题至应用或特定Activity。运行时切换主题可通过重新设置并重启Activity实现,或使用`setTheme`和`recreate()`方法。这允许开发者定制界面并与品牌指南匹配,或提供多主题选项。
20 6
|
13天前
|
Android开发 开发者
Android UI中的Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等。要更改主题
【6月更文挑战第25天】Android UI中的Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等。要更改主题,首先在`styles.xml`中定义新主题,如`MyAppTheme`,然后在`AndroidManifest.xml`中设置`android:theme`。可应用于全局或特定Activity。运行时切换主题需重置Activity,如通过`setTheme()`和`recreate()`方法。这允许开发者定制界面以匹配品牌或用户偏好。
15 2
|
9天前
|
Android开发
Android自定义View之正方形
【6月更文挑战第23天】
|
10天前
|
Android开发 UED 开发者
Android Activity启动模式详解
Android Activity启动模式详解
13 0
|
10天前
|
Android开发 UED
Android Activity的生命周期详解
Android Activity的生命周期详解
10 0
|
10天前
|
Android开发
Android Activity跳转详解
Android Activity跳转详解
14 0
|
16天前
|
开发工具 Android开发
Android 代码自定义drawble文件实现View圆角背景
Android 代码自定义drawble文件实现View圆角背景
19 0
|
16天前
|
Android开发
Android 自定义View 测量控件宽高、自定义viewgroup测量
Android 自定义View 测量控件宽高、自定义viewgroup测量
18 0
|
16天前
|
开发工具 Android开发 git
Android自定义View——可以设置最大宽高的FrameLayout
Android自定义View——可以设置最大宽高的FrameLayout
25 0
|
16天前
|
JSON Android开发 数据格式
Android动态添加view设置view大小(宽高)
Android动态添加view设置view大小(宽高)
11 0