图解Android - Android GUI 系统 (1) - 概论
Android的GUI系统是Android最重要也最复杂的系统之一。它包括以下部分:
- 窗口和图形系统 - Window and View Manager System.
- 显示合成系统 - Surface Flinger
- 用户输入系统 - InputManager System
- 应用框架系统 - Activity Manager System.
它们之间的关系如下图所示
只有对这些系统的功能和工作原理有基本的了解,我们才能够解答一些经常萦绕在脑海里的问题,比如说:
- Activity启动过程是怎样的?Activity的onXXX()在后台都做了什么工作?Activity的show()和hide()是如何控制的?
- Surface是什么时候被谁创建的?
- Android是如何把一个个的控件画到Surface上的?然后显示到手机屏幕上的?
- 应用程序窗口是如何获取焦点的?用户的按键(触摸屏或键盘)是怎样传递到当前的窗口?
- Android是一个多窗口的系统吗?
- Android是怎么支持多屏互动的?(Wifi Display)
- 输入法窗口到底属于哪个进程?为什么不管什么应用,只要有输入框的地方都能弹出它?
本文将从框架和流程角度出发,试图对Android的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 等。
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 系统应该有了一些模糊的认识,接下来我们将按下面的顺序将一步步深入其中的细节。
1. 图解Android - Android GUI 系统 (2) - 窗口管理系统
在这一章里,我们将试图解答下面几个问题:
- Surface是什么时候,被谁创建的?
- 窗口是何时,何地被绘制和更新?
- 窗口的显示和隐藏是如何控制的?
- 窗口的切换动画是怎样实现的?
- More ...
2. 图解Android - Android GUI 系统 (3) - Surface Flinger (TBD)
在这一章里,我们将探讨:
- Surface 后面的Buffer 何时何地创建和销毁,如何管理。
- 多个Surface是如何合成在一起。
- 怎样实现多屏输出?
- More ...
3. 图解Android - Android GUI 系统 (4) - Activity的生命周期
- Acitivity的生命周期是怎样的?何时创建,何时显示,何时隐藏,何时销毁?
- 什么是UI thread? 为什么说不要在Andrid 的UI thread里做太多的事情?
- More ...
4. 图解Android - Android GUI 系统 (5) - Android的用户输入处理
按键是怎么从底层driver 传递到具体的窗口进行处理的?
全局键是怎么处理的?
ANR是如何产生的?又如何处理?
等等...
图解Android - Android GUI 系统 (1) - 概论的更多相关文章
- 图解Android - System Service 概论 和 Android GUI 系统
通过 图解Android - Binder 和 Service 一文中,我们已经分析了Binder 和 Service的工作原理.接下来,我们来简要分析Android 系统里面都有哪些重要的Servi ...
- 图解Android - Android GUI 系统 (2) - 窗口管理 (View, Canvas, Window Manager)
Android 的窗口管理系统 (View, Canvas, WindowManager) 在图解Android - Zygote 和 System Server 启动分析一 文里,我们已经知道And ...
- Android GUI系统
图解Android - Android GUI 系统 (1) - 概论 图解Android - Android GUI 系统 (2) - 窗口管理系统 图解Android - Android GUI ...
- 图解Android - Android GUI 系统 (5) - Android的Event Input System
Android的用户输入处理 Android的用户输入系统获取用户按键(或模拟按键)输入,分发给特定的模块(Framework或应用程序)进行处理,它涉及到以下一些模块: Input Reader: ...
- Android监听系统短信数据库变化-提取短信内容
由于监听系统短信广播受到权限的限制,所以很多手机可能使用这种方式没法监听广播,从而没办法获取到系统短信,所以又重新开辟一条路. Android监听系统短信数据库内容变化使用场景: 1.监听短信数据库的 ...
- 让Android程序获得系统的权限,实现关机重启,静默安装等功能
引用:http://www.cnblogs.com/welenwho/archive/2012/05/10/2494984.html android想要获得系统权限有几种途径,一种就是你的程序固化的系 ...
- Android manifest之系统自带的permission
Android manifest之系统自带的permission 本文描述Android系统自带的permission.点击查看:“关于permission的原始定义和说明”.点击查看:“Androi ...
- Android应用与系统安全防御
来源:HTTP://WWW.CNBLOGS.COM/GOODHACKER/P/3864680.HTML ANDROID应用安全防御 Android应用的安全隐患包括三个方面:代码安全.数据安全和组件安 ...
- android BroadcastReceiver ACTION_TIME_TICK 系统时间监听不到
android BroadcastReceiver ACTION_TIME_TICK 系统时间监听不到 今天做android上的消息推送,启动了一个独立service,然后在里面监听系统的ACTION ...
随机推荐
- HDU 4782 Beautiful Soup --模拟
题意: 将一些分散在各行的HTML代码整理成标签树的形式. 解法: 模拟,具体见代码的讲解. 开始没考虑 '\t' .. 代码: #include <iostream> #include ...
- 两道相似KMP题
1.POJ 3450 Coporate Identity 这两题的解法都是枚举子串,然后匹配,像这种题目以后可以不用KMP来做,直接字符串自带的strstr函数搞定,如果字符串未出现,该函数返回NUL ...
- NYOJ----1124数量
数量 时间限制:200 ms | 内存限制:65535 KB 难度:0 描述 HJS大牛想去街上吃饭,街道旁边拴着很多狗,他想我堂堂......(省略n个字)岂会被狗咬,所以他很牛的从狗的面前经过 ...
- leetcode: Path Sum II 迭代法
Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given su ...
- ExtJS要利用观察者模式 去实现自定义的事件
// 要利用观察者模式 去实现自定义的事件 //1:由于浏览器他自己能定义内置的事件(click/blur...) // 我们也应该有一个类似于浏览器这样的类,这个类 自己去内部定义一些事件(自定义事 ...
- java 14 -1 正则表达式
正则表达式:符合一定规则的字符串. 1.判断QQ号码是否正确的案例: public class RegexDemo2 { public static void main(String[] args) ...
- Mybaits学习总结2
http://www.cnblogs.com/xdp-gacl/p/4262895.html 继续参考这篇文章写Mybaits学习总结 上一章,我修改了编码,统一为UTF8之后,便没有编码错误 < ...
- main方法的理解
1),在执行一个类的时候,所找到的方法是mian(). 2)string args[]:输入的参数. public class StaticDemo08{ public static void mai ...
- jQuery on(),live(),trigger()
jQuery on()方法是官方推荐的绑定事件的一个方法. $(selector).on(event,childSelector,data,function,map); 由此扩展开来的几个以前常见的方 ...
- 17Mybatis_动态sql-sql片段
这篇文章讲一下sql片段. 讲一下sql片段的的需求: 将上边实现的动态sql判断代码块抽取出来,组成一个sql片段.其它的statement中就可以引用sql片段. 方便程序员进行开发. 第一步我们 ...