文章

LayoutInflater 源码解析

LayoutInflater 源码解析

LayoutInflater 源码解析

LayoutInfleter 获取

getSystemService()

1
LayoutInflater  inflater = (LayoutInflater) MainActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE)

LayoutInflater.from(Context)

1
LayoutInflater inflater = LayoutInflater.from(context)

inflate layout 的 2 种方式

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

参数详解

  • 1、 如果 root 为 null,attachToRoot 将失去作用,设置任何值都没有意义。
  • 2、 如果 root 不为 null,attachToRoot 设为 true,则会给加载的布局文件的指定一个父布局,即 root,并 generateLayoutParams 在 addView 的时候设置进去。
  • 3、 如果 root 不为 null,attachToRoot 设为 false,不会给布局指定一个父布局,会将布局文件最外层的所有 layout 属性进行设置,当该 view 被添加到父 view 当中时,这些 layout 属性会自动生效 setLayoutParams()。
  • 4、 使用两个参数的 LayoutInflater.inflate(int resource,ViewGroup root) 或者 View.inflate(resource,root),在不设置 attachToRoot 参数的情况下,如果 root 不为 null,attachToRoot 参数默认为 true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    // ...
    View result = root;
    final String name = parser.getName();
    if (TAG_MERGE.equals(name)) {
        // ...
        rInflate(parser, root, inflaterContext, attrs, false);
    } else {
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
        ViewGroup.LayoutParams params = null;
        if (root != null) { 
            // 如果root不为null,那么会生成LayoutParams
            params = root.generateLayoutParams(attrs);
            if (!attachToRoot) {
                // 如果attachToRoot为false,那么会为createViewFromTag()生成的根View设置setLayoutParams(params)
                temp.setLayoutParams(params);
            }
        }
        // ...
        rInflateChildren(parser, temp, attrs, true);
        // ...
        // 如果root不为null且attachToRoot为true,那么将生成的View添加到root
        if (root != null && attachToRoot) {
            root.addView(temp, params);
        }
        // 如果root为null或者attachToRoot为false,那么返回的View为生成的View,否则为root
        if (root == null || !attachToRoot) {
            result = temp;
        }
    }
    // ...
    return result;
}

public static View inflate(Context context, @LayoutRes int resource, ViewGroup root)

1
2
3
4
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}

小结

merge 相关

  1. merge 必须作为布局的根标签,如果非 root 标签抛出异常 InflateException<merge /> must be the root element
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) {
    //...
    int type;
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
        //...
        final String name = parser.getName();
        if (TAG_REQUEST_FOCUS.equals(name)) {
        //...
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        }
        //...
    }
}

疑问:为啥 TAG_MERGE.equals(name) 就一定不为 root 标签呢?
这是因为如果 merge 为根标签,在 inflate(parser, root, attachToRoot)

1
2
3
4
5
6
7
8
9
10
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    advanceToRootNode(parser); // 这里调用了一次parser.next()
    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); // 这里又调用了一次parser.next()
    }
}
  1. 如果 inflate(resource,root,attachToRoot) 中的 root 为 null 或者 attachToRoot 为 false,那么 resource 布局根标签不能为标签,抛出异常 InflateException<merge /> can be used only with a valid ViewGroup root and attachToRoot=true
1
2
3
4
5
6
7
8
9
10
// LayoutInflater
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    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);
    } 
}

include 相关

  1. include 不能作为根标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) {
    //...
    int type;
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
        //...
        final String name = parser.getName();
        if (TAG_REQUEST_FOCUS.equals(name)) {
        //...
        } else if ((TAG_INCLUDE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        }
        //...
    }
}
  1. include 不能用在 View 里面,只能用于 ViewGroup 中,否则报错 "<include /> can only be used inside of a ViewGroup"
  2. include 必须指定 layout 属性,而且 layout 指定的 layout 必须有效 "You must specify a layout in the include tag: <include layout=@layout/layoutID/>"
  3. 如果 include 所在的 ViewGroup 可以 generateLayoutParams() 那么就用这个,如果不能,会默认设置一个 ViewGroup.LayoutParams

inflate() 原理

inflate

这个函数就是我们把 Xml 布局文件实例化为一个 View 对象的入口了;tryInflatePrecompiled() 是编译期通过 xml 生成 view,目前 (2020 年 10 月 26 日) 还不可靠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// LayoutInflater
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 = tryInflatePrecompiled(resource, res, root, attachToRoot); //这段代码其实是必然返回null的,因为当前版本写死了预编译的Enable为false
    if (view != null) {
        return view;
    }
    XmlResourceParser parser = res.getLayout(resource); //获取XmlBlock.Parser对象
    try {
        return inflate(parser, root, attachToRoot); 
    } finally {
        parser.close(); 
    }
}

