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);
    }

看一下效果:
在这里插入图片描述
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());
    }

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;
            }
        }
    }

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() {...}
            ...
}

差不多见名识意吧:

它会在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);
                ...
        }
    }

除了相关变量的赋值操作,在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);
}

这个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();
    }
}

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();
        ...

上面列出了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整体框架

在这里插入图片描述

相关推荐

  1. android中给view添加遮罩层

    2024-04-25 22:56:03       9 阅读
  2. Android StudioView和ViewGroup

    2024-04-25 22:56:03       13 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-25 22:56:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-25 22:56:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-25 22:56:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-25 22:56:03       18 阅读

热门阅读

  1. python高级进阶(三)[面向对象]

    2024-04-25 22:56:03       12 阅读
  2. MyBatis笔记——参数处理

    2024-04-25 22:56:03       13 阅读
  3. 玩转nginx的配置文件2

    2024-04-25 22:56:03       8 阅读
  4. 字符串、数组的反转

    2024-04-25 22:56:03       10 阅读