自学内容网 自学内容网

setContentView调用流程(二) -将布局添加到mContentParent

Android setContentView执行流程(一)-生成DecorView
Android setContentView执行流程(二)-将布局添加到mContentParent

上篇博客我们介绍了setContentView的第一步即生成DecorView以及获取到mContentParent的流程,同时还提到继承自Activity和AppCompatActivity生成DeocrView的流程有点区别,但是将我们自定义的xml布局添加到mContentParent的流程是一样的,因此这个流程我们就以继承自Activity为例进行阐述,另外我们开发中常见的如下问题也跟setContentView流程的源码相关,我们也会通过源码深入讲解

  • merge、include、ViewStub是如何工作的,尤其是如果通过id查找布局的根View
  • View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, true)中inflate方法的参数有什么用呢?

在开始本篇内容学习之前一定要先把上一篇文章看完。

一、 整体时序图

同样先看下setContentView整体的时序图,及到哪些类从整体上了解下。
在这里插入图片描述

二、 源码分析

上一篇提到Acitivty的setContentView方法会调用getWindow.setContentView,而getWindow得到的拿到的就是PhoneWindow因此我们就要从PhoneWindow的setContentView源码开始看起,PhoneWindow#setContentView

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            // 初始化DecorView 并获取到mContentParent
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
。。。
        } else {
            // 将我们自定义Activity的布局添加到mContentParent
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        。。。
        // setContentView执行完的标识
        mContentParentExplicitlySet = true;
    }

在上一节我们讲了installDecor相关内容,今天我们要讲的就是第14行mLayoutInflater.inflate(layoutResID, mContentParent)是如何把layoutResID添加到mContentParent的。对于LayoutInflater平时开发中一般有三种方式来获取它

        // 第一种
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        // 第二种:
        LayoutInflater inflater= LayoutInflater.from(context);

        // 第三种:
        // 在Activity内部调用getLayoutInflater()方法
        getLayoutInflater();

这三种方式本质上都是通过第一种方式来获得的,我们就精简下源码,看最终获得的是个什么东西,Context是抽象类,它的实现类是ContextImpl因此我们看下
ContextImpl#getSystemService的源码

    @Override
    public Object getSystemService(String name) {
    。。。
        return SystemServiceRegistry.getSystemService(this, name);
    }

可以看到调用了SystemServiceRegistry.getSystemService方法,我们来看下它的相关源码

@SystemApi
public final class SystemServiceRegistry {
    private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES = new ArrayMap<Class<?>, String>();
    private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new ArrayMap<String, ServiceFetcher<?>>();
    static {
    // 。。。省略很多注册服务的代码
        // 在静态代码块中注册LAYOUT_INFLATER_SERVICE服务
            registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                // 返回一个PoneLayoutInflater对象
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
    }

    private static <T> void registerService(@NonNull String serviceName,
            @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
        // 对LayoutInflater来讲,这里的key:LayoutInflater.class,value:Context.LAYOUT_INFLATER_SERVICE
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        // key:Context.LAYOUT_INFLATER_SERVICE,value:CachedServiceFetcher对象
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
        SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
    }

    static abstract interface ServiceFetcher<T> {
        T getService(ContextImpl ctx);
    }

    static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
       
        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
        。。。。
    // 调用createService也就是上面第11行实现的createService方法
            service = createService(ctx);
            。。。
            ret = service;
            return ret;
        }

        public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
    }
    
    public static Object getSystemService(ContextImpl ctx, String name) {
       。。。
        // 通过name获取CachedServiceFetcher对象
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
       。。。
        // 调用CachedServiceFetcher的getService方法,获取对应的对象
        final Object ret = fetcher.getService(ctx);
        。。。
        return ret;
    }
}

