【Android Framework系列】第11章 LayoutInflater源码分析

1 前言

本章节我们主要目目的是了解Activityxml布局解析、对LayoutInfater源码进行分析。

我们知道Android界面上的每一个控件都是一个个View,但是Android也提供了通过xml文件来进行布局控制,那么xml布局文件如何转成最终的View的呢?转换利器就是LayoutInflater。在分析LayoutInfater源码之前,我们先来简单看一下xml解析都有哪些方式。
本文基于Android10(Q)的源码做分析

2 XML的三种解析方式

2.1 Dom解析

是一种用于XML文档的对象模型,可用于直接访问XML文档的各个部分。它是一次性全部将内容加载在内存中,生成一个树状结构,它没有涉及回调和复杂的状态管理。 缺点是加载大文档时效率低下。

具体解析步骤如下:

1.构建一个DocumentBuilderFactory实例

2.构建DocumentBuilder

3.加载XML文档(Document)

4.遍历XML文档

2.2 Sax解析

使用流式处理的方式,它并不记录所读内容的相关信息。它是一种以事件为驱动的XML API,解析速度快,占用内存少。使用回调函数来实现。

2.3 Pull解析(Android 推荐)

Pull内置于Android系统中。也是官方解析布局文件所使用的方式。PullSAX有点类似,都提供了类似的事件,如开始元素和结束元素。不同的是,SAX的事件驱动是回调相应方法,需要提供回调的方法,而后在SAX内部自动调用相应的方法。而Pull解析器并没有强制要求提供触发的方法。因为他触发的事件不是一个方法,而是一个数字。它使用方便,效率高。目前Android的LayoutInfater解析使用的是该方法。

3 LayoutInfater源码分析

要解析XML并在Activity中显示,你可以使用Android提供的XML解析器来完成。下面是一个简单的示例代码,演示如何解析上述XML并将其显示到Activity中:

下面我们拿AppCompatActivity为例进行分析。

3.1 Activity的onCreate()方法调用setContentView(R.layout.xxx)方法

其实是调用了父类AppCompatActivityAppCompatDelegate的实现类AppCompatDelegateImpl中的setContentView()方法

java 复制代码
    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

3.2 通过LayoutInflater类来解析传入的xml文件

java 复制代码
    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) + ")");
        }

		// 使用反射尝试获取已经提前编译好的View对象
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        // 根据资源ID获取布局的XML资源解析器
        XmlResourceParser parser = res.getLayout(resource);
        try {
        	// 使用XML资源解析器对XML进行解析
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

先直接使用反射尝试获取已经提前编译好的View对象,如果获取到直接返回View对象,如果获取不到再根据资源ID获取布局的XML资源解析器,然使用XML资源解析器对XML进行解析。

3.3 将XML解析并实例化后的View对象进行添加返回操作

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 {
                advanceToRootNode(parser);
                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)) {
                    // 如果布局根标签为merge,要求传入的root不能为空,且attachToRoot需为true
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
					// 继续解析XML
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                	// 根据XML中的根标签生成根View对象
                    // 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);
                        }
                        // 如果布局提供参数,则创建匹配root的布局参数
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                        	// 如果attachToRoot为false,则使用addView的时候再设置布局参数
                            // 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");
                    }

					// 如果root!=null,且attachToRoot==true,则自动将布局添加到root中,并设置布局参数
                    // 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);
                    }

					// 当root==null或者attachToRoot==false,则直接返回View对象
                    // 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(
                        getParserStateDescription(inflaterContext, attrs)
                        + ": " + 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;
        }
    }

3.4 使用XML资源解析器对XML进行解析

想要获取XML解析并实例化的View对象,就必须先进行XML解析得到AttributeSet, 也就是rInflate()rInflateChildren()等方法;

java 复制代码
    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 {
            	// 生成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);
            }
        }

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

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

然后使用反射根据tag对解析后的AttributeSet进行实例化得到View对象,也就是createViewFromTag()createView()等方法。

java 复制代码
  View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
