android inflate源码分析

我们知道,每个界面的样式都可以使用xml文件来设置。但是为什么可以这样,android系统又是怎么将xml文件转化为布局的呢?

首先,使用inflate是这样来用的:

LayoutInflater.from(this).inflate(R.layout.activity_main,null);

这句话做了什么呢?我们先看from(context)方法:

/**
     * Obtains the LayoutInflater from the given context.
     */
    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;
    }

可以看到,这个方法是获取了系统的叫做LAYOUT_INFLATER_SERVICE的服务。

那么inflate方法做了什么呢?

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

他会调一个重载方法,并且第三个参数是判断第二个参数不为空。也就是root不为空

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        final XmlResourceParser parser = res.getLayout(resource);   //拿到xml解析器
        try {
            return inflate(parser, root, attachToRoot); //parse xml解析器, root 根布局  attachRoot 根布局不为空
        } finally {
            parser.close();
        }
    }
这个方法会通过资源文件拿到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 {
                //找到根节点
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                   //如果执行到这的根节点不是Start标签,就抛异常
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();   //获取节点的名称
                if (TAG_MERGE.equals(name)) {         //处理merge 标签
                    //代码省略
                } else {
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);  //将节点名解析成对应的view
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {       
                        params = root.generateLayoutParams(attrs);  //将属性解析成layoutParams
                        if (!attachToRoot) {    
                            temp.setLayoutParams(params);              //将参赛设置给新创建出来的view
                        }    
                    }
                    rInflateChildren(parser, temp, attrs, true);       //解析子布局,第二个参数是新建好的view,要注意下。
                    
                    if (root != null && attachToRoot) {                
                        root.addView(temp, params);                    //将新的view添加到根布局中
                    }

                    if (root == null || !attachToRoot) {    //root 为空  那么 attachToRoot一定是false
                        result = temp;
                    }
                }

            } catch (rException e) {   //异常处理,省略了很多异常
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } 
            return result;
        }
    }

      具体的流程注释已经比较清楚的体现了。就是,先找到根节点,如果没有找到就抛异常。然后就是调用createViewFromTag 创建对应的view出来,然后在解析子view。这里注意下,如果我们调用inflate的时候root传的null,那么最后会将构建好的布局传回去。如果root不为空,那么就走的是root.addView(temp),会将新建好的布局添加到根布局中去。

        接下来看看createViewFromTag 这个方法做了些什么:


    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {     //处理新建的view就是View的情况
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        if (!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();
        }

        if (name.equals(TAG_1995)) {    //这里有故事啊,这个tag是blink,特殊处理
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        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);     //解析系统控件,其实最后也是调用的createView
                    } else {
                        view = createView(name, null, attrs);        //解析自定义控件,第二个参数为null
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
            return view;
        } catch (InflateException e) {  //异常处理省略了很多
            throw e;

        } 
    }

这里其实就做了这么几件事:

是否要解析主题参数。

是否按照我们自定义的方式去解析布局,也就是说支持自己鞋解析方式。

判断是否是系统的控件,或者是自定义控件,然后分别解析成view。

那么,系统的控件有事怎么解析的呢?

   protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }

其实就是将 android.view.前缀加上去调用解析自定义控件的方式解析,也就是控件的包全名,然后调用createView去解析。在上面解析自定义控件的时候第二个参数传的是 null。接下来看看createView做了什么:

 public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        //省略部分代码
        Class<? extends View> clazz = null;

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

           
                // 这里会判断第二个参数不为空时和name拼起来去加载对应的类,
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
          //省略部分代码
            Object lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                // Fill in the context if not already within inflation.
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);   //通过反射去新建view
            if (view instanceof ViewStub) {                //特殊处理ViewStub
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;

        }catch (Exception e) {   //异常处理省略了很多
            final InflateException ie = new InflateException(
                    attrs.getPositionDescription() + ": Error inflating class "
                            + (clazz == null ? "<unknown>" : clazz.getName()), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

        这个地方就是view最终生成的地方,是通过反射来做的。反射的类名如果prefix不为空,会将其和name拼起来,在解析系统布局的时候用到的。还有一个方法,创建子view的是啥样的呢?

  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)) {   //处理requestFocus
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } 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)) {    //处理merge,因为merge只能在根节点中创建,所以这里直接抛异常了
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);   //调用上面的方法创建view
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);                        //继续解析子view
                viewGroup.addView(view, params);                                    //将新建的view加到父布局中
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

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

从这里可以看出,解析子view的时候是按深度优先的方式去遍历布局树的(递归调用自己)。

       从整体上看出,inflate的过程就是从根布局开始,按深度优先的规则去解析xml文件里面的view节点,新建view,在将view添加到其父布局中,最终构成了一个view树。





猜你喜欢

转载自blog.csdn.net/u012674854/article/details/80383893