来理一下它的设计思路:首先在静态代码块中初始化了Context.LAYOUT_INFLATER_SERVICE服务,在源码中有大量的服务在此进行初始化,初始化的时候会调用registerService方法,传递了三个参数第一个参数Context.LAYOUT_INFLATER_SERVICE它的值为"layout_inflater",第二个参数是LayoutInflater.class,第三个参数是CachedServiceFetcher对象,在第22行它会将key:“layout_inflater”,value:CachedServiceFetcher放入SYSTEM_SERVICE_FETCHERS这个map中,所以在调用SystemServiceRegistry的getSystemService方法时,第49行会拿到我们在静态代码块中初始化的CachedServiceFetcher对象,然后调用它的getService方法,而它的getService在第37行会调用createService方法,这个方法就是我们静态代码块第11行定义的方法,可以看到它会返回一个PhoneLayoutInflater对象,这里需要强调下得到的是PhoneLayoutInflater对象。

回到我们今天的主题:LayoutInflater.from(mContext).inflate(resId, contentParent)是如何将resId添加到contentParent中的呢?

首先来看下LayoutInflater.from的源码LayoutInflater#from

    public static LayoutInflater from(@UiContext Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

可以看到它其实是通过context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);这个方法得到的LayoutInflater对象,刚才我们提到它其实是一个PhoneLayoutInflater对象。LayoutInflater是一个抽象类,PhoneLayoutInflater继承自LayoutInflater这个抽象类并重写了LayoutInflater中两个参数的onCreateView(String name, AttributeSet attrs)方法,这一点需要注意下后面会用到它,接着刚才的流程from之后会调用LayoutInflater的inflate方法,LayoutInflater#inflate

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
    
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
       。。。
        XmlResourceParser parser = res.getLayout(resource);
       
        return inflate(parser, root, attachToRoot);
    }

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            View result = root;
            。。。
            if (TAG_MERGE.equals(name)) {
                // merge 标签必须要有根布局,否则会抛如下异常
                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 {
                // 获取xml文件中的根View
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                。。。
                // 解析子布局
                rInflateChildren(parser, temp, attrs, true);
                。。。
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }
            。。。
            return result;
        }
    }

第26行调用了createViewFromTag方法,获取到根View,LayoutInflater#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 (-1 == name.indexOf('.')) { // name=LinearLayout
            view = onCreateView(context, parent, name, attrs);
        } else {
            // 比如name=androidx.constraintlayout.widget.ConstraintLayout
            view = createView(context, name, null, attrs);
        }
        。。。
        return view; 
    }

第7行有个if语句这个语句主要用来判断标签的名字是否有“.”,如果没有的话就调用LayoutInflater#onCreateView如果有"."就调用LayoutInflater#createView,我们来看下它们的源码LayoutInflater#onCreateView,这个方法有多个重载的方法,大家在看源码的时候要注意下

    public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
            @NonNull String name, @Nullable AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(parent, name, attrs);
    }

    protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }

这里需要注意了,第9行调用了两个参数的onCreateView方法,刚才我们提到LayoutInflater.from(mContext)得到的是PhoneLayoutInflater并且它重写了onCreateView两个参数的方法,因此第9行调用的是PhoneLayoutInflater里的onCreateView,我们来看下它的源码

public class PhoneLayoutInflater extends LayoutInflater {
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };

    @Override 
    protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                // prefix是view的前缀android.widget.
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }

        return super.onCreateView(name, attrs);
    }
}

第10行的for循环,这个for循环会遍历sClassPrefixList,sClassPrefixList这个数组里放的是View的前缀,第12行又会调用LayoutInflater#createView

    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Context context = (Context) mConstructorArgs[0];
        if (context == null) {
            context = mContext;
        }
        return createView(context, name, prefix, attrs);
    }

    public final View createView(@NonNull Context viewContext, @NonNull String name,
        @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
        Class<? extends View> clazz = null;
        。。。
            // perfix + name是类全称,比如:android.widget.LinearLayout,prefix:android.widget,name:LinearLayout
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);
            。。。
            constructor = clazz.getConstructor(mConstructorSignature);
            。。。
            final View view = constructor.newInstance(args);
            return view;
        }


