View创建的那些事儿

文章目录:

本来是想总结view的绘制测量过程,但是一开始就来个measure、layout、draw总感觉让人有点丈二的和尚摸不着头脑。什么时候添加的这个view?谁控制添加的?它为什么能控制添加?这个view现在在哪呢?等等一系列问题都出来了。

理解几个概念

在学习研究View创建过程先明白几个概念,帮助后边理清思路,这些包括Activity、ActivityThread、Window、PhoneWindow、WindowManagerService、WindowManager、ViewRootImpl、surface、DecorView。这些都是与view有关的,大部分都是framework层,这里简单介绍一下各自的作用,为后边view的创建及绘制渲染做准备。

  • Activity:这个都认识,四大组件之一主要用于与用户进行交互处理,每个 Activity 都会获得一个用于绘制其用户界面的窗口,来呈现各种各样的布局。
  • ActivityThread:主线程或UI线程,主要的作用是根据AMS(ActivityManagerService)负责调度和执行activities、broadcasts和其它操作。
  • Window:往虚的说是视图窗口,往实的说是一个抽象类。它提供了一套标准的UI方法,比如添加背景,标题等等。Window 有三种类型,分别是应用 Window、子 Window 和系统 Window。应用类 Window 对应一个 Acitivity,子 Window 不能单独存在,需要依附在特定的父 Window 中,比如常见的一些 Dialog 就是一个子 Window。
  • PhoneWindow:Window的实现类,可以通过实现具体抽象方法去绘制窗口。DecorView作为该类的成员变量,可以在此类中获取DecorView的实例。
  • DecorView:FramLayout的子类,作为窗口中最顶层的view,起到一个基础框架作用。内部包含对view的measure、layout、draw、onAttachedToWindow、以及触摸事件处理等等所有与view绘制相关的功能。
  • ViewRootImpl:作为WindowManager和DecorView之间的纽带,主要作用是协助WindowManager将DecorView添加到Window中,并获取顶层视图的MeasureSpec,然后对ViewGroup和子View进行迭代测量绘制,最终将View展现出来。
  • WindowManager:是一个接口,主要用来管理窗口的一些状态、属性、view增加、删除、更新、窗口顺序、消息收集和处理等,继承自接口ViewManager(内含有三个方法addView、updateViewLayout、removeView),由WindowManagerImpl来进行实现。
  • WindowManagerGlobal:WindowManagerImpl内部成员变量,是一个WindowManager全局工具类,WindowManager中View的增加、删除、更新以及WindowManagerService实例的获取等最终处理都是在此工具类中处理的。
  • WindowManagerService:位于 Framework 层的窗口管理服务,它的职责就是管理系统中的所有窗口的排布和次序。
  • Surface:源码给出的解释Handle onto a raw buffer that is being managed by the screen compositor,Surface中的Canvas成员是专门用于供程序员画图的场所,就像黑板一样;其中的原始缓冲区是用来保存数据的地方;Surface本身的作用类似一个句柄,得到了这个句柄就可以得到其中的Canvas、原始缓冲区以及其它方面的内容,每一个window都对应一个surface,作为窗口的“底板”,窗口上各种绘制排版都是在此底板上展现的。由WindowManagerService进行分配。

屏幕显示过程

上边提到了Surface扮演着在一块窗口上管理绘图数据的角色。WindowManagerService职责就是管理系统中的所有窗口,每添加一个窗口的过程,其实就是 WindowManagerService 为其分配一块 Surface 的过程,一块块的 Surface 在 WindowManagerService 的管理下有序的排列在屏幕上,Android 才得以呈现出多姿多彩的界面。
屏幕显示过程图

UI界面层级结构

Activity作为四大组件之首承载着view的展示和处理的角色。无论是触发桌面Launcher还是startActivity最终的启动过程都是由ActivityThread来负责支配的。界面启动后,Activity作为一个载体承载window,然后各种视图依次展现。
上边提到数据缓存区创建因而也就能得到一个window的显示区域,在window内部中又有各种各样的view来呈现多姿多彩的内容,DecorView便是这颗ViewTree的根节点,DecorView包含两部分,TitleView和ContentView,TitleView受主题的限制有时我们不需要显示他,我们自己定义的主要内容显示在ContentView中。
UI界面架构图:
UI界面架构图
ViewTree结构图:
ViewTree结构图
ContentView中展示的View有两种形式,View和ViewGroup,其实最终都归为View,下边是一副view的拓展图,可以帮助更好的理解各种view的来源。
view拓展图

从setContentView说起

这是一个老生常谈的问题,这里都认识更容起步,setContentView这一部分是将布局文件加载到DecorView中,不涉及DecorView如何填充到Window中(后续再说这一部分)。
当我们自定义Activity继承自android.app.Activity时候,调用的setContentView()方法是Activity类的,源码如下:

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

这里的getWindow()就是PhoneWindow,走的就是PhoneWindow的setContentView()方法。而PhoneWindow是在Activity的attach()方法中进行实例化的:

    final void attach(Context context,...) {
        //绑定上下文
        attachBaseContext(context);   

        mFragments.attachHost(null /*parent*/);

        //创建Window的实现类PhoneWindow
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        //绑定回调监听,一旦window有相关动作回调给Activtiy
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);

        ......

        //设置WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        //通过getWindowManager获取WindowManager实例WindowManagerImpl
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }

01#PhoneWindow#setContentView()

