从setContentView探讨View,Window与Activity的关系

前言

Activity生命周期的调用时通过ActivityThread管控的,我们在设置应用页面时,都是在onCreate()中调用setContentView()加载布局,这样就产生了三个疑惑:

1:为什么要在onCreate()中设置setContentView()。
2: setContentView是如何起作用的。
3: DecorView和PhoneWindow如何结合。

我么利用android studio 查看布局结构树发现:
E3C27C6A-D864-4C1C-922D-9023369ADCFD.png

通过布局结构树我们发现:应用布局的外层有一个根视图DecorView,那么这个DecorView是如何出现在我们的布局中的呢?

我们发现,setContentView调用了PhoneWindow中的setContentView方法。
public void setContentView(int layoutResID) {  
     getWindow().setContentView(layoutResID);                 
     initActionBar();  
} 

Window是一个抽象类,注解中声明Window是一个管理窗口外观和属性策略的抽象类,它的实现类将会以顶层视图的形式添加到窗口管理器中。它提供了标准的UI策略。且有一个唯一的实现类:PhoneWindow。重新回到Activity源码中搜索PhoneWindow,确实找到了这个类,同时也是getWindow()的返回值类型。注解中声明PhoneView所在包为android.view,但实际上通过检索PhoneView已经被移到了android.internal.policy下。


640.png

在一个Activity对象被创建的初期,会首先依靠WindowManagerGlobal和WMS建立通信关系,WindowManagerGlobal用来向WindowManagerService注册,主要是获取到 WindowManagerService 代理对象。对外提供与WindowManagerService(WMS)的底层通信。随后ActivityThread通过performLaunchActivity调用Activity生命周期。
Activity.attach()是Activity实例化后最先被调用的,这就保证了Window实例化对象的可用性。而onCreate()和onStart()是初始阶段唯一可以重写的方法,其他的都是final类型,鉴于Activity本质是管理页面交互,布局加载时机越早越有益于页面的展示。所以此时不设,更待何时呢。setConteneView(int layoutID)就在onCreate()中调用了。这样第一个问题就回答完了。

setContentView是如何起作用的?

Activity在attach()中实例化了PhoneWindow对象,并且进行了绑定操作,操作如下:
mWindow = new PhoneWindow(this, window);  
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);  
mWindow.setOnWindowDismissedCallback(this);

根据分析可知:
手机展示的页面实际上是一个层层嵌套的样式,一个Activity启动后,首先实例化PhoneWindow对象,调用setContentView时,首先执行installDecor(),通过generateDecor()实例化一个DecorView对象,将PhoneWindow和DecorView进行了关联绑定,通过generateLayout()加载系统布局到DecorView上,并将ID为content的FrameLayout赋值给mContentParent,最后执行inflate()将我们的布局文件自动添加到mContentParent。

View和Window如何结合?

当setContentView()执行完毕后,此时PhoneWindow和DecorView都已经创建完成,但是DecorView并没有添加到PhoneWindow上,这个操作需要在onResume()才会触发,ActivityThread在执行完performLaunchActivity后,便会执行handlerResumeActivity(),具体流程和源码如下图所示:


