从点击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的整体层次结构如下图所示(转自工匠若水

View整体层次结构.png

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258

推荐阅读更多精彩内容