......
        try {
        	// 这里的逻辑:调用mFactory2的onCreateView,如果没有设置mFactory2就尝试mFactory,
        	// 否则调用mPrivateFactory,mFactory2和mFactory后面再说,这里先往后走。
            View view = tryCreateView(parent, name, context, attrs);

            if (view == null) {
            	// 如果还是没加载到view,判断name是否有.
            	// 如果没有就表明是Android原生的View,最终都会调用到createView方法
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        }
......
    }
  1. 先是判断tryCreateView()是否返回View,这个方法具体我们接下来看

  2. 如果还是没有加载到View,先判断name,看名字里是不是有.,如果没有就表明是Android原生的View,最终都会调用到createView方法,onCreateView最终会调用到createView(name, "android.view.", attrs);,会在View名字天面添加"android.view."前缀。

java 复制代码
    public final View tryCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context,
        @NonNull AttributeSet attrs) {
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        View view;
        // 注意:这个地方有两个mFactory
        if (mFactory2 != null) {
        	// 有mFactory2,则调用mFactory2的onCreateView方法 
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
        	// 有mFactory,则调用mFactory的onCreateView方法 
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }

        if (view == null && mPrivateFactory != null) {
        	// 有mPrivateFactory,则调用mPrivateFactory的onCreateView方法 
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        return view;
    }

调用mFactory2onCreateView,如果没有设置mFactory2就尝试mFactory,否则调用mPrivateFactorymFactory2mFactory后面再说,这里先往后走。onCreateView()实际上调用的是createView()方法。

java 复制代码
 public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
......
     try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                }
                // 反射获取这个View的构造器 
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                // 缓存构造器 
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                                mContext.getClassLoader()).asSubclass(View.class);

                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, viewContext, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                }
            }

            Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = viewContext;
            Object[] args = mConstructorArgs;
            args[1] = attrs;
	        try {
	         	// 使用反射创建View的实例对象
	             final View view = constructor.newInstance(args);
	             if (view instanceof ViewStub) {
	             	// 如果是ViewStub类型,当稍后解析ViewStub时使用同个上下文解析
	                 // Use the same context when inflating ViewStub later.
	                 final ViewStub viewStub = (ViewStub) view;
	                 viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
	             }
	             return view;
	         } finally {
	             mConstructorArgs[0] = lastContext;
	         }
......
    }

这个方法可以看到View是怎么创建出来的,用类的全限定名拿到class信息,有一个sConstructorMap缓存类的constructor,如果能拿到有效的构造器就不再重复创建来提升效率,如果没有缓存的构造器,就反射得到构造器并添加到sConstructorMap中以便后面使用。这里有个mFilter来提供自定义选项,用户可以自定义哪些类不允许构造。

拿到构造器之后,实际上newInstance是调用了两View个参数的构造方法。第一个参数是Context,第二个参数是attrs,这样我们就得到了需要加载的View

4 LayoutInflater 的 inflate 方法工作过程总结

  1. LayoutInfalter的作用是把XML转化成对应的View对象,需要用Activity#getLayoutInflater()或者getSystemService获取
  2. 加载时先判断是否是merge标签merge标签走递归方法rinflate,否则走createViewFromTag
  3. createViewFromTag作用是根据xml标签的名字去加载对应的View,使用的是反射的方法
  4. LayoutInflater.Factory2是设计出来灵活构造View的接口,可以用来实现换肤或者替换View的功能,同时也是AppcompatActivity用来做兼容版本替换的接口
相关推荐
Digitally1 小时前
如何将文件从 iPhone 传输到 Android(新指南)
android·ios·iphone
whysqwhw2 小时前
OkHttp深度架构缺陷分析与演进规划
android
用户7093722538512 小时前
Android14 SystemUI NotificationShadeWindowView 加载显示过程
android
木叶丸2 小时前
跨平台方案该如何选择?
android·前端·ios
顾林海3 小时前
Android ClassLoader加载机制详解
android·面试·源码
用户2018792831673 小时前
🎨 童话:Android画布王国的奇妙冒险
android
whysqwhw4 小时前
OkHttp框架的全面深入架构分析
android
你过来啊你4 小时前
Android App冷启动流程详解
android
泓博4 小时前
KMP(Kotlin Multiplatform)改造(Android/iOS)老项目
android·ios·kotlin
移动开发者1号5 小时前
使用Baseline Profile提升Android应用启动速度的终极指南
android·kotlin