此处又调用了 inflate(parser, root, attachToRoot) 这个函数,来对 Xml 布局进行解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {//XmlPullParser是一个接口
	//此函数是真正执行将xml解析为视图view的过程,此处的parser为根据xml布局获取到的parser对象
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final Context inflaterContext = mContext;
        final inflateAttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root; //需要返回的view对象

        try {
            advanceToRootNode(parser); //对START_TAG和END_TAG进行判断和处理
            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标签
                if (root == null || !attachToRoot) { //使用merge标签必须有父布局,且依赖于父布局加载,否则就会抛出异常
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }

                rInflate(parser, root, inflaterContext, attrs, false);//递归(Recursive)生成布局视图
            } else { //如果不使用merge标签,创建tmp 作为临时的根节点,并最终赋值给result返回
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, inflaterContext, attrs); ////根据标签名创建一个view

                ViewGroup.LayoutParams params = null;

                if (root != null) {  //如果rootView不为空
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);  //根据rootView生成layoutparams
                    if (!attachToRoot) { //如果attachToRoot为false
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);  //设置一个临时的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不为空,且attachToRoot为true
                    root.addView(temp, params); //把temp添加到到rootview中
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) { // 如果root为空且attachToRoot为false
                    result = temp; //将temp,也就是根结点的View赋值给result
                }
            }

        } 
		// ...
        return result;  //返回结果
	}
}

rInflate

不管是 merge 标签,还是非 merge 标签,最终都会调用到 rInflate() 这个函数,这个是用于递归向下遍历 xml 布局,最终调用 createViewFromTag() 函数来反射创建 View 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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)) { //如果需REQUEST_FOCUS标签
            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); //对 include标签进行解析
        } else if (TAG_MERGE.equals(name)) { //如果是merge标签
            throw new InflateException("<merge /> must be the root element"); //直接抛出异常
        } else { //其他标签
            final View view = createViewFromTag(parent, name, context, attrs); //根据Tag创建view,反射创建View
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true); //递归调用rInflate函数
            viewGroup.addView(view, params);
        }
    }

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

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

createViewFromTag

真正根据解析到的 Tag 标签去反射创建 View 的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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) {
        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();
    }

    try {
        View view = tryCreateView(parent, name, context, attrs); //尝试使用Factory来闯将View对象

        if (view == null) { //如果tryCreateView返回null
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
				//sample:com.aiwinn.base.widget.CameraSurfaceView
                if (-1 == name.indexOf('.')) {  //如果当前Tag含有“.”
                    view = onCreateView(context, parent, name, attrs);
                } else {
                    view = createView(context, name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } 
  	// ...
}

tryCreateView() 通过 Factory/Factory2/mPrivateFactory 来创建 View

在这个函数中会首先调用 tryCreateView() 来获取 View 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
    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);
    }
    return view;
}

如果标签为 blink 返回 BlinkLayout;根据设置的 Factory2 和 Factory 来创建对象

createView() 没有自定义 Factory,默认创建,反射

如果 tryCreateView() 返回为 null,则进一步调用 createView() 函数来创建 View 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public final View createView(@NonNull Context viewContext, @NonNull String name,
        @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
			//根据Tag反射创建view
    Objects.requireNonNull(viewContext);
    Objects.requireNonNull(name);
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;

    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);//把prefix和name进行拼接,获取到对应的Class对象

            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);//获取构构造器对象
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        } else {
            // ...
        }

        Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = viewContext;
        Object[] args = mConstructorArgs;
        args[1] = attrs;

        try {
            final View view = constructor.newInstance(args); //根据获取到的构造器创建一个View的实例化对象
            if (view instanceof 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;
        }catch{
          	// ...
        }
    } 
}

彩蛋 BlinkLayout

BlinkLayout 是 LayoutInflater 中的一个内部类,它本身是是 FrameLayout 的子类,如果当前标签为 TAG_1995blink,则创建一个隔 0.5 秒闪烁一次的 BlinkLayout 来承载它的布局内容

源码注释也很有意思,写了 Let’s party like it’s 1995!, 据说是为了庆祝 1995 年的复活节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<blink
    android:layout_below="@id/iv_qr_code"
    android:layout_centerHorizontal="true"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Android研习社"
        android:textColor="#157686"
        android:textSize="55sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</blink>

这种效果也适合来做 EditText 中光标的闪烁效果

Ref

本文由作者按照 CC BY 4.0 进行授权