setContentView

Activity 和 AppCompatActivity 的区别

Activity的实例化

//ActivityThread.java
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //在这里进行activity的实例化,但对于对于初始化ViewTree却不是在这里
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
                    appContext.getAttributionSource());
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        }
      ......
      ......
         appContext.getResources().addLoaders(
         app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

         appContext.setOuterContext(activity);
         //在这里对ViewTree 进行了初始化
         activity.attach(appContext, this, getInstrumentation(), r.token,
                 r.ident, app, r.intent, r.activityInfo, title, r.parent,
                 r.embeddedID, r.lastNonConfigurationInstances, config,
                 r.referrer, r.voiceInteractor, window, r.configCallback,
                 r.assistToken, r.shareableActivityToken);
        ......
        ......
  }

ActivityThread.performLaunchActivity
-->activity.attach
--> new PhoneWindow()
-->mInstrumentation.callActivityOnCreate

setContentView 的 流程

继承 Activity 的流程

  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

PhoneWindow ---》分为3类,哪些地方会创建
1.Activity
2.Dialog
3.PopupWindow
4.Toast

继承 AppCompatActivity 的流程
AppCompatDelegate.setContentView 
--> ensureSubDecor();
    --> mSubDecor = createSubDecor();
        --> ensureWindow(); // 从Activity 那PhoneWindow   请问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);

如何将xml文件加载到视图里面

// 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."
};

关于inflate四个参数的意义

LayoutInflate的参数的作用
// 方式一:将布局添加成功
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); // 已经addView
//        if (root != null && attachToRoot) {
//            root.addView(temp, params);
//        }
ll.addView(view);

// 方式三:布局成功,第三个参数为false
// 目的:想要 inflate_layout 的根节点的属性(宽高)有效,又不想让其处于某一个容器中
//        if (!attachToRoot) {
//            // Set the layout params for temp if we are not
//            // attaching. (If we are, we use addView, below)
//            temp.setLayoutParams(params);
//        }
View view = inflater.inflate(R.layout.inflate_layout, ll, false);
ll.addView(view);

// 方式四:root = null,这个时候不管第三个参数是什么,显示效果一样
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View,
//        if (root == null || !attachToRoot) {
//            result = temp;
//        }
// 但是子View(Button)有效,因为Button是出于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);


View 没有父容器  ---》 尺寸大小 参数的设置无效  View的绘制流程

//在这个过程我们会发现一些标签, 区别如下
merge

  1. 优化布局
  2. 必须作为rootView

include

  1. id 要注意
  2. 不能作为 root_View

ViewStub 标签

  1. 跟include 差不多 --> 隐藏作用,懒加载

插件化换肤 ---> setConteView的流程

面试题


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

  1. 为什么这么设计呢?
    DecorView的xml布局是通过设置的窗口特征进行选择的。
  2. 为什么 requestWindowFeature(Window.FEATURE_NO_TITLE);设置无效?
    需要用 supportRequestWindowFeature(Window.FEATURE_NO_TITLE);,因为继承的是AppCompatActivity,这个类里面会覆盖设置。

2.LayoutInflate几个参数的作用?

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,这个时候不管第三个参数是什么,显示效果一样
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View, 由内容决定
// 但是子View(Button)有效,因为Button是处于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);

3.描述下merge、include、ViewStub标签的特点
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);
      }

merge:

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

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);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容