640.png
 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {  
//执行到 onResume()  
ActivityClientRecord r = performResumeActivity(token, clearHide);  

if (r != null) {  
    final Activity a = r.activity;  
    boolean willBeVisible = !a.mStartedActivity;  
    ...  
    if (r.window == null && !a.mFinished && willBeVisible) {  
        r.window = r.activity.getWindow();  
        View decor = r.window.getDecorView();  
        decor.setVisibility(View.INVISIBLE);  
        ViewManager wm = a.getWindowManager();  
        WindowManager.LayoutParams l = r.window.getAttributes();  
        a.mDecor = decor;  
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;  
        l.softInputMode |= forwardBit;  
        if (a.mVisibleFromClient) {  
            a.mWindowAdded = true;  
            wm.addView(decor, l);  
        }  
    }  
     ...  
    if (!r.activity.mFinished && willBeVisible  
            && r.activity.mDecor != null && !r.hideForNow) {  
        ...  
        mNumVisibleActivities++;  
        if (r.activity.mVisibleFromClient) {  
            r.activity.makeVisible();   
        }  
    }  
    ...  
}   

在这段代码中,内部创建了好多临时变量,其实仔细分析的话,只是两个变量在执行操作,一个就是wm(PhoneWindow自身的WindowManager),一个是decor(setContentView()创建出来的DecorView)。两个变量的最终交互就是wm.addView(decor, l)。同时我们还会发现addView()执行的大前提是等待onResume()执行完毕,如果我们在onResume()中处理耗时操作,那就意味着应用页面的显示时间被延后,为了保障页面尽快进入绘制阶段,onResume中不要处理耗时任务。

理解完这些,我们再来看一下addView()到底做了什么,WindowManager是一个接口类,PhoneWindow的WindowManager对象是WindowManagerImpl,WindowManagerImpl其内部方法始终持有WindowManagerGlobal的引用,我们在ActivityThread的handlerLanuchActivity()中已经知道WindowManagerGlobal是用来和WindowManagerService(WWM)进行通信,在WindowManagerImpl.addView中其实质是把DecorView对象交付给WindowManagerGlobal的视图链中,并通知WWM对当前Window进行管理。引用流程及源码如下:


640-2.png
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {  
...  
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);          
view.setLayoutParams(wparams);      
mViews.add(view);      
mRoots.add(root);      
mParams.add(wparams);          
root.setView(view, wparams, panelParentView);  
...  
} 

WindowManagerGlobal分别存储着View链表和ViewRootImpl的链表,ViewRootImpl就是一个ViewParent视图管理类。每个传入的DecorView都会创建一个对应的ViewRootImpl来管理。它将实际控制着DecorView的绘制周期,同时还可以与WWM进行Binder通信。ViewRootImpl在调用setView后,即向WWM发起了添加请求,WWM便会将当前的PhoneWindow放入自身管理的Window列表中,将DecorView添加到PhoneWindow上,同时通知ViewRootImpl进行绘制操作(绘制操作将涉及到SurfaceFlinger,在这里暂不探讨),代码走到这里时,View和Window之间便真正的结合起来了。其完成流程图如下:


640-3.png

需要注意的是,当获得DecorView对象后,先执行了一次setVisibility(View.INVISIBLE)操作,执行完addView()操作后才会重新设置为VISIBLE,我觉得此处的做法类似于SurfaceView绘制过程对Canvas的锁操作,页面的显示需要由过渡动画管理器TranslateManager进行控制,如果直接在可见状态下进行页面绘制,会给用户一种页面加载卡顿的感觉,而等待页面全部加载绘制完毕后再整体展示给用户可以有效的避免这个问题。

通过对以上三个问题的探究,明确的了解了应用布局的加载过程,一个应用展示在手持设备上时,其布局结构实际如下图所示:


640-4.png

一个Activity对应一个PhoneWindow,一个PhoneWindow对应一个DecorView。
布局加载的整个过程中系统布局对外提供的都是FrameLayout,所以当你看到有些性能优化书籍提出的合并布局方案,建议用<merge>代替FrameLayout作父布局的原因就在这里。同时应用页面视图只会添加在ID为content的FrameLayout中,即系统布局的内容部分。不论开发者配置的样式或者主题有何区别,系统布局中必定会有一个ID为content的控件。

总结

阅读源码时发现,在setContentView()中,频繁用到了inflate()方法,源码中使用的是两参数形式的,而我们在使用inflate()时,更多的是用三参数的,在这列就顺便提一下,inflate(layoutResID, mContentParent)实际上等价于inflate(layoutResID, mContentParent, mContentParent !=null)。mContentParent设置的意义在于协助第一个参数layoutResID所指定布局的根节点生成布局参数,避免宽高设置等属性失效。属性表示一个控件在容器中的大小,就是说这个控件必须在容器中,这个属性才有意义。

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

推荐阅读更多精彩内容