到这里createViewFromTag就执行完了,可以看到第16行通过反射得到了View的Class对象,然后第21行调用newInstance得到了对应的View,比如LinearLayout

再回到LayoutInflater的inflate方法,代码如下,现在第5行的createViewFromTag执行完了,接下来就该填充它的子View了

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            。。。
                // 根据XML 解析根View对象
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                。。。
                // 解析子布局
                rInflateChildren(parser, temp, attrs, true);
            
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }
            return result;
        }
    }

我们来看下是如何解析子布局的LayoutInflater#rInflateChildren

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
        。。。
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
            。。。
            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);
                // 解析内部子View
                rInflateChildren(parser, view, attrs, true);
                // 将解析之后的View添加到父布局中
                viewGroup.addView(view, params);
            }
        }
。。。
    }

第26行,它也是调用createViewFromTag来生成对应的View,并且在第30行会循环调用解析它的子View,并将得到的View添加到parent中,这样最终的View就生成了。总结一下:

(1) 调用LayoutInflater.from(mContext)本质上是通过context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)得到PhoneLayoutInflater对象,它继承自LayoutInflater这个抽象类并重写了LayoutInflater中两个参数的onCreateView(String name, AttributeSet attrs)方法

(2) 调用createViewFromTag最终调用createView通过反射得到根View,比如LinearLayout

(3) 循环调用rInflateChildren最终也是调用createView通过反射得到相应的View,比如LinearLayout,TextView,Button等并将其添加到父View,这样就形成了一个View树。

这里第18~24行可以看到对merge、include进行了特殊处理,在平时的开发中merge、inlcude以及ViewStub对我们布局的优化也非常重要,因此我们就来看下它们的用法

三、merge include ViewStub详解

merge include的讲解根据上述LayoutInflater#rInflateChildren的源码进行分析

merge

merge比较简单它主要用来优化布局,使用它可以减少一层布局嵌套,可以看到第24行抛了一个异常" must be the root element"也就是说merge标签必须是根View,不能作为子View

include

第19行,如果parser.getDepth() == 0说明是根布局,则会抛异常" cannot be the root element"也就是说include不能做为根布局
include大家应该都使用过,对于它的使用需要注意的是它的id,如果对include设置了id,并且对include布局的根View也设置了id那么它的id是找不到的,比如下的布局嵌套activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include
        android:id="@+id/include_id"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        layout="@layout/include_layout" />

</LinearLayout>

include_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/include_layout_root_id"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

可以看到在我们的activity_main中有个include并且设置了id为include_id,在我们的include_layout.xml中根布局的id为include_layout_root_id。假如我用如下代码来查找此id是否能查找到呢?

        LinearLayout ll = findViewById(R.id.include_layout_root_id);
        TextView tv = ll.findViewById(R.id.tv_title);
        tv.setText("change text");

运行后发现报错

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.widget.LinearLayout.findViewById(int)' on a null object reference

也就是说根据include_layout_root_id查找不到对应的View。我们换一种查找方法

        LinearLayout ll = findViewById(R.id.include_id);
        TextView tv = findViewById(R.id.tv_title);
        tv.setText("change text");

上述代码直接查找include_id发现可以正常运行,这是为什么呢?看下上方第22行的pareinclude的源码就清楚了,LayoutInflater#pareinclude

    private void parseInclude(XmlPullParser parser, Context context, View parent,
            AttributeSet attrs) throws XmlPullParserException, IOException {
          
            final View view = createViewFromTag(parent, childName,context, childAttrs, hasThemeOverride);
// 获取include_id
            final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
            。。。
            if (id != View.NO_ID) {
                // 如果include的id不是View.NO_ID则将view的id设置为include的id
                view.setId(id);
            }
    }

