我这里的GWES这个术语实际上从Microsoft 的Window上移植过来的,用GWES来表示Android的窗口事件系统不是那么准确,在Android中Window是个弱化了的概念,更多的表现在View这个概念上。在很大程度上,Android的View的概念可以代替Microsoft Window这个概念,有点和Microsof暗中较劲的意味,你用过的概念我就偏不用,这个也是我以为的设计者意图。
首先我们从Android的SDK外特性空间开始,在编写Actvitiy时,我们都是面对的处理函数:OnXXXX(),例如有按键按下就是OnKeyDown等,在这个过程中系统做了怎样的处理?要详细的理解这个过程,我们就需要理解Andoid的View管理,窗口系统,消息系统和输入系统。我们还是从最本质的地方开始,Android作为一种嵌入式的图形用户界面系统,它的基本原理与一般GUI的原理是相同的,同时也是遵循GWES(图形窗口事件系统)的一般规律,总体上Android就是管理用户输入和系统屏幕输出的一个系统。其实GWES这个名称更能体现GUI的基本实质要素:图形、窗口、事件。
GUI的实现就是对上面提到的三个基本要素的管理,根据这这三个要素的特性及其涉及的范围,GUI在总体上可以分为三部分:
(1) 事件管理器
(2)窗口管理器:
(3)GDI
在本质上GUI就是管理用户输入和屏幕输出,我们从上面的体系结构可以看到GUI的这两大数据流的基本流向,这也决定了Android GWES设计的最基本的着眼点。
Android弱化了窗口的概念,着重使用View的概念。所以Android的基本组成可以从上面的图修改成如下的组成:
我们要理解Android的消息系统,Looper,Handle,View等概念还是需要从消息系统的基本原理及其构造这个源头开始。从这个源头,我们才能很清楚的看到Android设计者设计消息系统之意图及其设计的技术路线。
从一般的系统设计来讲,一个消息循环系统的建立需要有以下几个要素:
首先来研究一下消息驱动的基本模型,我使用如下的图形来表示一个消息系统最基本构成:
上面的模型代表应用程序一直查询自己的消息队列,如果有有消息进来,应用消息处理函数中根据消息类型及其参数来作相应的处理。
消息系统要运作起来,必定有消息的产生和消费。我们可以从下图看到消息生产和消费的一个基本的链条,这是一个最基本的,最简单的消息系统。
Android消息模型
Android要建立一个消息系统使用了Looper,MessageQueue,Handler等概念,从上节的原理我们可以知道这些都是概念包装,本质的东西就是消息队列中消息的分发路径的和消息分发处理方式的设计。Android巧妙的利用了对象抽象技术抽象出了Looper和Handler的概念。在Looper和Handler两个概念的基础上,通过View的处理函数框架,Android十分完美的达到消息分发的目的。
参照基本消息系统描述模型,我给出了Android消息系统整体框架,表示如下:
Android消息系统消息分发框架
Looper,Handler详解
Looper只是产生一个消息循环框架,首先Looper创建了消息队列并把它挂接在Linux的线程上下文中,进入到取消息,并分发消息的循环当中。Handler对象在同一个线程上下文中取得消息队列,对消息队列进行封装操作,最主要的就是SendMessage和担当起dispatchMessage这个实际工作。外部系统需要向某个Android线程发送消息,必须通过属于该AndroidThread的Handler这个对象进行。
Handler属于某个线程,取决Handlerd对象在哪个线程中建立。Handler在构建时做了如下的默认动作:
Handler使用消息队列进行对象封装,提供如下的成员函数:
几乎所有的Message发送时,都指定了target。Message.target=(this).
Looper运行在Activity何处?我们现在可以从代码堆栈中纵观一下Looper的位置。
NaiveStart.main()
ZygoteInit.main
ZygoteInit$MethodAndArgsCall.run
Method.Invoke
method.invokeNative
ActivityThread.main()
Looper.loop()
ViewRoot$RootHandler().dispatch()
handleMessage
....
这样我们就更清楚的了解到Looper的运行位置。
基本构架原理
Android的窗口管理是C/S模式的。Android中的Window是表示Top Level等顶级窗口的概念。DecorView是Window的Top-Level View,这个View我称之为主View,DecorView会缺省的attach到Activity的主窗口中。主View被加入到WindowManager中,WM使用WindowState与这个主View对应。
Activity建立一个主窗口后,在将主窗口添加到WindowManager时,首先要建立WindowManager代理对象,并打开一个会话(实现IWindowSession AIDL接口),并维持该会话。Activity将通过该会话与WindowManager建立联系,这个Session是C/S体系的基础,Client通过WindowSession将window加入到Window Manager中。一个完整的窗口概念横跨了View,ViewRoot,WindowManager Service。Window,DecorView,View,IWindow ,ISession,WindowState之间的关系如下:
客户端的Activity通过Session会话与WindowManager建立对话,而WindowManager则通过IWindow接口访问Client,将消息传递到Client端,通过消息分发渠道,将消息传递到处理函数OnXXX。
Client端
我一致认为在Android中Window的概念并不是个很重要的概念。他的Window类,只是在PhoneWindow和MidWindow中使用。而PhoneWindow只是做了一个具体跟手机功能相关的公用事件的处理,所以在Android中PhoneWindow并不是一个抽象的纯正概念,而是一个跟手机系统相关的一个特别窗口概念,例如按键的默认动作处理,按键音的发出等等。
在Activity中真正重要的概念是View,以下是Google官方对View的定义:
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 <em>widgets</em>, which are used to create interactive UI components (buttons, text fields, etc.). The {@link android.view.ViewGroup} subclass is the base class for <em>layouts</em>, which are invisible containers that hold other Views (or other ViewGroups) and define their layout properties.
我对View不做翻译,翻译成视图好像不太佳,View在Android中,View比视图具有广的外延。View包含了用户交互,包含了显示,视图在中文中仅仅表示了静态的显示。对于View的理解应该从最容易的理解开始。我们使用过编辑器,在Android中这个编辑器就是一个View,这个编辑器需要显示文字,需要接收用户的键盘输入和鼠标选择,但是一个屏幕上有多个编辑器,如何管理,如何切换焦点编辑器,这些都是需要管理的。
客户端的组成:(Window,View,ViewRoot,WindowManager Proxy)
在Activity在performLaunchActivity时,会使用Activity.attach()建立一个PhoneWindow主窗口。这个主窗口的建立并不是一个重点。handleResumeActivity真正要启动一个Activity时候,将主窗口加入到WindowManager,当然并不是将主窗口本身,而是将主窗口的DecorView加入到WindowManager中。
真正Window核心的抽象概念存在于View,ViewRoot,WindowManger中的WindowState。为了描述概念的方便性,我特别提出主View这个概念,这个主View就是Top-Level View of the window. 主View与View想对,突出主View是attatch到主窗口上的。而一般的View则是存在于主View中的。主窗口这个概念,我讲的主窗口实际上就是Android提到的Top Level Window。
我们所提到的概念:View,GroupView,DecorView,ViewRoot都是存在于Client端,只有WindowState这个概念存在于Window Manager Service端。
DecorView实际上是一个ViewGroup。在依存关系上来讲,对看个主窗口来讲,DecorView是Top-Level View.View并不是关注的重点,重要的是我们如何需要知道分发路径是建立在什么关系上的。View的成员变量mParent用来管理View上级关系的。而ViewGroup顾名思义就是一组View的管理,于是在ViewGroup构建了焦点管理和子View节点数组。这样通过View的mParent和ViewGroup的mChildren构建了Android中View直接的关系网。
所谓的Foucs Path就是我们的KeyEvent传递的路线。一般的我们的KeyEvent在主循环中主View通过View的焦点记录关系传递到焦点View上。例如下图,View22是焦点,我们从最顶层的View通过mFcous的关系链找到最后所形成的路径就是Focus Path。
ViewRoot与Window Manager的核心是IWindowSession和IWindow。ViewRoot通过IWindowSession添加窗口到Window Manager。而IWindow这是Window Manager分发消息给Client ViewRoot的渠道。利用AIDL接口进行进程间通信。
ViewRoot实际是一个Handler,ViewRoot建立主View与WindowsManger通讯的桥梁。ViewRoot在本质上一个Handler。我们知道Handler的基本功能就是处理回调,发送消息。
Activity在使用getSystemService获取WindowManagerImpl ,建立了一个WindowManagerImpl实例,即Window Manager服务的代理:
wm=(WindowManagerImpl)context.getSystemService(Context.WINDOW_SERVICE);并调用wm.addview添加窗口到WMService中。
这个过程在客户端建立了什么样的管理框架,并如何这个会话?在Window Manager Proxy中建立了View,Layout ,ViewRoot三者的对应关系表。构造一个ViewRoot就会打开一个session,并利用IWindowSession建立会话上下文。
Window Manager Service
本次对于Window Manager Service的研究仅限于FocusWindow,消息系统。其他的部分将在后面的专门章节讨论。
Window Manager管理的窗口是应用程序的Top-level窗口,我这里参照Window的概念叫主窗口。主窗口为什么要放在在Service这边来管理呢?为什么不放在Client那边?主窗口放置在一起管理是为了计算Z-order序列,根据应用程序的状态来显隐应用程序的窗口。我想Android设计者在考虑设计窗口系统的时候,一定首先考虑:
Window Service大体上实现了如下的功能:,
(1)Z-ordered的维护函数
(2)输入法管理
(3)AddWindow/RemoveWindow
(4)Layerout
(5)Token管理,AppToken
(6)活动窗口管理(FocusWindow)
(7)活动应用管理(FocusAPP)
(8)转场动画
(9)系统消息收集线程
(11)系统消息分发线程
在服务端的窗口对象叫做WindowState。在Service维护了一个mWindow数组,这个mWindow就是Window的Z-order序数组。mWindowMap用于记录<Client:Binder,WindowState对象>。
WindowState有一个叫做mClient成员变量来记录客户端IWindow实例,通过IWindow接口实例,Service可以访问客户端的信息,说以IWindow是Service连接View桥梁。
(1) FocusWindow活动窗口如何计算?
基本原理就是查找前景应用(FousActivity),并同Z-Order序中找出属于该FousActivity(AppToken)的主窗口,这个窗口就是计算出来的Focus Window。
(2)为什么要提出Token这个概念呢?
一个应用程序要管理自己的窗口,那么如何来标识该窗口是属于某个Activity,Andoid设计者提出了AppToken这个概念。AppToken在本质上的描述:<Token:IBinder,allWindows>,通过Token找到属于该Token的allWindows。使用Token开始完成该应用程序的所有窗口的显示和隐藏。
(3)系统消息收集与处理
我们下面重点研究Service中的系统消息收集模式及其分发模式。Service使用KeyQ作为专门的消息队列。
系统有两个线程:
KeyQ线程,通过Navite函数readEvent轮询设备,将读取的结果放置在KeyQ队列中。
系统dispatcher 等待在KeyQ消息队列上,一旦从消息队列中获取到消息,就通过分发函数通过mClient传递到Client端。