欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

AndroidSdk源码分析之--LayoutInflater加载生成View树的过程

程序员文章站 2022-05-29 21:34:36
...

LayoutInflater在Android中叫做布局管理器,很多时候我们都会用到LayoutInflater来加载指定的布局,然而,我对Android的UI加载View部分的源码进行了学习,也发现了,最终我们的布局也是通过 mLayoutInflater.inflate(layoutResID, mContentParent);进行渲染进去的

  • PhoneWindow.java类

AndroidSdk源码分析之--LayoutInflater加载生成View树的过程

没错,就是上面的mLayoutInflater.inflate(layoutResID, mContentParent),至于mLayoutInflater的初始化是在PhoneWindow的构造方法里面实现的,这里就不贴出来了,很简单。

下面我们点进这个方法看看做了些什么事情?

  • LayoutInflater.java

    注意:这个类是一个抽象类,具体的实现,我们一会再说

AndroidSdk源码分析之--LayoutInflater加载生成View树的过程

这里我们需要注意一下最后一个参数attachToRoot:root != null,而我们前面root是有值的mContentParent,所以这个值为true。

接下来,我们继续跟下去
AndroidSdk源码分析之--LayoutInflater加载生成View树的过程
final XmlResourceParser parser = res.getLayout(resource); 通过res.getLayout(resource)来查找参数resource所描述的UI布局文件

  • Resources.java

AndroidSdk源码分析之--LayoutInflater加载生成View树的过程

AndroidSdk源码分析之--LayoutInflater加载生成View树的过程

参数id描述的是一个资源ID,TypeValue value = obtainTempTypedValue();来获得ID所对应的资源值,并且保存在一个类型为TypedValue的变量value中。

后面的就暂时不看了,反正就是解析读取处理XML布局,然后返回一个XmlResourceParser对象

现在我们看第三张截图里面的 return inflate(parser,root,attachToRoot),点进去看看

  • LayoutInflater.java
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("**************************");
                }

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

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    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
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

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

                    // Inflate all children under temp against its context.
                    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) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    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;
        }
    }

上面的代码那么多,我们来结合图来具体说明一下
AndroidSdk源码分析之--LayoutInflater加载生成View树的过程

下面就是看一下rInflate方法

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

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        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)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                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 (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

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

里面有两处关键点,分别是

  • rInflateChildren(parser, view, attrs, true); 顾明思议就是递归的解析出来所有的子View,然后直接调用parent.onFinishInflate(),表明遍历完结,然后就会在栈中一步步的向上调用 parent.onFinishInflate()方法来通知父容器,本View已经解析完成.

  • createViewFromTag(…)负责根据指定的 name 生成具体的 View

  • viewGroup.addView(view, params),该句负责把每次解析出来的 View 都添加进本次的根 ViewGroup 中,这样最终会将其下所有的子 View 都添加进来。

我们就将 LayoutInflate 解析 xml ,从而生成 View树 的过程讲解完了,当然,具体的内容还有很多,接下来我会一步步的去讲解,在本篇中的一些迷惑,你也会一一解开。