第6行会获取include的id,然后在第8行有个if判断,如果include的id不为空则对view的id重新赋值为include的id。因此如果include设置了id,则include的那个xml的根布局的id就失效了,也就是说在上面的例子中activity_main中include设置了id,include_layout.xml文件中的include_layout_root_id就失效了,如果去掉activity_main中include的id,则include_layout_root_id可正常使用即

        LinearLayout ll = findViewById(R.id.include_layout_root_id);
        TextView tv = ll.findViewById(R.id.tv_title);
        tv.setText("change text");

这种方式是可以正常运行,另外需要说明的是此动作只针对include_layout中的根View的id,对于其子View则可正常查找,比如

        TextView tv = findViewById(R.id.tv_title);
        tv.setText("change text");

可以直接查找include_layout布局中的tv_title。

ViewStub

对于merge和include大家比较熟悉,对ViewStub大家可能比较陌生,首先ViewStub是一个View它继承自View,它的作用就是懒加载布局,本质上来说它是一个宽高都为0的一个View,且默认是不可见的,只有在需要的时候,调用setVisibility或者inflate方法才会将目标布局给加载出来,从而达到延迟加载的效果。这样就能优化页面的渲染速度。可能有的同学会有疑问,这种效果通过设置一个View的GONE和VISIBLE不也能实现吗?没错可以实现,但是即使你把View设置为GONE在inflate的时候View仍然会被inflate,也就是说还是会创建对象、实例化等在一定程度上会耗费内存资源。而ViewStub不会,它只有在调用inflate的时候才会被加载进来,那么问题来了,ViewStub是怎么做到的呢?来看下它的源码

public final class ViewStub extends View {
    private int mInflatedId;
    private int mLayoutResource;
    。。。
    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context);

        final TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ViewStub, defStyleAttr, defStyleRes);
        
// 通过自定义属性inflatedId来获取加载的视图跟节点ID,默认返回NO_ID
        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
        // 需要加载的视图资源ID
        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
        mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
        a.recycle();

        setVisibility(GONE);
        setWillNotDraw(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 将宽高设置为0
        setMeasuredDimension(0, 0);
    }
}

可以看到第17行直接调用了setVisibility(GONE)将布局直接隐藏,并且在测量的时候大小也设置为了0,所以它默认是不可见的,那它是怎么做到inflate的时候才把目标布局加载出来的呢?看下它inflate的源码ViewStub#inflate

    public View inflate() {
        final ViewParent viewParent = getParent();
        。。。
        if (mLayoutResource != 0) {
            final ViewGroup parent = (ViewGroup) viewParent;
            // 把ViewStub_layout渲染成view对象
            final View view = inflateViewNoAdd(parent);
            replaceSelfWithView(view, parent);

            mInflatedViewRef = new WeakReference<>(view);
            if (mInflateListener != null) {
                mInflateListener.onInflate(this, view);
            }

            return view;
        }
    }

    private View inflateViewNoAdd(ViewGroup parent) {
        final LayoutInflater factory;
        if (mInflater != null) {
            factory = mInflater;
        } else {
            factory = LayoutInflater.from(mContext);
        }
        // 把ViewStub_layout渲染成view对象
        final View view = factory.inflate(mLayoutResource, parent, false);
// 跟include类似,如果mInflatedId不为空则把view的id设置成mInflatedId
        if (mInflatedId != NO_ID) {
            view.setId(mInflatedId);
        }
        return view;
    }
        
    private void replaceSelfWithView(View view, ViewGroup parent) {
        final int index = parent.indexOfChild(this);
        parent.removeViewInLayout(this);
// 如果viewstub布局设置了参数则使用否则直接将View添加到parent中
        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }
    }

可以看到在使用inflate的时候第7行会把自定义的属性ViewStub_layout即android:layout中的布局渲染成View,接着就会把此View添加到父View中。

四、LayoutInflater.inflate的参数

在开发中我们经常会写如下代码

View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, true);
// 对应的方法的参数如下
inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

