[源码分析]Android之setContentView

背景


在每个 Activity 中,一般为了操作各种 view ,首先要做的就是 setContentView。我们总是在使用它,但是实际上它运作的背后到底发生了什么呢?一直想找时间深入了解一下,多读读源码,学习下别人优秀的表达。碰巧遇到视频在view上的播放问题,趁机走一波(源码版本,Android-25.)。

setContentView


通过下面的图,我们来看看这个方法到底做了些什么:


setContentView

对照实际关系,如下图所示:


实例

上图中所set进去的mContentView布局文件为:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@android:color/holo_blue_light"
    android:gravity="center"
    xmlns:android="http://schemas.android.com/apk/res/android" >

    <TextView
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="@android:color/holo_red_light"
        android:text="Hello\nWorld"
        android:textStyle="bold"
        android:textSize="20sp"
        android:gravity="center"
        android:layout_gravity="center"/>
</LinearLayout>

是不是对“神秘button”有点好奇?布局文件中根本没有这个控件啊?是的。此处插播一个小曲,简单说明下这个button的来历,是用了addContentView方法。

addContentView(btn, new FrameLayout.LayoutParams(params));

该方法的源码为:

 @Override
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            // TODO Augment the scenes/transitions API to support this.
            Log.v(TAG, "addContentView does not support content transitions");
        }
       //直接添加到mContentParent,与mContentView同级
        mContentParent.addView(view, params);
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
结论

回到主题,在一个window中,所有view共同的祖先,是 decorview。先有 decorview,然后在 decorview上进行层层绘制。

setContentView前后,更进一步,更多细节


setContentView是在onCreate中执行的。在此之前,谁在调用onCreate呢?
在ActivityThread的performLaunchActivity方法中,有几个关键步骤:

...
//创建Activity
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
...
//创建该Activity的PhoneWindow对象
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);
...
//执行onCreate回调,同时,在该回调方法的setContentView中,decorView被set进PhoneWindow中
if (r.isPersistable()) {
       mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
       mInstrumentation.callActivityOnCreate(activity, r.state);
 }
...
//执行onStart回调
if (!r.activity.mFinished) {
       activity.performStart();
       r.stopped = false;
}
...

到此,我们可以看到:

  1. 首先activity将会被创建;
  2. activity中创建Window(实际上是PhoneWindow)对象;
  3. 在OnCreate的setContentView中,将我们的布局放置到decorView中,然后,将decorView set进了Window中;
  4. onStart回调执行。

此时,其实我们仍然不清楚,setContentView之后,我们的布局该如何显示给用户呢?
其实,performLaunchActivity执行后,还有一个重要的方法将要执行,那就是handleResumeActivity,我们接着看它的细节。

...
//执行onResume回调
r = performResumeActivity(token, clearHide, reason);
...
r.window = r.activity.getWindow();
//拿出decorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//拿出WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
    a.mWindowAdded = true;
    r.mPreserveWindow = false;
    ViewRootImpl impl = decor.getViewRootImpl();
    if (impl != null) {
                 impl.notifyChildRebuilt();
    }
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
  a.mWindowAdded = true;
  //将decorView 放置到WindowManager中
  wm.addView(decor, l);
 }
...

由上面一些列过程,可知最终,decorView交给了WindowManager处理。这个关键的交接节点,就是上面的wm.addView方法。
该方法的执行,内部充斥这本地进程和服务进程的跨进程通信过程。最终,在WindowManagerGlobal实现:

...
root = new ViewRootImpl(view.getContext(), display);
...
//decorView被交给了WindowManagerGlobal中的ViewRoot
root.setView(view, wparams, panelParentView);
...

此后,decorView被装进了ViewRoot中。而在ViewRoot里包含了一个Surface,decorView将被绘制在这个Surface中。

用户感知和性能提升


先来说明下帧率和刷新率。
所谓帧率,是对于GPU而言,每秒钟能够绘制的帧数,单位是FPS(帧数/秒),Android设备的标准帧率是60FPS;
所谓刷新率,是指每秒钟屏幕更新的次数,单位是赫兹(Hz),大部分Android设备的刷新率是60Hz。
其实当每秒钟帧数(帧率)达到10-12帧,人们就可以感知到运动,但是这会产生运动模糊。我们都知道,在Android设备上,App的帧率是60FPS,这就意味着每一帧必须在16.667ms内完成绘制,否则会出现丢帧。丢帧如果很严重,将会导致的糟糕的交互体验,更严重的是,由于资源消耗严重,造成卡顿。
就上面提到的View 的层次关系,是否存在改善性能的空间呢?
首先需要尽可能减少布局层次,然后,再从每个view的绘制上下功夫。
SDK文档中指出:

Draw traversal performs several drawing steps which must be executed
in the appropriate order:
1. Draw the background
2. If necessary, save the canvas' layers to prepare for fading
3. Draw view's content
4. Draw children
5. If necessary, draw the fading edges and restore layers
6. Draw decorations (scrollbars for instance)

在绘制View的时候,首先将要绘制的就是背景。实际上,这么多View的层次关系,我们需要的只有一个背景,其他的对于GPU来说都是毫无意义的负担。
所以,对于一个Window来说,注意设置一个背景就够了。

getWindow().getDecorView().setBackground(null);

或者

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

推荐阅读更多精彩内容

  • 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? 知道Android...
    CoorChice阅读 26,423评论 42 312
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,046评论 25 707
  • 时间颗粒度就是一个人安排时间的基本单位。 我觉得一个越成功的人,他的颗粒度才会越小!时间颗粒度一定和这个人的地位成...
    畅畅_阅读 203评论 0 3
  • 夏季六月十三日,永江携夫人秀红女士,返第二故乡葫芦岛,軍营战友去北站迎接,单位同事邹女士也从兴城赶到接站,几十年分...
    广仁阅读 479评论 0 0
  • 表弟可能要去找他对象和他对象的老公动刀子了,而我在幸灾乐祸。 这是一个复杂的故事。 他小我一天,我俩出生日期同年同...
    北芜菇凉阅读 350评论 0 0