从点击App图标到View显示出来主要流程(二)源码解析setContentView主要工作及View整体布局层次

setContentView主要工作以及VIew整体布局层次

View测量布局绘制流程都是从根View(DecorView)开始的,DecorView究竟在那里创建?在Activity.onCreate生命周期中setContentView()主要做了哪些工作;以及Activity中View的整体布局层次;带着这些疑问,我们来一步步寻找问题的答案;
在Activity.onCreate中会调用setContentView(R.layout.xxx),Activity的视图由setContentView提供,R.layout.xxx是布局文件的资源id;

    public void setContentView(@LayoutRes int layoutResID) {
        /* Activity的视图由setContentView提供,layoutResID是布局文件资源id;
        Window是一个抽象类,具体实现位于Phonewindow中 
        */
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

PhoneWindow.setContentView代码如下:

    //在onResume的makeVisible方法中,DecorView会真正完成添加和显示着两个过程,那时Activity的视图才能被看到
    @Override
    public void setContentView(int layoutResID) {
        // 1.Acitivity刚启动时,mContentParent为null,会执行installDecor();
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 2.将Activity中的布局文件添加到DecorView的mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
    }

在PhoneWindow.setContentView中有两个关键的方法:
installDecor()和mLayoutInflater.inflate(layoutResID, mContentParent);
下面来分别看下这两个方法主要做了哪些工作:
PhoneWindow.installDecor();

    private void installDecor() {
        mForceDecorInstall = false;
        //第一次执行installDecor时,mDecor和mContentParent都为null;
        if (mDecor == null) {
            /*mDecor是DecorView实例,DecorView的根View.
            generateDecor会创建DecorView*/
            mDecor = generateDecor(-1);
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //generateLayout主要工作:找到系统默认布局,并将默认布局文件添加到DecorView中
            mContentParent = generateLayout(mDecor);
        }
    }

PhoneWindow.generateDecor(-1)如下,可以看出DecorView的是在PhoneWindow.installDecor()中创建的。

    protected DecorView generateDecor(int featureId) {
        //主要工作是创建并返回DecorView实例;
        return new DecorView(context, featureId, this, getAttributes());
    }

PhoneWindow.generateLayout(mDecor);

    protected ViewGroup generateLayout(DecorView decor) {
        // Inflate the window decor.
        /*layoutResource是系统默认的布局文件的资源id;
        以下代码根据设置的window属性(notitle、noactionbar等)查找对应系统根布局文件;
        系统布局文件位于/framworks/base/core/res/layout/screen_title、screen_simple(默认布局文件);
        这些系统布局文件中都有ID为Content的控件,并且id为content控件基本都是一个FrameLayout;
        */
        int layoutResource;
        int features = getLocalFeatures();
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            //根据window属性选择对应的默认布局文件;
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            layoutResource = R.layout.screen_progress;
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                //根据window属性选择对应的默认布局文件;
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            layoutResource = R.layout.screen_simple;
        }
        //通过以上代码可知:设置window的flag在setContentView之前才能起作用;
        /*DecorView.onResourcesLoaded方法:通过layoutInfalter将系统默认布局加载出来,
        然后调用decorview的addView方法,将系统默认布局文件加载到DecorView中,这样系统默认布局文件
        就加载到了DecorView中。
        */
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        /*通过findViewById(ID_ANDROID_CONTENT);查找ID为com.android.internal.R.id.content;布局文件。
        这就是应用布局的父view。Phonewindow.findViewById()实际是获取Decor.findViewById()方法;
        因为Decorview已经把系统默认布局文件添加早自己的view中,默认布局中有ID为content布局,找到ID为content 
        FrameLayout布局之后,将该framelayout赋值给mContentParent;
        setContentView设置布局最后是加载到mContentParent中。
        */
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
        return contentParent;
    }

相关说明:
(1)首先根据设置的window属性(notitle、noactionbar等)查找对应系统根布局文件;系统布局文件位于/framworks/base/core/res/layout/screen_title、screen_simple(默认布局文件);这些系统布局文件中都有ID为Content的控件,并且id为content控件基本都是一个FrameLayout;
(2)找到系统默认布局文件的资源id之后,调用DecorView.onResourcesLoaded(layoutinflater,系统默认布局文件资源id)
(3)DecorView.onResourcesLoaded方法:步骤一:通过layoutInfalter将系统默认布局加载出来,然后调用decorview的addView方法,将系统默认布局文件加载到DecorView中,这样系统默认布局文件就加载到了DecorView中;
(4)通过findViewById(ID_ANDROID_CONTENT);查找ID为com.android.internal.R.id.content;布局文件。这就是应用布局的父view。Phonewindow.findViewById()实际是获取Decor.findViewById()方法;因为Decorview已经把系统默认布局文件添加早自己的view中,默认布局中有ID为content布局,找到ID为content FrameLayout布局之后,将该framelayout赋值给mContentParent;setContentView设置布局最后是加载到mContentParent中。
(5)installDecor执行之后,回到setContentView中,继续执行mLayoutInflater.inflate(layoutResID,mContentParent);layoutResID:我们应用中setContentView的布局资源id; mContentParent:系统默认布局文件中id为content的Framelayout中;这样setContentView工作就完成了。
(6)以上解释了为什么设置window的属性必须在setContentView之前才能起作用;
(7)View整体层次为:DecorView>系统默认根布局文件>应用setContentView的布局添加到系统默认布局文件中Id为android.R.id.content的布局中。
(8)以上已经完成将布局文件添加到系统根布局文件中,Android中所有视图都是通过Window来呈现的,此时DecorView还没有被WM添加到Window中。Window addView之后,会调用ViewRootImpl来完成界面的绘制工作;因为View还没有开始绘制,这时候是不会显示出来的。View测量布局绘制是从ViewRootImpl的performTraversals方法中开始的,
(9)通过以上分析可以得出View的整体层次结构如下图所示(转自工匠若水)

从点击App图标到View显示出来主要流程(二)源码解析setContentView主要工作及View整体布局层次_第1张图片
View整体层次结构.png

你可能感兴趣的:(从点击App图标到View显示出来主要流程(二)源码解析setContentView主要工作及View整体布局层次)