View-Window-Surface
前言
本文打算从Root View的建立出发,呈现View
,Window
,Surface
之间的联系。
从而厘清这三者之间的不同,加深对Android Graphic系统的理解
V-W-S简介
View
:android中最基本的UI组件,会占据屏幕上的一席之地并可以处理一些基本事件。This class represents the basic building block for user interface components. A View
occupies a rectangular area on the screen and is responsible for drawing and
event handling. View is the base class for widgets, which are
used to create interactive UI components (buttons, text fields, etc.).Window
:Window是top View的容器,这里的top view我们一般都是说ViewRoot。Window的概念在Android中比较抽象,广义上,被认为是一个App对应了一个window,狭义上,Window实际又对应了
ViewRoot,对于WindowManagerService来说,如何呈现/管理App端的显示,都是通过Window来实现的Surface
:Surface可以认为是一块提供绘制的buffer,所有View的修改,最终都会被统一反馈到Surface上。每个ViewRoot都会有一个真实的surface变量,View的所有操作会反馈到这块Surface。这块Surface也即SurfaceFlinger
的某一个composer对象,因此只有操作到了ViewRoot中的Surface,屏幕上才会有变化
注意:此处的Surface并不是SurfaceView,SurfaceView本质上还是一个View,注意区分
RTFSC
为了更深刻的理解这个问题,本文打算从ViewRoot的建立一路讲述,一直到三者的联系完全建立未知。
setContentView
对于
setContentView
相信大家都不会陌生,一般都是传入一个Resouce ID,这样整个Activity
的视图就建立了。ok,对于
Activity
来说,其实是透过getWindow
,来做setContentView
,那么我们看看对应的Window
是谁。mWindow
是Activity类内部的一个私有变量,顺藤摸瓜,我们看一下mWindow
是从哪里被new出来的。如果你熟悉Activity的创建过程,应该就知道了,mWindow其实是在
performLaunchActivity
的时候,调用Activity.attach
的时候创建的。所以,下面我们需要去看看
PhoneWindow.setContentView
顺便插一句,
PhoneWindow
这个类比较重要,而它本身是extends Window
可以看到对于传入的
layoutResID
,最终会通过inflate创建出对应的View
但是整个过程中并没有看到ViewRoot,所以我们还需要看看划红线的installDecor
通过上图可以看到,当且仅当
mContentParent == null
的时候才会走进去,那么mContentParent
是哪里赋值的呢?这里又平白无故多出了个
mDecor
,什么是private DecorView mDecor
我们先通过视图查看神器hierarchyviewer
搂一眼,先看看第一个initXXX
显然,DecorView是一个Root View,如果这个不清楚..那么请看下图
让我们来聚焦到:
mDecor = generateDecor(-1);
直接了当,继续往下看看
new DecorView
首先,
DecorView
是extends自Framelayout
,从本质上,DecorView
是一个容器Viewgroup
,也是一个View
再来看看真正的
new DecorView
通过
setWindow
函数,会建立起PhoneWindow
与DecorView
建立联系,因此,DecorView
中就会持有PhoneWindow
的引用- 至此,整个
mDecor = generateDecor(-1);
的流程就走完了,简单来说DecorView
是一个ViewGroup
,hierarchyviewer
中也可以看出来,整个DecorView是视图的Root
- 建立了
PhoneWindow
与DecorView
- 至此,整个
我们走完了
generateDecor
,继续往下走,来到mContentParent = generateLayout(mDecor);
整个
generateLayout
分为4步- 根据一系列判定条件选择合适的
layoutResource
- 通过对应的
layoutResource
进行inflate
- 透过整个
ViewTree
获取contentParent
- 这个
contentParent
,就是上面提到的mContentParent
其中比较重要的是步骤二和步骤三,也即:
- 透过整个
ViewTree
获取contentParent
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
- 这个
contentParent
,就是上面提到的mContentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
我们直接看一下
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
:- 其中
createDocrCaptionView
的部分可以略过,根据原生的代码,这一块主要是给FreeForm
来使用的
如下图所示: inflate
也没有什么可说的,第二参数为null,表示无默认Parent View
addView
亦是常规操作,显然是把刚才inflate
出来的root
作为child view
add到DecorView
- 最后一波
mContentRoot = (ViewGroup) root;
整个操作结束
我们回头再看一下
DecorView
三剑客mWindow
:在DecorView
初始化的时候就已经建立了联系mContentRoot
:通过feature
选择的最恰当layoutResource
,进而通过inflate
创建的View
mDecorCaptionView
:FreeForm
的“action bar”
再来看看
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
:
以上4支api完成了整个findViewById
的操作,通过之前的认知,整个mDocr
是Window
/ViewTree
的root
而对应的ID_ANDROID_CONTENT
,则是整个ViewTree的Root
下的第一个View
,而这个ID_ANDROID_CONTENT
这个id,确实也是在frameworks res
里面会用到,也就是那些默认的layout
们
到这里,我们把mContentParent = generateLayout(mDecor);
走完了,简单总结一下:- 通过
generateLayout
,基于mDecor
又添加了一个子View
,ID:ID_ANDROID_CONTENT
(视情况不同可能为CaptionView) - 最终
mContentParent
永远都是ID:ID_ANDROID_CONTENT
的View
- 根据一系列判定条件选择合适的
走完了上面一长串流程,其实是在建立
mDecor
/PhoneWindow
/mContentParent
之间的联系,而我们通过setContentView
传入的那个Resource ID
,压根都还没有用到。
重新看这一段,整个installDecor
就完成了。下面才是用到了
layoutResID
,通过inflate
,把这个layoutResID
对应的View放到mContentParent
那么这个
mContentParent
是谁呢?再回头看,其实就是generateLayout
中的return contentParent
,也就是对应的ID:ID_ANDROID_CONTENT
因此,整个setContentView
的所有逻辑就走完了。
- 通过
installDecor
,把PhoneWindow
/DecorView
/mContentRoot
建立起联系 - app端真正传入的
ResouceID
,会通过inflate
出一个View
,把它加到mContentRoot
也即ID:ID_ANDROID_CONTENT
小结:
对于app端来说setContentView
是整个ViewTree
建立的入口,也是好多Demo中onCreate
必撸的代码。
当了解了setContentView
传入的resID
最终会变成哪个View
,变成谁的Child View
,厘清这一点对于debug很有必要。
UML两张流
UML类图
比较重要的是三个类:Activity
,PhoneWindow
,DecorView
UML时序图
对应的流程图:
View与Window-MnagerService
先前的setContentView
帮助我们在app内部建立了一套完整的ViewTree
但是自始至终,我们都没有发现App与System_Server之间发生任何的关系,通过我们认识,Window
只有被add到了WindowManagerService
之后才会有机会被显示出来。
一点点基础知识
- 顺着这个思路,我们继续往下走,这边直接来到
ViewRootImpl
的构造函数处: 以上Call Stack是既成事实的,但是我们仍旧要看一下传入的各个参数:
这里的r,其实是
Activity
对应的r.window
,就是PhoneWindow
而r.window.getDecorView()
,其实就是对应的DecorView
因此整个ViewManager
最后会把decor
添加进去根据一系列的继承关系:
WindowManagerImpl
-|>WindowManager
-|>ViewManager
,所以就来到了WindowManagerImpl
这里的
mGlobal
又是一个典型的桥接模式
View / Window / WMS
在
mGlobal
的addView
中,就会去创建ViewRootImpl
,从而建立起与WMS的整个联系,也就是串起了View
和Window
。ViewRootImpl
的初始化,是一个非常重要的过程:对于第一步,我们先来看看
mWindowSession = WindowManagerGlobal.getWindowSession();
其中
mWindowSession
是一个IPC的Proxy对象:IWindowSession
对应的native实体,其实是
Session
,而这个Session
则是WindowState
中的一个的对象:
WindowState
的话,则是WindowManagerManager
中重要的对象(基本上可以认为一个client端的window,就是WMS端的一个WindowState再来看看第二步:
mWindow = new W(this);
,对于这个W
,它继承自IWindow.Stub
这里引入出了一个
IWindow
的概念,由于在App端我们持有的是Stub
,因此,在WMS那边的话其实就是对应的Proxy
了整个
ViewRootImpl
的初始化,我们有两个重要对象mWindowSession
以及mWindow
,我们先mWindowSession
:是继承自IWindowSession
,也即App端,是一个ClientmWindow
:是继承自IWindow.Stub
,也即App端,是一个Server
完成了
ViewRootImpl
的初始化,我们继续往下走:root.setView(view, wparams, panelParentView);
这里的
root
,其实就是ViewRootImpl
,因此这边其实是调用了ViewRootImpl.setView
- 整个
setView
的过程,其实是在建立App端与Server的联系,也即App与WMS
这里的
requestLayout
其实也有其特定作用,但本文先不分析了,我们直接看mWindowSession.addToDisplay
- 整个
根据我们对
AIDL
的认识,可以知道这边会直接跑到Server端运行,而之前也知道,对应的Server端其实是Session
对象,因此这边的mWindowSession.addToDisplay
,就会走到Session.addToDisplay
- 其中,传入的第一个参数
window
,在App端其实就是IWindow.Stub
实例mWindow
Session
中的mService
,实际是WindowManagerService
- 其中,传入的第一个参数
因此,
Session.addToDisplay
最终就跑到了WindowManagerService.addToDisplay
整个addWindow
的流程,针对当前的流程来梳理,我们需要看上述的第一步和第二步。其中第一步,
session
对应了App端的mWindowSession
,client
对应的则是App端的mWindow
这里的
mWinAnimator
,在WMS
里面往往是用来做各类动画的基础第二步,
win.attach
:这里的
mSession
是之前在构造函数中赋值的Session
实例。
通过这个windowAddedLocked
,Session
被mService.mSessions.add(this)
与WMS建立了联系
同样:mSurfaceSession = new SurfaceSession();
这个地方,Session
才真正与SurfaceFlinger建立了联系
小结
ViewRootImpl
的初始化过程中,会带出mWindowSession
以及mWindow
。
其中,mWindowSession
是ISession
在App端的Proxy
,而mWindow
则是IWindow
在App端的ISession
。
因此,两者的地位和作用也是不同的,mWindowSession
是用来与WMS做通讯的,而mWindow
则是WMS用来回调App的。
UML两张流
UML类图
点评一下这张令人头大的UML蜘蛛图:
Activity
中会持有mWindow
,它是PhoneWindow
的实例PhoneWindow
继承自Window
PhoneWindow
中,会持有DecorView
;同样DecorView
也会持有PhoneWindow
的实例DecorView
集成自FrameLayout
,这一路是从ViewGroup
,ViewParent
,ViewManager
下来的。
上述内容是之前setContentView
中的东西
ViewRootImpl
被WindowManagerGlobal
所持有WindowManagerGlobal
通过桥接模式,被WindowManagerImpl
所持有,而WindowManagerImpl
实现了WindowManager
的接口ViewRootImpl
会持有IWindowSession
的实例mWindowSession
IWindowSession
继承自IInterface
,它是一个Client端,运行在App进程,System_Server是Server端ViewRootImpl
会持有ViewRoot.W
的实例mWindow
ViewRootImpl.W
是继承自IWindow.Stub
,它是一个Server端,运行在App进程,System_Server是Client端