setContentView源码学习笔记

本文详细探讨了Android开发中setContentView的调用流程和源码分析,涉及Activity与AppCompatActivity的区别,以及相关面试题解答。从attach过程到DecorView的生成,解析了PhoneWindow与AppCompatDelegate在设置内容视图时的不同处理,并强调requestWindowFeature需在setContentView前调用的原因。同时,文中还提及LayoutInflater参数、include标签、merge标签和ViewStub的特性和使用注意事项。
摘要由CSDN通过智能技术生成

注:-->及缩进表示方法调用层级关系


一、setContentView调用流程

ActivityThread.performLaunchActivity
-->activity.attach
    --> new PhoneWindow()
-->mInstrumentation.callActivityOnCreate//内部调用onCreate方法

MainActivity.onCreate()
-->setContentView


二、setContentView源码分析

1. 继承 Activity 的流程

setContentView
-->getWindow().setContentView(layoutResID);//getWindow()得到的是new PhoneWindow()

PhoneWindow.setContentView --- 主要目的  创建 DecorView 拿到 Content

--> installDecor(); // 创建 DecorView 拿到 mContentParent
    --> mDecor = generateDecor(-1);
        --> new DecorView()
    --> mContentParent = generateLayout(mDecor);
        --> R.layout.screen_simple --> @android:id/content --> mContentParent
        //  R.layout.screen_simple --》 添加到 DecorView(FrameLayout)
        --> mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);  
        --> ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

--> mLayoutInflater.inflate(layoutResID, mContentParent); //  R.layout.activity_main 渲染到 mContentParent

 2. 继承 AppCompatActivity 的流程

setContentView
-->getDelegate().setContentView(view);//getDelegate()得到的是new AppCompatDelegate()

AppCompatDelegate.setContentView 
--> ensureSubDecor();
    --> mSubDecor = createSubDecor();
        --> ensureWindow(); // 从Activity 那PhoneWindow
        --> mWindow.getDecorView();
            --> installDecor(); // mContentParent
                -->... ...//此处与继承Activity的流程一致,省略
        // R.layout.screen_simple 里面的 content  ---》 把 content 的 View 复制到R.id.action_bar_activity_content
        --> final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        --> windowContentView.setId(View.NO_ID); // 将原始的 content id 置为 NO_ID
        --> contentView.setId(android.R.id.content); // subDecerView R.id.action_bar_activity_content --> 置为 content
        --> mWindow.setContentView(subDecor); // 
--> ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);

// R.layout.activity_main View 创建
--> LayoutInflater.from(mContext).inflate(resId, contentParent);
    // 通过反射创建View  --- 布局的rootView
    --> final View temp = createViewFromTag(root, name, inflaterContext, attrs);
            // 是否是sdk
        -->if (-1 == name.indexOf('.')) {  // LinearLayout 
                view = onCreateView(context, parent, name, attrs);
                    --> PhoneLayoutInflater.onCreateView(name, attrs);
                        --> View view = createView(name, prefix, attrs);

            } else { // name = androidx.constraintlayout.widget.ConstraintLayout
                view = createView(context, name, null, attrs);
                        // 通过反射创建 View 对象
                    --> clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);
                    --> constructor = clazz.getConstructor(mConstructorSignature);
                    --> final View view = constructor.newInstance(args);
            }
    --> rInflateChildren(parser, temp, attrs, true); // 创建子View
        --> rInflate
            --> View view = createViewFromTag(parent, name, context, attrs);

private static final String[] sClassPrefixList = {
    "android.widget.",
    "android.webkit.",
    "android.app.",
    "android.view."
};

三、相关面试题


1. 为什么requestWindowFeature()要在setContentView()之前调用?

         requestWindowFeature 实际调用的是 PhoneWindow.requestFeature,在这个方法里面会判断如果变量 mContentParentExplicitlySet 为true则报错,而这个变量会在 `PhoneWindow.setContentView` 调用的时候设置为true。

2. 为什么这么设计呢?

        DecorView的xml布局是通过设置的窗口特征进行选择的。

3. 为什么 requestWindowFeature(Window.FEATURE_NO_TITLE);设置无效?

        需要用 supportRequestWindowFeature(Window.FEATURE_NO_TITLE);,因为继承的是AppCompatActivity,这个类里面会覆盖设置。