先看一个PhoneWindow的setContentView()源码:

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            //如果父容器空,初始化父容器
            installDecor();     
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //如果父容器不为空,移除所有子View
            mContentParent.removeAllViews();    
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //一般情况下我们不需要过度动画特性,走这一步进行布局资源文件加载
            mLayoutInflater.inflate(layoutResID, mContentParent);    
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            //回调方法通知Activity窗口内容发生了改变
            cb.onContentChanged();    
        }
        mContentParentExplicitlySet = true;
    }

setContentView最重要的两件事:第一初始化父容器;第二加载布局文件。接着看一下installDecor()方法内部怎么实现的:

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //1.实例化DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //2.生成父容器
            mContentParent = generateLayout(mDecor);    
        ......
        }
    }

在初始installdecor()方法中同样做了重要的两件事:第一实例化DecorView;第二生成父容器。实例DecoView很简单,源码就是new出来一个,哈哈。关键第二步生成父容器涉及了针对DecorView主题以及TitleView的标题、标题Icon等设定做好多工作。这里可以粗略的看几个重点:

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        //获取系统Window主题资源
        TypedArray a = getWindowStyle();

        ......
        //根据主题资源设定父窗口样式
        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
        .......

        //设置系统状态栏
        if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
            decor.setSystemUiVisibility(
                    decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }

        ......

        return contentParent;
    }

至此,PhoneWindow中setContentView()的初始化父容器过程完毕,接下来分析布局资源文件加载过程。

02#PhoneWindow#mLayoutInflater.inflate()

PhoneWindow的setContentView()方法中调用了LayoutInflater的inflate()方法来填充布局,这个方法的源码如下:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        //获取Xml解析对象
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            //进一步加载布局资源
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }    

这个方法的主要作用就是为了拿到XmlResourceParser实例对象进行接下来的xml解析,平时我们动态加载自定义view最终也是走的这个方法,接下来看xml解析做的工作:

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                //如果根节点不是起始结点抛出没有起始标识异常
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }
                //获取结点标签名字
                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }


                //优先处理<morge/>标签
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //解析<morge/>标签下的view并将最终添加到root父容器中
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    //根据根节点标签创建父容器View对象
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        //获取父容器的layout params
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            //如果attachToRoot为false,调用view的setLayoutParams方法,便于后边的直接返回。
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    //根据rootView解析所有孩子的布局文件,并将解析的View依次递归合并到temp中
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        //将最终合并完成的子View(即大儿子temp)添加到root父容器中
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    //如果父容器为空或者attachToRoot为false直接将所有子view合并后的大儿子temp返回
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

这部分主要功能就是解析xml,预先针对父容器通过createViewFromTag()方法生成临时的View(temp),然后并将解析后子View依次合并到tempz中,最终添加到父容器中并返回,有些时候是没有父容器的,例如我们自定义view的动态加载,因此只需要把这部分xml解析组合成View后直接返回即可。接下来看一下递归解析孩子的rInflateChildren()方法:

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

rInflateChildren()内部进一步调用的rInflate()方法:

    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;

        //开始进行解析
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            //起始结点标识不是起始标识返回进行下一个结点的解析
            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            //获取结点标签名字
            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                //解析requestFocus标签
                parseRequestFocus(parser, parent);  
            } else if (TAG_TAG.equals(name)) {
                //解析tag标签
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                //解析include标签
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                //处理morge标签,这里直接抛出异常,因为morge标签只能存在最初父级结点上
                throw new InflateException("<merge /> must be the root element");
            } else {
                //如果是其他标签则根据标签生成View,并添加ViewGroup中,这部分进行递归遍历结点之下的结点
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }  

这个过程主要处理子View的xml文件的解析,内部针对四种特殊标签单独处理,然后如果标签下仍然有子view,则继续递归处理。每次根据标签解析出来的view是由createViewFromTag()方法生成的。

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {

        //如果标签是view,拿到view标签上的class值    
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        //如果该标签与主题相关,需要对context进行包装,将主题信息加入context包装类ContextWrapper
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }

        //如果是blink标签返回一个BlinkLayout,它包含的内容会一直闪烁。
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        //设置Factory,来对View做额外的拓展
        try {
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            //如果此时不存在Factory,不管Factory还是Factory2,还是mPrivateFactory都不存在,那么会直接对name直接进行解析
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    //如果name中包含.即为自定义View,否则为原生的View控件
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;

        } catch (Exception e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        }
    }

这部分内容是最终的解析部分,无论是自定义view还是原生的view都能进行处理,细心的你会发现最终生成view的是onCreateView()方法,其内部其实是creatView(),这是LayoutInflater默认生成view的方式,内部原理是类加载器根据前缀+标签名字去加载相应的对象,有兴趣的同学可看一下这部分源码,共同学习。
createViewFromTag整体流程图
到此View创建添加到DecorView这一过程完毕,下部分是DecorView添加至Window过程,然后接下来进行view的测量、布局、绘制,这一整套走完之后才能见到庐山真面目了。

总结

文中的源码是android7.0源码。本文主要描述的是view创建添加到DecorView过程,从熟知的setContentView()内部分析大的方面经历的两部分:

  • PhoneWindow的setContentView()
    • 初始化父容器
    • 实例化DecorView
    • 生成父容器ContentParent(包含主题、title、titleIcon等对父容器的设置)
    • 布局资源文件的加载,直接引申到第二部分
  • PhoneWindow的mLayoutInflater.inflate()。
    • 获取XmlResourceParser实例对象
    • 通过inflate进一步加载解析
    • 处理父容器结点(优先处理morge标签)
    • 递归处理子节点
    • 递归addView(),最终指向父容器的addView()并返回。

猜你喜欢

转载自blog.csdn.net/li0978/article/details/79223686