Android GUI解析
图解Android - Android GUI 系统 (1) - 概论
再做android的时候我们通常会遇到一些问题,比如说:
- Activity启动过程是怎样的?Activity的onXXX()在后台都做了什么工作?Activity的show()和hide()是如何控制的?
- Surface是什么时候被谁创建的?
- Android是如何把一个个的控件画到Surface上的?然后显示到手机屏幕上的?
- 应用程序窗口是如何获取焦点的?用户的按键(触摸屏或键盘)是怎样传递到当前的窗口?
- Android是一个多窗口的系统吗?
- Android是怎么支持多屏互动的?(Wifi Display)
- 输入法窗口到底属于哪个进程?为什么不管什么应用,只要有输入框的地方都能弹出它?
本文将从框架和流程角度出发,试图对Android的GUI系统做一个简要但全面的介绍,希望借此能够帮助大家找到上述问题的答案。
Android的GUI系统是Android最重要也最复杂的系统之一。它包括以下部分:
- 窗口和图形系统 - Window and View Manager System.
- 显示合成系统 - Surface Flinger
- 用户输入系统 - InputManager System
- 应用框架系统 - Activity Manager System.
它们之间的关系如下图所示
在讲解GUI之前我们需要先解释一些概念
1. Window, PhoneWindow 和 Activity
- Activity 是 Android 应用的四大组件之一 (Activity, Service, Content Provider, Broadcast Receiver), 也是唯一一个与用户直接交互的组件。
- Window 在 不同的地方有着不同的含义。在Activity里,Window 是一个抽象类,代表了一个矩形的不可见的容器,里面布局着若干个可视的区域(View). 每个Activity都会有一个Window类成员变量,mWindow. 而在WindowManagerService里,Window指的是WindowState对象,从图中可以看出,WindowState与一个 ViewRootImpl里的mWindow对象相对应。所以说,WindowManagerService里管理的Window其实是 Acitivity的ViewRoot。我们下面提到的Window,如果没有做特殊说明,均指的是WindowManagerService里的 ‘Window' 概念,即一个特定的显示区域。从用户角度来看,Android是个多窗口的操作系统,不同尺寸的窗口区域根据尺寸,位置,z-order及是否透明等参数 叠加起来一起并最终呈现给用户。这些窗口既可以是来自一个应用,也可以来自与多个应用,这些窗口既可以显示在一个平面,也可以是不同的平面。总而言之,窗 口是有层次的显示区域,每个窗口在底层最终体现为一个个的矩形Buffer, 这些Buffer经过计算合成为一个新的Buffer,最终交付Display系统进行显示。为了辅助最后的窗口管理,Android定义了一些不同的窗 口类型:
- 应用程序窗口 (Application Window): 包括所有应用程序自己创建的窗口,以及在应用起来之前系统负责显示的窗口。
- 子窗口(Sub Window):比如应用自定义的对话框,或者输入法窗口,子窗口必须依附于某个应用窗口(设置相同的token)。
- 系 统窗口(System Window): 系统设计的,不依附于任何应用的窗口,比如说,状态栏(Status Bar), 导航栏(Navigation Bar), 壁纸(Wallpaper), 来电显示窗口(Phone),锁屏窗口(KeyGuard), 信息提示窗口(Toast), 音量调整窗口,鼠标光标等等。
- PhoneWindow 是Activity Window的扩展,是为手机或平板设备专门设计的一个窗口布局方案,就像大家在手机上看到的,一个PhoneWindow的布局大致如下:
2. View, DecorView, ViewGroup, ViewRoot
View 是一个矩形的可见区域。
ViewGroup 是一种特殊的View, 它可以包含其他View并以一定的方式进行布局。Android支持的布局有FrameLayout, LinearLayout, RelativeLayout,AbsoluteLayout 等。
DecorView 是FrameLayout的子类,FrameLayout 也叫单帧布局,是最简单的一种布局,所有的子View在垂直方向上按照先后顺序依次叠加,如果有重叠部分,后面的View将会把前面的View挡住。我们 经常看到的弹出框,把后面的窗口挡住一部分,就是用的FrameLayout布局。Android的窗口基本上用的都是FrameLayout布局, 所以DecorView也就是一个Activity Window的顶级View, 所有在窗口里显示的View都是它的子View.
ViewRoot . 我们可以定义所有被addView()调用的View是ViewRoot, 因为接口将会生成一个ViewRootImpl 对象,并保存在WindowManagerGlobal的mRoots[] 数组里。一个程序可能有很多了ViewRoot(只要多次调用addView()), 在WindowManagerService端看来,就是多个Window。但在Activity的默认实现里,只有mDecorView 通过addView 添加到WindowManagerService里( 见如下代码)
//frameworks/base/core/java/android/app/Activity.java void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }
因此一般情况下,我们可以说,一个应用可以有多个Activity,每个 Activity 一个Window(PhoneWindow), 每个Window 有一个DecorView, 一个ViewRootImpl, 对应在WindowManagerService 里有一个Window(WindowState).
3. ViewRootImple, WindowManagerImpl, WindowManagerGlobals
WindowManagerImpl: 实现了WindowManager 和 ViewManager的接口,但大部分是调用WindowManagerGlobals的接口实现的。
WindowManagerGlobals: 一个SingleTon对象,对象里维护了三个数组:
- mRoots[ ]: 存放所有的ViewRootImpl
- mViews[ ]: 存放所有的ViewRoot
- mParams[ ]: 存放所有的LayoutParams.
同时,它还维护了两个全局IBinder对象,用于访问WindowManagerService 提供的两套接口:
- IWindowManager: 主要接口是OpenSession(), 用于在WindowManagerService 内部创建和初始化Session, 并返回IBinder对象。
- ISession: 是Activity Window与WindowManagerService 进行对话的主要接口.
ViewRootImpl:
ViewRootImpl 在整个Android的GUI系统中占据非常重要的位置,如果把Activity和View 看作 'MVC' 中的V, 把各种后台服务看作Modal,ViewRootImpl 则是'MVC' 中的'C' - Controller. Controller在MVC架构里承担着承上启下的作用,一般来说它的逻辑最为复杂。从下图可以看到,ViewRootImpl 与 用户输入系统(接收用户按键,触摸屏输入), 窗口系统(复杂窗口的布局,刷新,动画),显示合成系统(包括定时器Choreograph, SurfaceFlinger), 乃至Audio系统(音效输出)等均有密切的关联。研究ViewRootImpl 是研究Android整个窗口系统的核心和切入点,我们将在后面详细讨论ViewRootImpl的实现和作用。
三 者( ViewRootImpl, WindowManagerImpl, WindowManagerGlobal) 都存在于应用(有Activity)的进程空间里,一个Activity对应一个WindowManagerImpl, 一个DecorView(ViewRoot),以及一个ViewRootImpl (上面说过,实际一个Activity只有一个DecorView),而WindowManagerGlobals是一个全局对象,一个应用永远只有一 个。
注意的是,在某些情况下,一个应用可能会有几个ViewRootImpl对象,比如说ANR是弹出的对话框,或是网页里面一个视频窗口 (SurfaceView), 在WindowManagerService看来,它们也是一个窗口。同时,SystemServer的进程空间也有自己的 WindowManagerGlobals 和若干个ViewRoot, 因为WindowManagerService 内部也会管理某些系统窗口,如手机顶部的StatusBar, 手机底部的NavigationBar, 以及 锁屏(KeyGuard)窗口,这些窗口不属于某个特定的Activity。
4. WindowManager, WindowManagerService 和 WindowManagerPolicyService
WindowManager: 是一个接口类,定义了一些接口来管理Acitivity里的窗口。WindowManager 是Android应用进程空间里的一个对象,不提供IPC服务。
WindowManagerService: 是SystemServer进程里的一个Service,它的主要功能有
- 窗 口的显示刷新。这里的'Window' 其实是ViewRoot, 和上面WindowManager管理的'Window' 是不一样的,前者是实实在在要进行显示的‘窗口’, 而后者只是一个View的容器,并不会显示出来。大部分情况下,Android同时只有一个Activity工作,但这并不意思着只有一个Window被 显示,Android可能会同时显示来自相同或不同应用的多个Window,比如说,屏幕的上方有一个状态栏,最下方有一个导航栏,有时会弹出一些对话 框,背景可能会显示墙纸,在应用启动过程中,会有动画效果,这个时候两个Activity的窗口会有所变形且同时显示出来,这一切都需要 WindowManager来控制何时,何地,以何种方式将所有的窗口整合在一起显示。
- 预处理用户输入时间(GlobalKey? SystemKey), 并分发给合适的窗口进行处理。
- 输出显示(Display)管理。包括WifiDisplay.
WindowManagerService 是Android Framework里最为庞大复杂的模块之一,我们后面会从各个方面对它进行尽可能详细的分析。
5. Token, WindowToken, AppWindowToken, ApplicationToken, appToken
Token在英语中表示标记,信物的意思,在代码中,有点类似Handle,Cookie, ID, 用来标识某个特定的对象。在Android的窗口系统中,有很多的’Token', 它们代表着不同的含义。
WindowToken: 是在WindowManagerService 中定义的一个基类,顾名思义,它是用来标识某一个窗口。和下面的appWindowToken相比, 它不属于某个特定的Activity, 比如说输入法窗口,状态栏窗口等等。
appWindowToken: 顾名思义,它是用来标识app, 跟准确的说法,是用来标识某个具体的Activity.
ApplicationToken: 指的是ActivityRecord 类里的Token子类。appWindowToken里的appToken也就是它。
appToken: 和applicationToken是一个意思。
下 图描绘了各个Token之间的关系。一个Token下面带一个WindowList队列,里面存放着隶属与这个Token的所有窗口。当一个Window 加入WindowManagerService 管理时,必须指定他的Token值,WindowManagerService维护着一个Token与WindowState的键值Hash表。
通过 ‘dumpsys window tokens' 我们可以列出WindowManagerService当前所有的Token 和 窗口。比如,
WINDOW MANAGER TOKENS (dumpsys window tokens) All tokens: WindowToken{4ea639c4 null}: //token = NULL windows=[Window{4ea7670c u0 Application Not Responding: jackpal.androidterm}, Window{4ea63a08 u0 Keyguard}] windowType=-1 hidden=false hasVisible=true AppWindowToken{4eb29760 token=Token{4eb289d4 ActivityRecord{4ea87a20 u0 com.android.launcher/com.android.launcher2.Launcher}}}: //Launcher2 windows=[Window{4ea837c8 u0 com.android.launcher/com.android.launcher2.Launcher}] windowType=2 hidden=true hasVisible=true ... WindowToken{4eb1fd48 android.os.BinderProxy@4eae8a5c}: windows=[Window{4ea92b78 u0 PopupWindow:4ea0240c}] //对话框 windowType=-1 hidden=false hasVisible=false AppWindowToken{4eb5d6c0 token=Token{4ea35074 ActivityRecord{4ea68590 u0 jackpal.androidterm/.Term}}}: windows=[Window{4eb314e4 u0 jackpal.androidterm/jackpal.androidterm.Term}] windowType=2 hidden=false hasVisible=true app=true
6. Surface, Layer 和 Canvas, SurfaceFlinger, Region, LayerStack
在Android中,Window与Surface一一对应。 如果说Window关心的是层次和布局,是从设计者角度定义的类,Surface则从实现角度出发,是工程师关系和考虑的类。Window的内容是变化 的,Surface需要有空间来记录每个时刻Window的内容。在Android的SurfaceFlinger实现里,通常一个Surface有两块 Buffer, 一块用于绘画,一块用于显示,两个Buffer按照固定的频率进行交换,从而实现Window的动态刷新。
Layer是SurfaceFlinger 进行合成的基本操作单元。Layer在应用请求创建Surface的时候在SurfaceFlinger内部创建,因此一个Surface对应一个 Layer, 但注意,Surface不一定对应于Window,Android中有些Surface并不跟某个Window相关,而是有程序直接创建,比如说 StrictMode, 一块红色的背景,用于提示示Java代码中的一些异常, 还有SurfaceView, 用于显示有硬件输出的视频内容等。
当多个Layer进行合成的时候,并不是整个Layer的空间都会被完全显示,根据这个Layer最终的显示效果,一个Layer可以被划分成很多的Region, Android SurfaceFlinger 定义了以下一些Region类型:
- TransparantRegion: 完全透明的区域,在它之下的区域将被显示出来。
- OpaqueRegion: 完全不透明的区域,是否显示取决于它上面是否有遮挡或是否透明。
- VisibleRegion: 可见区域,包括完全不透明无遮挡区域或半透明区域。 visibleRegion = Region - above OpaqueRegion.
- CoveredRegion: 被遮挡区域,在它之上,有不透明或半透明区域。
- DirtyRegion: 可见部分改变区域,包括新的被遮挡区域,和新的露出区域。
Android 系统支持多种显示设备,比如说,输出到手机屏幕,或者通过WiFi 投射到电视屏幕。Android用Display类来表示这样的设备。不是所有的Layer都会输出到所有的Display, 比如说,我们可以只将Video Layer投射到电视, 而非整个屏幕。LayerStack 就是为此设 计,LayerStack 是一个Display 对象的一个数值, 而类Layer里也有成员变量mLayerStack, 只有两者的mLayerStack 值相同,Layer才会被输出到给该Display设备。所以LayerStack 决定了每个Display设备上可以显示的Layer数目。
SurfaceFlinger的工作内容,就是定期检查所有Layer的参数更新(LayerStack等),计算新的DirtyRegion, 然后将结果推送给底层显示驱动进行显示。这里面有很多的细节,我们将在另外的章节专门研究。
上面描述的几个概念,均是针对于显示这个层面,更多是涉及到中下层模块,应用层并不参与也无需关心。对于应用而言,它关心的是如何将内容画出来。Canvas 是Java层定义的一个类,它对应与Surface上的某个区域并提供了很多的2D绘制函数(借助于底层的Skia或OpenGL)。应用只需通过 LockCanvas() 来获取一个Canvas对象,并调用它的绘画方法,然后 unLockCanvasAndPost()来通知底层将更新内容进行显示。当然,并不是所有应用程序都需要直接操作Canva, 事实上只有少量应用需要直接操作Canvas, Android提供了很多封装好的控件 Widget,应用只需提供素材,如文字,图片,属性等等,这些控件会调用Canvas提供的接口帮用户完成绘制工作。
7. SurfaceFlinger, HWComposer, OpenGL 和 Display
SurfaceFlinger 是一个独立的Service, 它接收所有Window的Surface作为输入,根据ZOrder, 透明度,大小,位置等参数,计算出每个Surface在最终合成图像中的位置,然后交由HWComposer或OpenGL生成最终的显示Buffer, 然后显示到特定的显示设备上。
HWComposer 是 Andrid 4.0后推出的新特性,它定义一套HAL层接口,然后各个芯片厂商根据各种硬件特点来实现。它的主要工作是将SurfaceFlinger计算好的Layer的显示参数最终合成到一个显示Buffer上。注意的是,Surface Flinger 并非是HWComposer的唯一输入,有的Surface 不由Android的WindowManager 管理,比如说摄像头的预览输入Buffer, 可以有硬件直接写入,然后作为HWComposer的输入之一与SurfaceFlinger的输出做最后的合成。
OpenGL 是一个2D/3D图形库,需要底层硬件(GPU)和驱动的支持。在Android 4.0后,它取代Skia成为Android 的2D 绘图图形库,大部分的控件均改用它来实现,应用程序也可以直接调用OpenGl函数来实现复杂的图形界面。
Display 是Android 对输出显示设备的一个抽象,传统的Display 设备是手机上的LCD屏,在Andrid 4.1 后,Android 对SurfaceFlinger 进行了大量的改动,从而支持其他外部输入设备,比如HDMI, Wifi Display 等等。Display的输入是根据上面的LayerStack值进行过滤的所有Window的Surface, 输出是和显示设备尺寸相同的Buffer, 这个Buffer 最终送到了硬件的FB设备,或者HDMI设备,或者远处的Wifi Display Sink设备进行显示。输入到输出这条路径上有SurfaceFlinger, OpenGL 和 HWComposer。
有了上述概念的解析,对Android的GUI 系统应该有了一些模糊的认识,接下来我们将按下面的顺序将一步步深入其中的细节。