4. LayoutInflater几个参数的作用?

/**
 * Inflate a new view hierarchy from the specified xml resource. Throws
 * {@link InflateException} if there is an error.
 *
 * @param resource ID for an XML layout resource to load (e.g.,
 *        <code>R.layout.main_page</code>)
 * @param root Optional view to be the parent of the generated hierarchy (if
 *        <em>attachToRoot</em> is true), or else simply an object that
 *        provides a set of LayoutParams values for root of the returned
 *        hierarchy (if <em>attachToRoot</em> is false.)
 * @param attachToRoot Whether the inflated hierarchy should be attached to
 *        the root parameter? If false, root is only used to create the
 *        correct subclass of LayoutParams for the root view in the XML.
 * @return The root View of the inflated hierarchy. If root was supplied and
 *         attachToRoot is true, this is root; otherwise it is the root of
 *         the inflated XML file.
 */

LayoutInflater inflater = LayoutInflater.from(this);
// 方式一:布局添加成功,里面执行了 ll.addView(view)
View view = inflater.inflate(R.layout.inflate_layout, ll, true);

// 方式二:报错,一个View只能有一个父亲(The specified child already has a parent.)
View view = inflater.inflate(R.layout.inflate_layout, ll, true);
ll.addView(view);

// 方式三:布局成功,第三个参数为false
// 目的:想要 inflate_layout 的根节点的属性(宽高)有效,又不想让其处于某一个容器中
View view = inflater.inflate(R.layout.inflate_layout, ll, false);
ll.addView(view);

// 方式四:root = null,这个时候不管第三个参数是什么,显示效果一样
// View 没有父容器,尺寸大小参数的设置无效,此处涉及到View的绘制流程
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View,
// 但是子View(Button)有效,因为Button是处于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);


5. 描述下include标签的特点


1) 不能作为根元素,需要放在 ViewGroup中
2) findViewById查找不到目标控件,这个问题出现的前提是在使用include时设置了id,而在findViewById时却用了被include进来的布局的根元素id。
   1. 为什么会报空指针呢?
   如果使用include标签时设置了id,这个id就会覆盖 layout根view中设置的id,从而找不到这个id
   代码:LayoutInflate.parseInclude 
               -->final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
               -->if (id != View.NO_ID) {
                    view.setId(id);
                } 

6. 描述下merge标签的特点


1) merge标签必须使用在根布局
2) 因为merge标签并不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,
    且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点.
3) 由于merge不是View所以对merge标签设置的所有属性都是无效的.

7. 描述下ViewStub标签的特点

ViewStub:就是一个宽高都为0的一个View,它默认是不可见的
1) 类似include,但是一个不可见的View类,用于在运行时按需懒加载资源,只有在代码中调用了viewStub.inflate()
    或者viewStub.setVisible(View.visible)方法时才内容才变得可见。
2) 这里需要注意的一点是,当ViewStub被inflate到parent时,ViewStub就被remove掉了,即当前view hierarchy中不再存在ViewStub,
    而是使用对应的layout视图代替。附ViewStub.java源码说明

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    // 获取 InflatedId
    mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
    // 设置不显示
    setVisibility(GONE);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 设置宽高为0
    setMeasuredDimension(0, 0);
}

public View inflate() {
    final ViewParent viewParent = getParent();

    if (viewParent != null && viewParent instanceof ViewGroup) {
        if (mLayoutResource != 0) {
            final ViewGroup parent = (ViewGroup) viewParent;
            // 设置ID
            final View view = inflateViewNoAdd(parent);
            // 替换自己和View
            replaceSelfWithView(view, parent);

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

            return view;
        } else {
            throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
        }
    } else {
        throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
    }
}

private View inflateViewNoAdd(ViewGroup parent) {
    if (mInflatedId != NO_ID) {
        view.setId(mInflatedId);
    }
}

private void replaceSelfWithView(View view, ViewGroup parent) {
    final int index = parent.indexOfChild(this);
    // 移除自己
    parent.removeViewInLayout(this);

    final ViewGroup.LayoutParams layoutParams = getLayoutParams();
    // 添加View
    if (layoutParams != null) {
        parent.addView(view, index, layoutParams);
    } else {
        parent.addView(view, index);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值