这几个参数到底什么意思呢?首先来看几个实例,然后分别分析下,分析完后你就会对这几个参数有深刻理解,这个实例就是我们往布局中LinearLayout中填充一个inflate布局

// 案例一
LinearLayout root = findViewById(R.id.root);
LayoutInflater.from(context).inflate(R.layout.inflate, root, true);

// 案例二
LinearLayout root = findViewById(R.id.root);
View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, true);
root.addView(view)
    
// 案例三
LinearLayout root = findViewById(R.id.root);
View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, false);
root.addView(view)
    
// 案例四
View view = inflater.inflate(R.layout.inflate_layout, null, false);
root.addView(view);

要想充分理解这几个案例还是得从源码的角度来分析LayoutInflater#inflate,下面就结合案例和源码来分析下

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        。。。
        XmlResourceParser parser = res.getLayout(resource);
        return inflate(parser, root, attachToRoot);
    }

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
 
            // 实例化根节点的View
            final View temp = createViewFromTag(root, name, inflaterContext, attrs);

            ViewGroup.LayoutParams params = null;

            if (root != null) {
                // 获取根节点的attr
                params = root.generateLayoutParams(attrs);
                if (!attachToRoot) {
                    // 设置根节点的params
                    temp.setLayoutParams(params);
                }
            }
            。。。
            // We are supposed to attach all the views we found (int temp)
            // to root. Do that now.
            if (root != null && attachToRoot) {
                // 将根节点添加到root上,使用的布局参数是layout中定义的。
                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;
            }  
            return result;
        }
    }

首先案例一

LinearLayout root = findViewById(R.id.root);
LayoutInflater.from(context).inflate(R.layout.inflate, root, true);

root != null, attachToRoot=true,首先第11行会解析我们的xml文件得到对应的View,然后会进入到第15行的分支,获取root的参数,因为attachToRoot=true所以第19行的分支进不去,接着第26行的if语句满足所以将刚解析xml文件得到的View添加到root中,并将其返回,这个没有问题。

案例二

LinearLayout lrootl = findViewById(R.id.root);
View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, true);
root.addView(view);

案例二根案例相比,其实就是最后有一个ll.addView(view)的操作,通过案例一我们知道,这三个参数会进入到第26行的if语句将inflate布局添加到root中,如果再次执行root.addView就会报错,因为一个View只能有一个父亲,会报如下错误

The specified child already has a parent. You must call removeView() on the child's parent first.

案例三

LinearLayout root = findViewById(R.id.root);
View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, false);
root.addView(view);

这个可以正常运行,案例三跟案例二相比就是第三个参数案例二为true,案例三为false,为什么为false就可以了呢?同样看源码第26行的if语句不满足,而第32行的if语句满足因此直接将创建好的View直接返回,因此执行root.addView(view)不会报错,可正常执行。

案例四

View view = inflater.inflate(R.layout.inflate_layout, null, false);
root.addView(view);

案例四,能显示但是显示界面有异常,就是inflate_layout 根View设置的宽高无效,会以其子View的宽高为准,同样也是看源码来分析,我们看下它走了哪个流程

root==null,attachToRoot=false,满足第33行的if条件将布局直接返回,此时它没有父View所以它自己设置的宽高是无效的,这一点后面我讲UI的刷新机制的时候会详细讲。到这里对inflate的几个参数大家应该有所了解了吧。

五、总结

到这里关于setContentView的执行流程以及与其相关的知识点就介绍完了,整体的流程不算复杂,我们在阅读源码的时候切记不要每一行代码都搞明白,只了解主流程即可,否则代码会看的头晕目眩,容易放弃,另外关于对源码的学习一定要自己去阅读源码,做笔记这样才能了解更加透彻且复习也快。

如果大家有疑问或者发现错误,欢迎在下方留言,我会在第一时间回答!!
如果觉得文章对你有帮助就帮忙点个赞吧。


原文地址:https://blog.csdn.net/dmk877/article/details/143654161

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!