【Android 源码解析】一、setContentView 初探

最近在 CSDN 看了某大神的几篇源码解析的文章,自己再回顾整理一遍。

一、从 Activity 的 setContentView 开始

Activity 提供了三个重载的 setContentView 方法

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
}
public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initWindowDecorActionBar();
}

可以看到 Activity 的 setContentView 内部都先调用了 getWindow 的 setContentView 方法,然后调用了 Activity 的 initWindowDecorActionBar 方法。

二、Window 类

getWindow 方法返回的是 Activity 的 Window 类的成员变量 mWindow 。
这里简要介绍一下 Window 类:

  • Window 类是一个抽象类,它的唯一实现类是 PhoneWindow;
  • PhoneWindow 有一个内部类 DecorView,DecorView 是 Activity 的根 View;
  • DecorView 继承自 FramLayout;

三、PhoneWindow 的 setContentView 方法

Window 类的 setContentView 方法都是抽象的,直接看 PhoneWindow 类的 setContentView 方法。

1、setContentView(int layoutResID)

第一个方法传入的参数是布局的资源 ID:

public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        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 {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

在 setContentView 方法中首先判断成员变量 mContentParent 是否为 null,如果是第一次调用,mContentParent 为 null,调用 PhoneWindow 的 installDecor 方法,如果 mContentParent 不为 null,则判断是否设置 FEATURE_CONTENT_TRANSITIONS 的 Window 属性(默认false),如果没有设置该属性就移除 mContentParent 内所有的所有子View;

1.1、PhoneWindow 类的 installDecor 方法

那么 installDecor 方法里面做了 什么呢?

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            //根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent
            mContentParent = generateLayout(mDecor);
            //......
            //初始化一堆属性值
        }
    }

在 installDecor 方法里上来并没有先处理 mContentParent,而是先判断 mDecor 成员变量是否为 null,如果 mDecor 为 null,就调用 generateDecor 方法给 mDecor 赋值,generateDecor 的代码很简单:

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

就是通过 DecorView 的构造方法 new 了一个 DecorView 对象返回。 所以说 PhoneWindow 的 mDecor 是 DecorView 类的成员变量,也就是所有内容的根 View。

1.2、generateLayout 方法

此时 mDecor 不为 null 了,如果 mContentParent 为 null,则调用 generateLayout 方法创建 mContentParent :

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();

        //......
        //依据主题style设置一堆值进行设置

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        //......
        //根据设定好的features值选择不同的窗口修饰布局文件,得到layoutResource值

        //把选中的窗口修饰布局文件添加到DecorView对象里,并且指定contentParent值
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        //......
        //继续一堆属性设置,完事返回contentParent
        return contentParent;
    }

在 generateLayout 方法里面首先根据应用主题 style 设置一堆值进行设置,我们设置的 android:theme 属性都是在这里的 getWindowStyle 方法中获取的,而我们在代码中通过 requestWindowFeature() 设置的属性是在 getLocalFeature 方法中获取的,这也是为什么 requestWindowFeature() 代码要在 setContentView() 前面执行。
然后根据设定好的 features 值选择不同的窗口修饰布局文件,得到布局文件的 layoutResource 值,LayoutInflater 把布局的资源文件解析成 View 之后,添加到 DecorView 中,这个 View 就是 PhoneWindow 的 mContentRoot 成员变量,而 mContentParent 就是布局文件中 ID 为 @android:id/content 的 FramLayout。
再回到 setContentView 方法中,如果 Window 没有设置 FEATURE_CONTENT_TRANSITIONS 的话,就通过 LayoutInflater 把布局文件加载到 mContentParent 中。

2、setContentView(View view) 和 setContentView(View view,ViewGroup.LayoutParams params) 方法

一个参数的方法也是调用了两个参数的方法,只是 params 参数直接设置为 MATCH_PARENT。

@Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

可以看到与参数为 layoutResID 的方法不同之处在于直接调用了 ViewGroup 的 addView 方法将布局加载到 mContentParent 上面。
加载完 View 后,两个方法最后都调用了 Callback 的 onContentChanged 方法来通知对应的 Activity 视图内容发生了变化。getCallback 方法返回的是 Window 的 mCallback 成员变量,这个成员变量是通过 setCallback 方法进行赋值的,毫无疑问,Activity 实现了这个接口,并且在 attach 方法中通过 mWindow.setCallback(this) 进行设置,Activity 的 onContentChanged 方法是一个空方法,当 Activity setContentView 或者 addContentView 时会调用该方法。

3、Activity 的 initWindowDecorActionBar

private void initWindowDecorActionBar() {
        Window window = getWindow();

        // Initializing the window decor can change window feature flags.
        // Make sure that we have the correct set before performing the test below.
        window.getDecorView();

        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
            return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }

至于 Window 的 setContentView 方法执行完了之后的 initWindowDecorActioonBar 方法就是创建一个 Actionbar 并设置一些默认显示等。
Activity 调运 setContentView 方法自身不会显示布局的,一个 Activity 的开始实际是 ActivityThread 的 main 方法,当启动 Activity 调运完 ActivityThread 的 main 方法之后,接着调用 ActivityThread 类 performLaunchActivity 来创建要启动的 Activity 组件,在创建 Activity 组件的过程中,还会为该 Activity组件创建窗口对象和视图对象;接着 Activity 组件创建完成之后,通过调用 ActivityThread 类的 handleResumeActivity 将它激活。
在 handlerResumeActivity 中调用 Activity 的 makeVisible 方法显示我们上面通过 setContentView 创建的 mDecor 视图族。

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

参考:
Android应用setContentView与LayoutInflater加载解析机制源码分析

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

推荐阅读更多精彩内容