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

Android 深入理解LayoutInflater工作机制

程序员文章站 2022-07-14 18:24:54
...

Android里面有很多场景会用到LayoutInflate这个类,我们通过这个类去解析指定的布局,然后展示在布局里面。api的调用是如此的简单,我们如果每次都是单纯的调用,那就无法得到提升了,所以现在我们来看一下这个流程究竟是怎么一回事。
先来看下用法跟场景:

@Override
    public AllPavilionViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //在recyclerview/listview的适配器中 构建每个item的布局
        return new AllPavilionViewHolder(inflater.inflate(R.layout.item_location_pavilion, parent, false));
    }
 public AmountView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //自定义UI中 解析指定布局去依附在自定义view中
        LayoutInflater.from(context).inflate(R.layout.ui_amount, this);
    }
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //观察源码 其实这个setContentView 内部也是调用Layoutflate.inflate的方式来构建view的
        setContentView(R.layout.activity_reserve);
    }

可能还有一些其他场景会用到这个方法,这些就不一一举例了,我们直接来通过源码分析这块的流程吧。


   /**
     * Obtains the LayoutInflater from the given context.
     * //首先是初始化 可以看出我们常用的LayoutInflater其实是对如下方法的简单封装,意味着我们其实有两种方式来构建出LayoutInflater的实例
     */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;//为什么变量名字大写开头 我也不知道
    }

//拿到我们想要的实例之后,之后就是调用inflate方法去构建view了。

  //四种inflate方法 到最后还是殊途同归
 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        //里面调用的还是下面那个方法 只是做了一点判断而已
        return inflate(resource, root, root != null);
    }
  //说一下参数 
  //resource代表需要解析的xml文件
  //root 表示这个xml文件外围包裹的父布局,如果不需要 直接传null便可 
  //attachToRoot 是否需要布局文件依附在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) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);//得到一个xml解析器
        try {
            return inflate(parser, root, attachToRoot);//这个方法才是真正的开始解析布局文件
        } finally {
            parser.close();//解析器用完 关掉
        }
    }
  //重点来了 
 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!");
                }
                //开始构建 root view
                final String name = parser.getName();
                if (TAG_MERGE.equals(name)) {
                  //根布局为merge版
                    if (root == null || !attachToRoot) {
                    //判断根布局是不是merge 如果是的话 需要有父布局并且attachToRoot为true 否则抛出异常
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);//这里开始循环解析
                } else {
                       //根布局为非merge版
                    // 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) {//如果root 不为null 然后attachToRoot 为false
                    //只有在这种情况下 他的属性才会被真正设置进去 否则无效 root!=null&&attachToRoot==false (会影响宽高跟margin  ,关于padding 毕竟是作用在view的onDraw方法里面的,还是有效果的)
                        // 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);
                        }
                    }

                    //开始解析布局文件中 子view 循环解析所有子view
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);
                    // 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) {//判断是返回刚构建的view 还是之前传进来的root
                        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;
        }
    }

我们先看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) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        if (!ignoreThemeAttr) {//默认ignoreThemeAttr 给的是false
            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();//TypedArray用完都是要回收的
        }

        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);//一种闪烁的布局 
          //想要看效果 可以看http://blog.csdn.net/qq_22644219/article/details/69367150
        }

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

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);//主体还是这个方法 通过反射的方式创建了view
                    }
                } 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创建好了之后,之后会调用 rInflate(parser, root, inflaterContext, attrs, false)一步步的实现每一个子view

 /**
     * Recursive method used to descend down the xml hierarchy and instantiate
     * views, instantiate their children, and then call onFinishInflate().
     * 递归的调用方法构建整个xml的布局,沿层次一个个的实例化view
     */
    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) {//循环拿到那个start_tag
            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();
            //对一些特殊的节点做处理
            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
            } 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);//这里做include内容的解析
            } else if (TAG_MERGE.equals(name)) {//意思是 merge 不能作为一个子view 除非他是根布局
                throw new InflateException("<merge /> must be the root element");
            } else {
              //这里做真正的创建view 还是通过反射的形式 另外把一些属性设置进去 再添加到父view中
                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);
            }
        }
        //在这里 所有的view 都被构建成功 
        if (finishInflate) {//当根布局不是merge 那就为true
            parent.onFinishInflate();//就是一个回调吧
        }
    }

整个流程就是这样子 ,我们再来看一下,我在最初的时候说 setContentView也是通过LayoutInflate方法的:

 @Override
    public void setContentView(@LayoutRes int layoutResID) {//我们在activity里面的调用 就是这个方法
        getDelegate().setContentView(layoutResID);
    }

//这是一个抽象的方法
    public abstract void setContentView(@LayoutRes int resId);

//然后我们看AppCompatDelegateImplV9的实现
 @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);//最后 还是调用到这个方法了
        mOriginalWindowCallback.onContentChanged();
    }

总结一下LayoutInflate里面的实现,其实也是很简单的用了android提供的pull解析一步步的解析下来的,里面的每一个节点就构建成一个view了(通过反射),从根布局开始一层层的解析构建,最终形成一个完整的DOM结构,然后把根布局的引用传出去,这样inflate方法就成功完成了。

最后说一下三个inflate的三个参数作用,毕竟有时候会疑惑该如何传参:

  1. 如果root为null,attachToRoot将失去作用,设置任何值都没有意义。
  2. 如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root,此时是root设置的那些宽高跟margin ,是没有效果的。
  3. 如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。
  4. 在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。

还有一点,请不要在listview\recyclerview里面构建每个item的时候设置root!=null 并且attachToRoot又给了true ,这样会直接报错的,因为

@Override
  public void addView(View child) {
        throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
  }

参考:

Android LayoutInflater原理分析,带你一步步深入了解View(一)
Android LayoutInflate深度解析 给你带来全新的认识