setContentView 背后那些事儿

一行简单的 setContentView() 背后也会有大量的底层工作。往常总是手快的敲下这一行代码,甚至使用 AS 自动创建 Activity 都不用自己敲这一行代码,但是你有没有想过这一行简单代码背后的机制呢?这次就一起来看看。

Hierarchy View

在对这行代码背后的机制进行分析之前,要先学会如何去看一个具体的 Activity 是由哪些部分构成的,这对我们接下来的理解有很大的帮助。Hierarchy View 可以用图形化的视图来展示界面的组成结构。打开 AS 的 DDMS 视图,也就是 Android Device Monitor 这个工具。点击顶部工具栏 DDMS 按钮左边的 Open Perspective 按钮,选择 Hierarchy View ,就可以进入了。点击左边的小手机图标就能选择具体的 Activity 来查看组成结构。不过这个工具只能用来看虚拟机上的 Activity,实体机目前还不支持。

上面就是我的某个 App 的一个 Activity 界面的组成结构。其最左边,也就是最顶层的视图,是一个 PhoneWindow,同时也是一个 DecorView。记住这两个词,后面会用到。

Activity#setContentView()

我们知道,Activity 是所有类型的 Activity 的父类,那么自然 setContentView 这个方法最开始也是在 Activity 这个类中定义的。

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

从代码里看得出来,(系统)拿到了一个 window 并把布局 id 设置到了这个 window 里,然后初始化了 window 的 Decor 和 ActionBar,就这么两件事情。首先看看拿到的 window 是什么:

    public Window getWindow() {
        return mWindow;
    }

mWindow 是一个抽象类 Window, 而 mWindow 这个变量在整个 Activity 中只有一处被初始化:

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);
 
        mFragments.attachHost(null /*parent*/);
 
        mWindow = new PhoneWindow(this, window);
        ...
        mWindow.setCallback(this);
        ...
    }

PhoneWindow?好像有点眼熟,在第一步 Activity 的组织结构里,最左边的不就是个 PhoneWindow 么?那么这个 PhoneWindow 应该就是一个界面最顶层的 View 了,不过在看 PhoneWindow 的源码之前,先看看它的抽象类 Window 的说明:

/**
 * Abstract base class for a top-level window look and behavior policy.  An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
    ...
}

Window 是一个顶层窗口(界面)的外观和行为的代理,是个抽象类。这个类的实例应该当做顶级 View 添加到 window manager 中。这个类提供基本的 UI,例如背景、标题区域、默认的按键处理程序等。同时 PhoneWindow 这个类也是 Window 的唯一实现类。当你要显示一个视窗的时候你就必须实例化这个类(PhoneWindow)。也就是说,setContentView() 最终会调用 PhoneWindow 里的 setContentView() 方法。

PhoneWindow#setContentView()

下面是 PhoneWindow 中的代码:

    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);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

这个方法中的第二行 insallDecor 这个方法确保了 mDecor 和 mContentParent 这两个变量都已经被实例化,将 mDecor 和 window 进行绑定,同时初始化视窗的标题栏,并从实例化的 window 中获取一些视窗标记(flag),还记得我们为了隐藏 Activity 的标题栏而调用 requestWindowFeature() 这个方法么,就是与这里是相同类型的标记,因为获取这些标记的过程是在 setContentView 里进行的。如果我们想让自定义的标记生效,就得在 setContentView() 这个方法之前调用 requestWindowFeature()
那么 mContentParent 又是什么呢:

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;

这个 view 是用来放置视窗内容的(就是后期由我们自行添加的区域),要是隐藏了系统状态栏,它的大小就会跟它的上一层 mDecor 相同。也就是说,系统的状态栏是处于 mDecor 内部、mContentParent 外部的。

在实例化 mDecor 和 mContentParent 之后,就开始解析传进来的布局文件,将这个布局设置到 mContentParent 里面,最后通知 Activity 布局已经发生变化:首先 Activity 是实际的视窗的控制者,不通知它还能通知谁呢?其次呢,确实就是这么回事,还记得前面 Acticity 中的 attach 方法么,里面有这么一句:mWindow.setCallback(this);,而 mWindow 就是我们现在在讨论的 PhoneWindow 了,所以铁证如山啊。

Activity#insallDecor()

而关于 insallDecor 这个方法的具体情况,内容比较多,就不贴代码了,只要知道:

  • mDecor 是通过 generateDecor() 这个方法生成的,这个方法获取当前所能得到最高级别的 Context 来生成 DecorView 的实例,就是有 ApplicationContext 就尽量用 ApplicationContext;
  • mContentParent 是通过 generateLayout() 这个方法生成的,在这个方法里,会根据预设的 Window Style 来对布局文件多进行定制,也就是多次调用 requestWindowFeature() 这个方法,根据 Feature 的不同,还会选择不同的布局作为 DecorView 的初始布局。

Activity#initWindowDecorActionBar()

到这里 PhoneWindow 的 setContentView 就解读完了,在开始的 Activity 里的 setContentView 方法中还有一句代码: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());
    }

这个方法是对初始化布局的进一步补充,在 PhoneWindow 里知识加载出一些默认的布局比如标题栏,那么这里就是对默认的标题栏进行一些初始的设置,比如在 manifest 文件中给一个 Activity 设置 label 属性,那么打开 Activity 就会直接显示 label 的内容,这个 label 就是在这里被设置的。

总结

结合前面这些加载过程的详情和最开始的组织结构,能够得到下面这个更直观的视窗组织结构示意图:


  • 系统 Launcher 界面:每一个 App 的图标都是在系统 Launcher 里面的一个按钮,通过按钮打开我们自己的 App,所以 Activity 外面就是 Launcher 的界面的了;
  • 应用窗口(mWindow):这个就是一个 Activity 内容的容器,是区分不同的 Activity 界面的最小单位,也是 PhoneWindow 的一个实例,Activity 加载过程中调用 attach方法的时候被实例化;
  • 顶级 View :如果说 Window 知识一个容器,那么从 mDecor 开始就能够真的显示一些东西了,这是一个 DecorView 对象,定义在 PhoneWindow 中,用来加载包括状态栏和导航栏在内的所有布局;
  • 系统状态栏:statusbar,PhoneWindow 会加载其背景进行占位,但并不具体绘制其内容;
  • 导航栏:navigationbar,与状态栏一样,PhoneWindow 会加载其背景进行占位,但并不具体绘制其内容;
  • mContentParent:这个就是可以经由我们自由添加和修改的布局了,其中可以添加和隐藏标题栏,而如果没有标题栏,大小就会跟 mDecor 相同;

而 setContentView 的过程,就是实例化 mWindow、mDecor、mContentParent 的过程,在这个过程中,根据 window 的主题、属性特征的不同,会加载不同的布局和 UI,包括是否显示状态栏、导航栏甚至是标题栏。经过这个过程,一个基本的 Activity 就可以显示出来,在这个基础上,我们就可以去按照自己的需求对 mContentParent 进行定制来充实自己的 App。

本文最早发布于alphagao,com

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

推荐阅读更多精彩内容