• WMS之添加View



    前言

    WMS 功能繁杂,通过添加View流程进一步分析WMS

    通过本文了解掌握以下知识:

    Window是什么?

    • 表示一个窗口的概念,是所有View的直接管理者,任何视图都通过Window呈现(点击事件由Window->DecorView->View;
      Activity的setContentView底层通过Window完成)
    • Window是一个抽象类,具体实现是PhoneWindow。这个可以看Activity#attach方法源码
    • 创建Window需要通过WindowManager创建,WindowManager是外界访问Window的入口,Window具体实现位于WindowManagerService中
      WindowManager和WindowManagerService的交互是通过IPC完成

    Window和View关系?

    • Window和View通过ViewRootImpl建立联系,View是视图的呈现方式,但是不能单独存在,必须依附在Window这个抽象的概念上。
    • WMS把所有的用户消息发给View/ViewGroup,但是在View/ViewGroup处理消息的过程中,有一些操作是公共的, Window把这些公共行为抽象出来, 这就是Window。

    Activity、View、Window三者之间的关系?

    • 在Activity启动过程其中的attach()方法中初始化了PhoneWindow,而PhoneWindow是Window的唯一实现类。
    • 然后Activity通过setContentView将View设置到了PhoneWindow上,而View通过WindowManager的addView()、removeView()、updateViewLayout()对View进行管理。

    一、addview示例

    先看一个简单的案例。在主屏幕上添加一个TextView并展示,并且这个TextView独占一个窗口。

    1.activity布局:
    布局里什么都不绘制
    在这里插入图片描述
    2.addview

    onCreate方法里获取Windowmanager
    动态添加TextView ,设置其背景颜色、字体颜色
    设置Windowmanager的宽高、TYPE

        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_iccpanel);
            TextView mview = new TextView(this);
            mview.setText("add view");
            mview.setBackgroundColor(Color.RED);
            mview.setTextColor(Color.BLUE);
            WindowManager mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
            WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams();
            wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
            wmParams.width = 200;
            wmParams.height = 100;
            mWindowManager.addView(mview, wmParams);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    看一下效果:
    在这里插入图片描述
    WindowManager 通过addview的方式,将TextView显示到了屏幕上。


    二、addview流程

    WindowManager只是一个接口,真正实现类是WindowManagerImpl,并最终以代理模式交给WindowManagerGlobal实现。
    可以通过源码自行查看,也可以阅读我的第一篇WMS文章WMS之窗口属性的WindowManager简介部分。

    在addview之前,activity已经拥有了window实例和父布局View:此块内容查看SetContentView()流程分析
    接下来看addview的流程:

    2.1 流程图

    在这里插入图片描述

    2.2 流程分析

    2.2.1 Actitity的启动流程创建PhoneWindow和DecorView

    WindowManager.addView添加窗口之前,TextView的onDraw不会被调用,也就说View必须被添加到窗口中,才会被绘制。只有申请了依附窗口,View才会有可以绘制的目标内存,而窗口的创建就是在Actitity的启动流程

    在 Activity 的启动流程中,当调用 attach() 方法时,会创建 PhoneWindow 对象,并同时创建一个 WindowManagerImpl 实例来维护 PhoneWindow 内的内容。PhoneWindow 表示应用程序窗口的实体内容,而 WindowManagerImpl 则负责实现窗口的添加、删除、更新等操作。
    在 Activity 的 onCreate() 方法中,通常会调用 setContentView() 方法来设置布局资源,这个方法内部会创建一个 DecorView 实例作为 PhoneWindow 的实体内容,同时将该视图添加到 WindowManagerImpl 管理的窗口列表中。DecorView 是一个特殊的 View,它包含所有 UI 控件,并为其提供一个基础框架,比如标题栏、状态栏、背景等。

    2.2.2.WindowManagerImpl 添加view

    WindowManagerImpl 作为WindowManager的实例,内部最终是以代理模式交给WindowManagerGlobal实现。
    WindowManagerImpl.java

        public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyTokens(params);
            mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                    mContext.getUserId());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    WindowManagerGlobal.java

      public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow, int userId) {
            if (view == null) {
                throw new IllegalArgumentException("view must not be null");
            }
            if (display == null) {
                throw new IllegalArgumentException("display must not be null");
            }
            if (!(params instanceof WindowManager.LayoutParams)) {
                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
            }
    			/**
    			*检查view是否为null,如果是则抛出IllegalArgumentException异常。
    			 检查display是否为null,如果是则抛出IllegalArgumentException异常。
    			 检查params是否为WindowManager.LayoutParams类型,如果不是则抛出IllegalArgumentException异常。
    			*/
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
            //将params强制转换为WindowManager.LayoutParams类型,并赋值给wparams变量。
            if (parentWindow != null) {
                parentWindow.adjustLayoutParamsForSubWindow(wparams);
     // 如果parentWindow不为null,则调用parentWindow的adjustLayoutParamsForSubWindow()方法来调整wparams的参数。
            } else {
            	//如果没有parentWindow,则根据应用程序的硬件加速设置来设置View的硬件加速标志。
                // If there's no parent, then hardware acceleration for this view is
                // set from the application's hardware acceleration setting.
                final Context context = view.getContext();
                if (context != null
                        && (context.getApplicationInfo().flags
                                & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                    wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
                }
            }
    		
            ViewRootImpl root;
            View panelParentView = null;
    
            synchronized (mLock) {
            /**
            *在同步块内部进行以下操作:
    			创建一个Runnable对象mSystemPropertyUpdater,用于监视系统属性的变化。
    			查找是否已经存在相同的view,如果存在则根据情况执行相应的操作。
    			对于panel window类型的窗口,查找其所依附的父窗口。
            */
                // Start watching for system property changes.
                if (mSystemPropertyUpdater == null) {
                    mSystemPropertyUpdater = new Runnable() {
                        @Override public void run() {
                            synchronized (mLock) {
                                for (int i = mRoots.size() - 1; i >= 0; --i) {
                                    mRoots.get(i).loadSystemProperties();
                                }
                            }
                        }
                    };
                    SystemProperties.addChangeCallback(mSystemPropertyUpdater);
                }
    
                int index = findViewLocked(view, false);
                if (index >= 0) {
                    if (mDyingViews.contains(view)) {
                        // Don't wait for MSG_DIE to make it's way through root's queue.
                        mRoots.get(index).doDie();
                    } else {
                        throw new IllegalStateException("View " + view
                                + " has already been added to the window manager.");
                    }
                    // The previous removeView() had not completed executing. Now it has.
                }
    
                // If this is a panel window, then find the window it is being
                // attached to for future reference.
                if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                        wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                    final int count = mViews.size();
                    for (int i = 0; i < count; i++) {
                        if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                            panelParentView = mViews.get(i);
                        }
                    }
                }
    
                IWindowSession windowlessSession = null;
                // If there is a parent set, but we can't find it, it may be coming
                // from a SurfaceControlViewHost hierarchy.
                if (wparams.token != null && panelParentView == null) {
                    for (int i = 0; i < mWindowlessRoots.size(); i++) {
                        ViewRootImpl maybeParent = mWindowlessRoots.get(i);
                        if (maybeParent.getWindowToken() == wparams.token) {
                            windowlessSession = maybeParent.getWindowSession();
                            break;
                        }
                    }
                }
    			//根据条件创建ViewRootImpl对象root,如果有父窗口,则使用windowlessSession创建ViewRootImpl对象。
                if (windowlessSession == null) {
                    root = new ViewRootImpl(view.getContext(), display);
                } else {
                    root = new ViewRootImpl(view.getContext(), display,
                            windowlessSession, new WindowlessWindowLayout());
                }
    			//设置View的LayoutParams为wparams。
                view.setLayoutParams(wparams);
    			//将view、root、wparams添加到相应的列表中(mViews、mRoots、mParams)。
                mViews.add(view);
                mRoots.add(root);
                mParams.add(wparams);
    
                // do this last because it fires off messages to start doing things
                //将view设置到root中,并处理可能出现的RuntimeException异常。
                try {
                    root.setView(view, wparams, panelParentView, userId);
                } catch (RuntimeException e) {
                    final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
                    // BadTokenException or InvalidDisplayException, clean up.
                    if (viewIndex >= 0) {
                        removeViewLocked(viewIndex, true);
                    }
                    throw e;
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121

    WindowManagerGlobal中维护的和Window操作相关的3个列表,在窗口的添加、更新和删除过程中都会涉及这3个列表,它们分别是View 列表(ArrayList<View> mViews)、布局参数列表(ArrayList<WindowManager.LayoutParams>mParams)和ViewRootImpl列表(ArrayList<ViewRootImpl>mRoots

    WindowManagerImpl 的addView()主要做了以下事情:

    对传入的参数进行合法性检查,包括检查View、Display和LayoutParams是否为空,以及对LayoutParams进行类型转换和调整。
    创建ViewRootImpl对象,用于管理View的显示和交互。
    将View的LayoutParams设置为传入的LayoutParams。
    将View、ViewRootImpl对象和LayoutParams添加到相应的列表中。
    尝试将View设置到ViewRootImpl对象中,并处理可能出现的RuntimeException异常。

    中间的IWindowSession 暂且忽略。

    最后就到了ViewRootImpl 的setView方法

    2.2.3 ViewRootImpl.setView

    在上面的addView方法中创建了一个 ViewRootImpl 实例,将 ViewRootImpl 与 View 树进行关联,这样 ViewRootImpl 就可以指挥View树的具体工作,包括测量、布局和绘制等操作。ViewRootImpl 是一个核心组件,它作为控制器,负责处理系统消息队列、与 WindowManagerService 通信、触发 View 树的渲染等任务。

    大致看一下ViewRootImpl类的重要内容

    public final class ViewRootImpl implements ViewParent,
            View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
            AttachedSurfaceControl {
                ...
                //持有主线程
    	        final Thread mThread;
                //    View
                View mView;
                final View.AttachInfo mAttachInfo;
                ...
                //执行setView方法去想Window添加View
                public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
                int userId) {...}
                void doTraversal() {...}
                void scheduleTraversals() {...}
                ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    差不多见名识意吧:

    它会在WindowManager调用addView添加rView时进行初始化,能和系统的WindowManagerService交互,管理View 的绘图和窗口状态;会持有View;
    会进行绘制主线程检查,异步线程抛异常;
    会进行我们所熟知视图绘制三大流程,performMeasure、performLayout、performDraw
    在View中会被mParent变量所持有,在View.AttachInfo中被mViewRootImpl变量所持有,我们平时调用的getViewRootImpl()方法,就是获取的mViewRootImpl变量的引用。
    调用View的invalidate()方法也会到ViewRootImpl里面,另外,除了绘制流程,它还承担了事件分发。
    负责和WMS进行进程间通信。

    继续看ViewRootImpl 的setView()

    //ViewRootImpl构造方法
    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
                boolean useSfChoreographer) {
    	...
       	//持有主线程,即创建ViewRootImpl的线程
        mThread = Thread.currentThread();
        //初始化AttachInfo,内部传递持有了ViewRootImpl,后续的代码里,它将被传递给View,所以可以通过View获取到mAttachInfo,进而获取到ViewRootImpl。
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
    }
    
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
                int userId) {
            synchronized (this) {
                if (mView == null) {
                    //持有View
                    mView = view;
                    ...
                    //AttachInfo持有View
                    mAttachInfo.mRootView = view;
                    //调用requestLayout
                    requestLayout();
                    ...
                        //通过WindowSession来完成window最终的添加,该过程mWindowSession类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,所以这其实是一次IPC的过程,远程的调用了Session的addToDisPlay方法。
                        res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(), userId,
                                mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                                mTempControls);
    				...
                   	//将自己传递给View的mParent变量,如此调用View相关的invalidate方法,就直接调用到了ViewRootImpl
                    view.assignParent(this);
                    ...
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    除了相关变量的赋值操作,在setView中有两个很值得注意的地方。

    • 调用了requestLayout方法,其内部会调用到scheduleTraversals,进而调用了measurelayoutdraw
    • 通过IPC,将window添加到WMS(WindowManagerService)进行管理。

    针对将window添加到WMS这一块,看一下Session的代码:

    Session
    public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
        InputChannel outInputChannel, InsetsState outInsetsState,
        InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
        requestedVisibility, outInputChannel, outInsetsState, outActiveControls);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这个mService就是WMS,也就是说ViewRootImpl通过Session可以和WMS进行跨进程调用,而我们看到Session也被WMS持有,每个应用程序进程都会对应一个Session,WMS会用ArrayList来保存这些Session。剩下的工作就交给WMS来处理,在WMS中会为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WMS 会将它所管理的Surface 交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。

    View的绘制流程则是通过调用requestLayout来继续完成的

    ViewRootImpl
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //判断当前是否是主线程即mThread,如果不是,则抛异常。就是在子线程更新UI没使用handler的话就会抛出的异常
            checkThread();
            //设置mLayoutRequested为true。
            mLayoutRequested = true;
            //进而测量、布局、绘制
            scheduleTraversals();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    requestLayout的方法中,首先进行调用者的线程检查,如果不是主线程,即不和mThread指向的不是同一个线程对象,就抛出异常。随后设置mLayoutRequested为true,这个变量是做什么呢?它主要用来区分是否需要进行测量和布局。最后调用mLayoutRequested方法。

    这里说明一下,requestLayout方法和invalidae方法均是通过最后均调用了scheduleTraversals方法,那么是如何区分是不是需要测量和布局呢?就是上面提到的mLayoutRequested变量了,如果是requestLayout则赋值为true,就会调用测量和布局;如果是invalidae,则默认就为false。

    接下来看一下scheduleTraversals及其相关方法的调用流程。

        ViewRootImpl
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    	mTraversalRunnable
        final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                doTraversal();
            }
        }
    	ViewRootImpl
        void doTraversal() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
                //调用了performTraversals
                performTraversals();
            }
        }
    	ViewRootImpl
        private void performTraversals() {
            final View host = mView;
            ...
            //将mAttachInfo传入View,同时会调用View的onAttachedToWindow方法
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            ...
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            ...
            performLayout(lp, mWidth, mHeight);
            ...
            performDraw();
            ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    上面列出了scheduleTraversals整个方法的调用链,来梳理一下:

    • 给Choreographer注册了一个mTraversalRunnable。Choreographer用于接收显示系统的VSync信号,在下一个帧渲染时控制执行一些操作。Choreographer的postCallback方法用于发起添加回调,这个添加的回调将在下一帧被渲染时执行。所以我们知道mTraversalRunnable里面的内容将在下一帧渲染时被执行。
    • 看一下mTraversalRunnable的类型,内部调用了doTraversal。而在doTraversal内部调用了performTraversals。
      在performTraversals中,调用View 的dispatchAttachedToWindow方法,给View绑定了mAttachInfo,进而可以调用到View的onAttachedToWindow方法。之后就是熟悉的测量、布局、绘制调用流程。

    ViewRootImpl的创建以及视图绘制流程就清楚了,和之前的结合进行简单的总结一下:

    • 在handleLaunchActivity中进行Activity的实例化,之后初始化WindowManager,Context等,Window持有WindowManager,给Activity绑定Window对象。然后调用到onCreate的setContentView,创建DecorView被Window持有。同时我们自己的布局包含在了DecorView中;之后调用handleStartActivity方法,里面会调用onStart以及onRestoreInstanceState;
    • 之后真正的显示过程在调用handleResumeActivity回调调用onResume之后,此时会将DecorView通过WindowManager的addView方法和WindowManager进行绑定,利用ViewRootImpl进行和WMS交互,加入window布局到到WMS,然后进行View的测量、布局、绘制,最后调用Activity的makeVisible进行显示。

    三、总结

    addView:

    1.创建ViewRootImpl;
    2.将ViewRoot、DecorView、布局参数保存到WM的内部列表中;
    3.ViewRoot.setView()建立ViewRoot和View的联系。
    setView:
    1.进行View绘制三大流程:performMeasure、performLayout、performDraw
    2.会通过WindowSession完成Window的添加过程(一次IPC调用)
    requestLayout:内部调用scheduleTraversals(), 底层通过mChoreographer去监听下一帧的刷新信号。
    mWindowSession.addToDisplay: 执行WindowManagerService的addWindow
    addWindow: 检查参数等设置;检查Token;将Token、Window保存到WMS中;将WindowState保存到Session中。(WindowState WindowSession等知识)

    附带一张WMS整体框架

    在这里插入图片描述

  • 相关阅读:
    (九)集合 - List
    13年实践经验总结,200多页PPT的企业级推荐系统原理与实践,助力企业精细化与个性化运营...
    自己编译JDK
    .NET混合开发解决方案3 WebView2的进程模型
    【Jetson 设备】window10主机下使用VNC可视化控制Jetson Orin NX
    Graph Self-Supervised Learning: A Survey
    SQL查询中的小技巧:SELECT 1 和 LIMIT 1 替代 count(*)
    MySQL小记——DDL、DML、DQL
    nginx转发https:SSL_do_handshake() failed
    CSS:弹性布局(display:flex)
  • 原文地址:https://blog.csdn.net/qq_45649553/article/details/138188690