首页 > 代码库 > Android 4.4(KitKat)窗口管理子系统 - 体系框架
Android 4.4(KitKat)窗口管理子系统 - 体系框架
窗口管理系统是Android中的主要子系统之一,它涉及到App中组件的管理,系统和应用窗口的管理和绘制等工作。由于其涉及模块众多,且与用户体验密切相关,所以它也是Android当中最为复杂的子系统之一。一个App从启动到主窗口显示出来,需要App,ActivityManagerService(AMS),WindowManagerService(WMS),SurfaceFlinger(SF)等几个模块相互合作。App负责业务逻辑,绘制自己的视图;AMS管理组件、进程信息和Activity的堆栈及状态等等;WMS管理Activity对应的窗口及子窗口,还有系统窗口等;SF用于管理图形缓冲区,将App绘制的东西合成渲染在屏幕上。下面分几个部分进行分析。
窗口管理系统的主要框架及各模块之间的通讯接口大体如下:
基于Binder的本地过程调用(LPC)让Android的模块间耦合度更低,结构更加清晰。每个模块各司其职,并向其它模块提供接口。进程和uid这些Linux中的机制对这些模块提供了天然的保护,使得系统更加鲁棒。模块之间经常使用C/S的结构,而Service本身也可能是使用其它Service的Client。举例来说,如果Service的实现叫XXXManagerService,那一般它对Client提供接口IXXXManager,然后Client要用Service的时候便会申请一个叫BpXXXManager的代理对象,它是远端BnXXXManager本地对象在Client端的代理。代理对象BpXXManager实现了IXXXManager的所有接口,只不过里面的函数都是壳子,只负责参数的准备,然后就调用远端对象去执行。远端的的BnXXXManager对象及其继承类是真正做事的,BnXXXManager继承自IXXXManager.Stub抽象类,实现了IXXXManager接口。Stub就如其名字一样,是BnXXXManager的继承类在BnXXXManager中的“钩子”。通过调用这些接口便可以调用到远端的Service功能了。概念上类似远程gdb调试,host机上的gdb和guest上的gdbserver相连以后,在host上敲命令会让gdbserver去执行,但感觉就像是在host本地执行一样。这儿的gdbserver就提供了类似于Stub的功能。
这种远程调用模型的建立过程一般是分层次的。比如WindowManagerGlobal会与WMS进行连接,ViewRootImpl会与WMS中的Session进行连接,高层先与高层通信,同时帮助建立低层间的通信,然后低层与低层直接通信。打个比方,张三是A部门的员工,他想要和B部门合作搞一个活动,他一般不会直接冲过去B部门挨个问的。所以他先和自己的主管李四说,我要和B部门合作,于是李四找到B部门的主管王五,说你出个人吧。于是王五和赵六说,你负责这事儿吧,并告诉了A部门主管李四。李四再告诉下属张三,赵六是B部门接口人,你以后直接和他联系吧。于是张三和赵六以后就直接联系了。如果合作中有需要超越自己权限的操作,他们再向各自的主管申请。比如App与WMS的连接,首先会建立一个Session到WMS,之后就会通过IWindowSession接口与WMS中的Session直接通信。还有例如WMS和SF先创建SurfaceSession,其中会创建SurfaceComposerClient,访问SurfaceComposerClient时会在SF中创建Client与之对应,这个Client实现了ISurfaceComposerClient接口,之后SurfaceComposerClient会通过该接口与SF中的Client直接通信。
看代码过程中,各个对象间的数量及对应关系经常让人混淆,下面列举了在一般情况下各对象之间的实体关系图。其中标色的是相应子系统中比较基础核心的类。
要注意的几点:1. App中可以没有Activity,也可以没有PhoneWindow和DecorView,比如一个显示浮动窗口的Service。2. Task中的Activity可以来自不同进程,比如App运行过程中打开相机App拍照。3. WindowState代表WMS中的一个窗口,这和App端的Window类是不一样的,尽管很多时候一个Window类(即PhoneWindow)有一个对应的WindowState,但那不是绝对的。一个Activity在WMS中有对应的AppWindowToken,一个AppWindowToken又可以包含多个WindowState,因为除了主窗口外,还可能有子窗口和启动窗口。此外对于系统窗口,WindowState还可能不对应AppWindowToken。4.这里的Application指的是App端的一个进程,它不同于AndroidManifest.xml中的<application>标签。后者是配置文件中对组件的管理者,它和进程之间没有本质关系,通过android:process标签可以让同一个<application>下的组件跑在多个进程,也可以让多个<application>中的组件跑在同一个进程。所以如果是<application>定义的Application的话和ProcessRecord就是m:n的关系了。以下谈到Application都是指一个App的进程。
首先分析下App端的结构。移动平台一般显示区域有限,要完成一个工作往往不是一屏内容中能搞定的,所以Android中有了Activity的概念,让用户可以把相关的子内容放到单独的Activity中,然后通过Intent在Activity间跳转。类似于浏览网页,点击链接跳转到另一个网页。这些同一交互过程中的一系列Activity成为一个Task。这些Activity运行在主线程ActivityThread中。Activity要展现出来的主视图是DecorView,它是一棵视图树。ViewRootImpl负责管理这个视图树和与WMS交互,与WMS交互通过WindowManagerImpl和WindowManagerGlobal。DecorView被包含在系统的通用窗口抽象类Window当中。视图对应的图形缓冲区由Surface管理。其中涉及到的主要的类包括下面几个:
Activity:描述一个Activity,它是与用户交互的基本单元。
ActivityThread:每一个App进程有一个主线程,它由ActivityThread描述。它负责这个App进程中各个Activity的调度和执行,以及响应AMS的操作请求等。
ApplicationThread:AMS和Activity通过它进行通信。对于AMS而言,ApplicationThread代表了App的主线程。简而言之,它是AMS与ActivityThread进行交互的接口。注意ActivityThread和ApplicationThread之间的关系并不像Activity与Application。后者的关系是Application中包含了多个Activity,而前者ActivityThread和ApplicationThread是同一个东西的两种"View",ApplicationThread是在AMS眼中的ActivityThread。
ViewRootImpl:主要责任包括创建Surface,和WMS的交互和App端的UI布局和渲染。同时负责把一些事件发往Activity以便Activity可以截获事件。每一个添加到WMS中的窗口对应一个ViewRootImpl,通过WindowManagerGlobal向WMS添加窗口时创建。大多数情况下,它管理Activity顶层视图DecorView。总得来说,它相当于MVC模型中的Controller。
ViewRootImpl::W:用于向WMS提供接口,让WMS控制App端的窗口。它可看作是个代理,很多时候会调用ViewRootImpl中的功能。这种内嵌类的用法很多,特别是这种提供接口的代理类,如PhoneWindow::DecorView等。
Instrumentation:官方提供的Hook,主要用于测试。如果只关注窗口管理流程的话可以先无视。
WindowManagerImpl:Activity中与窗口管理系统通信的代理类,实现类是WindowManagerGlobal。WindowManagerGlobal是App中全局的窗口管理模块,因此是个Singleton。其中管理着该App中的ViewRootImpl,DecorView等结构,以有两个Activity的App为例:
Window:每个App虽然都可以做得各不相同,但是作为有大量用户交互的系统,窗口之间必须要有统一的交互模式,这样才能减小用户的学习成本。这些共性比如title, action bar的显示和通用按键的处理等等。Window类就抽象了这些共性。另外,它定义了一组Callback,Activity通过实现这些Callback被调用来处理事件。注意要和在WMS中的窗口区分开来,WMS中的窗口更像是App端的View。
PhoneWindow:PhoneWindow是Window类的唯一实现,至少目前是。这样的设计下如果要加其它平台的Window类型更加方便。每个Activity会有一个PhoneWindow,在attach到ActivityThread时创建,保存在mWindow成员中。
Context:运行上下文,Activity和Service本质上都是一个Context,Context包含了它们作为运行实体的共性,如启动Activity,绑定Service,处理Broadcast和Receiver等等。注意Application也会有Context。Activity的Context是对应Activity的,Activity被杀掉(比如转屏后)后就变了。所以要注意如果有生命周期很长的对象有对Activity的Context的引用的话,转屏、返回这种会引起Activity销毁的操作都会引起内存泄露。而Application的Context生命周期是和App进程一致的。关于Context的类结构图有下面的形式。Context是抽象类,定义了接口。ContextImpl是Context的实现类,包含了实现。而ContextWrapper是Context的包装类,它把请求delegate给其中的ContextImpl类去完成。ContextThemeWrapper是ContextWrapper的装饰类,它在ContextWrapper的基础上提供了自定义的主题。这结构初看有点乱,但结合下面的Decorator模式就一目了然了。
Surface:这是在App端管理图形缓冲区的类,其中最重要的是图形缓冲区队列。经由WMS从SF中得到IGraphicBufferProducer接口对象BufferQueue后,Surface便可以从该队列中queue和dequeue图形缓冲区。SurfaceControl在WMS中封装了Surface以及与SF的交互。Canvas和Surface从字面意思上很像,但前者其实更确切地说不是“画布”,而是“画家”。Surface中的图形缓冲区才是App的画布。
上面这些基本类之间的主要关系如下:
其中比较重要的三个类的PhoneWindow,DecorView和ViewRootImpl。PhoneWindow和ViewRootImpl都包含了mDecor成员,它类型为DecorView,描述了Activity的根视图。它也是ViewRootimpl和PhoneWindow间的枢纽。类似的,PhoneWindow父类中的Callback是PhoneWindow与Activity的枢纽,而ViewRootImpl::W是ViewRootImpl和WMS间的枢纽。DecorView依次继承自FrameLayout,ViewGroup和View,因此它本质上是一个View,只是它是Activity最根部的View,它下面可能有很多Subview。DecorView描述App窗口视图,而它的更新是由ViewRootImpl来控制的。粗糙点说的话,如果用MVC模型来说的话,前者是View,后者是Controller。ViewRootImpl中的内嵌类W就是给WMS通信的接口。W的声明中有两个成员变量:mViewAncestor和mWindowSession,它一头连着App端的ViewRootImpl,一头连着WMS中的Session,且实现了IWindow的接口。意味着它是App和WMS的桥梁,是WMS用来回调App端,让ViewRootImpl做事用的。举例来说,dispatchAppVisibility()的流程就是经过它来完成的:WMS ->ViewRootImpl::W->ViewRootHandler->handleAppVisibility()->scheduleTraversals()。
Activity创建完后需要attach到主线程上。在attach()过程中会创建Window(实际是PhoneWindow),然后把PhoneWindow中的mCallback设为Activity。在PhoneWindow中两个关键内嵌类Callback和DecorView,Callback连接了Activity,DecorView连接了ViewRootImpl。这样,当ViewRootImpl有事件传来时,便可以沿着ViewRootImpl->DecorView->Window.Callback->Activity这条路来通知Activity。如按键事件就是通过这条路来传达的。
Android里还可以找到很多这种使用内嵌类来实现远端代理的例子,这种设计使得系统满足最小隔离原则,Client端该用到哪些接口就暴露哪些接口。注意这种类的函数都是跑在Binder线程中的,所以其中不能调用非线程安全的函数,也不能直接操作UI控件,所以一般都是往主线程消息队列里丢一个消息让其异步执行。
接下来,看下一个Activity的启动过程。以Launcher中启动一个App为例,比如在Launcher中我们点了一个图标启动一个App的Activity,Launcher里会执行:
Intent intent = new Intent("xxx");
startActivity(intent);
接下来的大体流程如下:
图比较大,抓大放小,看清里面的主要脉络即可,App与AMS交互流程主要分以下几步:
1. 原App通知AMS要起一个新Activity。
2. AMS创建相应数据结构,然后通知WMS创建相应数据结构,再通知原Activity暂停。
3. 原Activity暂停后通知AMS。
4. AMS创建新App进程,通知WMS新App可见,再通知App创建Activity等相应数据结构。
流程上我们可以总结出模块间的异步工作模式:当一个模块要求另一个模块做特定任务时,一般是先调用目标模块的scheduleXXX(),这时目标模块的Binder线程只是向主线程发起一个异步请求,然后对方主线程在消息队列中被唤醒处理,执行处理函数handleXXX()。另外我们也注意到很多函数都是带有Locked后缀。这说明出来混,一定要申明自己是不是线程安全的。
为了使系统中的策略更加灵活,容易替换,系统使用了一些设计模式将之从其它逻辑中解耦。如IPolicy是一个工厂类接口,Policy为它的具体实现类。它负责创建一系列策略相关的对象,如makeNewWindow()创建PhoneWindow等。同时PolicyManager还使用了Strategy模式将Policy包装起来,这为策略的替换提供了便利,也使运行时更换策略成为可能。
虽然目前为止貌似只有一种针对“Phone”的策略,所以还没看到这样设计的好处。但是,一方面,也许将来在多合一的移动设备上,笔记本,平板什么的可以切换,那么Policy自然也需要动态切换。Android里还有很多把这种Policy单独拎出来的例子,如WindowManagerPolicy类。另一方面,Android作为一个框架,需要让各个厂商把Android用到自己的平台上更加容易适配。将来如果作为眼镜,车载,智能家电等等嵌入式设备的统一平台,如果将Policy与其它模块紧耦合,那这些个平台上的代码就会差异越来越大,越来越难维护。
下面以类图的方式具体看下各模块之间的通信关系:
图中App和AMS的交互通过Binder,使用了代理模式。从App调用AMS是通过ActivityManagerProxy代理对象,它是本地对象ActivityManagerNative在App端的代理,实现了IActivityManager接口,提供了startActivity()这样的AMS服务函数。而ActivityManagerNative的实现其实就是AMS本身。而从AMS调用App端用的是ApplicationThreadProxy代理对象,它实现了IApplicationThread接口,其对应的实现是ApplicationThreadNative本地对象,存在于App端,ApplicationThread是其实现类。AMS可以通过它来向App发出如scheduleXXX这些个异步消息。
Activity在AMS中的对应物是ActivityRecord,在WMS中对应物为AppWindowToken。ActivityRecord::Token可以看作ActivityRecord的一个远端句柄,在WMS和App中分别存于AppWindowToken和ActivityClientRecord之中。ActivityThread中的mActivities存放了远程ActivityRecord::Token到本地ActivityClientRecord的映射,由于这个Token是全局唯一的,所以还可以用来作为HashMap的Key。ActivityRecord::Token实现了IApplicationToken。当WMS要通知AMS窗口变化时,就是用的这个接口。
新启动的Activity的创建初始化主要是在handleLaunchActivity()中完成的。handleLaunchActivity()的作用是加载指定的Activity并运行。这其中在App端主要是创建ActivityThread,Activity,PhoneWindow,DecorView等对象,并调用Activity生命周期中的onCreate(),onResume()函数执行用户逻辑。正常点的App里onCreate里会调用setContentView设置主视图。setContentView()里主要是调用了installDecor(),其中会设置窗口的通用元素,如title, action bar之类,还会把xml文件inflate成布局对象。
可以看到这时创建了DecorView,这便是Activity的主窗口的顶层视图。DecorView创建好后,handleResumeActivity()中会将它添加到WMS中去。当App向WMS添加窗口时,会调用WindowManagerImpl的addView()。注意WindowManagerImpl中的addView()函数和ViewGroup里的addView()函数完全不一样,后者是将View加入到本地的View hierarchy中去,和WMS没有关系。addView()的流程如下:
首先通过WindowManagerImpl的addView()会创建对应的ViewRootImpl,然后ViewRootImpl申请WMS得到Session(如果是App第一个Activity会新建),其接口为IWindowSession,然后ViewRootImpl通过addToDisplay()把自己的ViewRootImpl::W(实现了IWindow接口)注册给WMS。这样,双方都有了对方的接口,WMS中的Session注册到WindowManagerGlobal的成员WindowSession中,ViewRootImpl:W注册到WindowState中的成员mClient中。前者是为了App改变View结构时请求WMS为其更新布局。后者代表了App端的一个添加到WMS中的View,每一个像这样通过WindowManager接口中addView()添加的窗口都有一个对应的ViewRootImpl,也有一个相应的ViewRootImpl::W。它可以理解为是ViewRootImpl中暴露给WMS的接口,这样WMS可以通过这个接口和App端通信。Session建立好后,接下来就是通过ViewRootImpl的setView将ViewRootImpl中的W注册到WMS中,WMS会创建相应数据结构,并将其插入内部维护的窗口堆栈,还会与SF建立Session以备将来为之创建Surface。addView()执行完后这个Activity的主视图就正式对WMS可见了。总结来说,addView()的工作主要包括创建ViewRootImpl,和远程WMS建立Session,并将当前视图注册到WMS这几步。可以看到App端通过WindowManagerGlobal调用addView(),调用链到WMS就变成addWindow(),概念发生了改变,这也印证上面提到的App端和WMS端的Window概念不一样的说法。
从以上Activity启动的整个流程可以看到,窗口的添加和管理需要AMS和WMS两个Service的配合。下面看看AMS与WMS的主要作用和结构。
AMS(ActivityManagerService)
Activity的管理者。其实除了Activity,AMS也管Service等组件信息,另外AMS还管理Process信息。下面是AMS几个关键数据结构:
ActivityRecord:描述单个Activity,Activity堆栈中的基本单元。
ActivityRecord::Token:对应ActivityRecord的IBinder对象,可以看作远程对象的本地句柄。可用于LPC,又可用来作映射中的unique ID,经常是两用的。
ProcessRecord:描述一个App进程,包含了该进程中的Activity和Service列表。
TaskRecord:TaskRecord中的mActivities是ActivityRecord的列表,它们是按照历史顺序排序的。
ActivityStack:Activity堆栈,其中的ActivityRecord是通过TaskRecord这一层间接地被管理着。
ActivityStackSupervisor:ActivityStackSupervisor是ActivityStack的总管。4.4中默认引入了两个ActivityStack,一个叫Home stack,放Launcher和systemui,id为0;另一个是Applicationstack,放App的Activity,id可能是任意值。定义如下:
137 /** The stack containing the launcher app*/ 138 private ActivityStack mHomeStack; 145 /** All the non-launcher stacks */ 146 private ArrayList<ActivityStack> mStacks = new ArrayList<ActivityStack>();
系统中的Activity堆栈信息可以通过dumpsys activity命令查看:
$ adb shell am stackboxes Box id=3 weight=0.0vertical=false bounds=[0,0][1280,736] Stack= Stack id=3 bounds=[0,0][1280,736] taskId=6:com.example.android.apis/com.example.android.apis.ApiDemos taskId=7:com.android.camera/com.android.camera.Camera Box id=0 weight=0.0vertical=false bounds=[0,0][1280,736] Stack= Stack id=0 bounds=[0,0][1280,736] taskId=3:com.android.launcher/com.android.launcher2.Launcher
从dump信息可以看出,这大致是一个层级结构,从上到下依次是Stack->Task->Activity的结构。Stack放在ActivityStackSupervisor中的mStacks。它是一个列表,元素类型为ActivityStack。因此,基本元素ActivityRecord是以层级的结构被AMS管理起来的:
为什么引入了Task的概念呢?首先看下Task的官方定义“A task (from the activity that started it to the next task activity)defines an atomic group of activities that the user can move to.”。Task是为了完成一个功能的一系列相关的有序Activity集合,可以理解为用户与App之间对于特定功能的一次会话。一个Task中的Activity可以来自不同的App,比如在邮件App中需要看图片附件,然后会开imageview的Activity来显示它。根据不用的业务逻辑,我们会在启动Activity的Intent中设不同的FLAG(FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_MULTIPLE_TASK等),这些FLAG的处理中会对Task的处理,进而对Activity的调度产生影响。具体的FLAG可参见/frameworks/base/core/java/android/content/Intent.java。关于Task的官方介绍http://developer.android.com/guide/components/tasks-and-back-stack.html。
总得来说,上面这些个数据结构之间的关系如下
WMS(WindowManagerService)
窗口的管理者。与AMS不同,一些高层的App中的概念,如进程等,WMS是不care的。因为WMS只对窗口进行管理,哪个进程的它不关心。像Activity这些概念在WMS仍然有,因为Activity对窗口的管理会产生影响。
WMS主要责任是维护窗口堆栈,计算每个窗口的layer信息交给SF,替App申请和调整绘图Surface,当窗口显示状态变化了还要通知其它模块,另外还要处理系统输入。所以说,WMS可能是与其它模块交互最多的模块之一了。它与AMS,App,SF及Input等模块都交集。说到窗口管理,首先看一下Android中有哪些窗口。Android中大体有以下几种窗口类型:1.应用窗口,一般来说就是Activity的主窗口。但也有些情况App没有Activity,直接把自定义的View添加到WMS中,比如浮动窗口。2.子窗口,需要有一个父窗口,如Context Menu,Option Menu,Popup Window和Dialog等。3.系统窗口,如状态栏,锁屏窗口,输入法窗口,壁纸窗口和Toast之流,由系统创建的,不依赖于父窗口。
WMS中涉及到的主要数据结构有这么几个:
WindowState:WMS中最基本的元素,描述WMS中的一个窗口。它既可以是由App添加过来的View,也可以是系统创建的系统窗口。mAttrs为WindowManager.LayoutParams类型,描述布局参数。mClient为IWindow类型,也就是App端的ViewRootImpl::W。为了查找方便,WMS中的mWindowMap保存了IWindow到WindowState的映射,mTokenMap保存了IApplicationToken到WindowToken的映射。
Session:向App提供IWindowSession接口让其可以和WMS通信。每个App在WMS有一个Session对象,App就是通过这个Session来向WMS发出窗口管理申请的。命令dumpsys window sessions可以查看系统中的Session:
WINDOW MANAGERSESSIONS (dumpsys window sessions) Session Session{b32d7d68 1404:u0a10008}: mNumWindow=1 mClientDead=falsemSurfaceSession=android.view.SurfaceSession@b31adc20 Session Session{b32dd278 1326:u0a10007}: mNumWindow=5 mClientDead=falsemSurfaceSession=android.view.SurfaceSession@b327b348 Session Session{b3290f68 1275:1000}: mNumWindow=1 mClientDead=falsemSurfaceSession=android.view.SurfaceSession@b30a3890
SurfaceSession:WMS和SF之间的会话。每个App会在WMS中有一个对应的SurfaceSession,也会有一个相应的SurfaceComposerClient。用于向SF申请和设置图形缓冲区等。
WindowToken: 描述WM中一组相关的窗口,这些Window对应的WindowState放在其成员变量windows里。其主要继承类AppWindowToken,它是针对App的WindowToken结构。WindowState中的mAppToken指向所属的AppWindowToken,如果是系统窗口,mAppToken为空,mToken指向WindowToken对象。
命令dumpsys window tokens用于查看WindowToken和AppWindowToken信息:
WINDOW MANAGERTOKENS (dumpsys window tokens) All tokens: WindowToken{b32921e8 null}: windows=[Window{b333cd40 u0 SearchPanel},Window{b328d920 u0 Keyguard}, Window{b32961d8 u0 NavigationBar},Window{b32c6aa0 u0 StatusBar}, Window{b3292288 u0 KeyguardScrim}] windowType=-1 hidden=false hasVisible=true WindowToken{b3260980android.os.Binder@b3269d60}: windows=[Window{b325c180 u0com.android.systemui.ImageWallpaper}] windowType=2013 hidden=falsehasVisible=true AppWindowToken{b322a358 token=Token{b3287ea0ActivityRecord{b3287c28 u0 com.android.launcher/com.android.launcher2.Launchert1}}}: windows=[Window{b328b0c0 u0com.android.launcher/com.android.launcher2.Launcher}] windowType=2 hidden=false hasVisible=true app=true allAppWindows=[Window{b328b0c0 u0com.android.launcher/com.android.launcher2.Launcher}] groupId=1 appFullscreen=truerequestedOrientation=-1 hiddenRequested=false clientHidden=falsewillBeHidden=false reportedDrawn=true reportedVisible=true numInterestingWindows=1 numDrawnWindows=1inPendingTransaction=false allDrawn=true (animator=true) startingData=http://www.mamicode.com/null removed=falsefirstWindowDrawn=true>AppWindowToken:每个App的Activity对应一个AppWindowToken。其中的appToken为IApplicationToken类型,连接着对应的AMS中的ActivityRecord::Token对象,有了它就可以顺着AppWindowToken找到AMS中相应的ActivityRecord。其中allAppWindows是一个无序的列表,包含该Activity中所有的窗口。用dumpsys window display可以查看z-ordered的AppWindowToken列表:
Application tokens in Z order: App #4 AppWindowToken{b31c2128token=Token{b3235c98 ActivityRecord{b324c8a0 u0com.example.android.apis/.view.PopupMenu1 t9}}}: windows=[Window{b32a5eb8 u0com.example.android.apis/com.example.android.apis.view.PopupMenu1}] windowType=2 hidden=false hasVisible=true app=true allAppWindows=[Window{b32a5eb8 u0com.example.android.apis/com.example.android.apis.view.PopupMenu1},Window{b32eb6f0 u0 PopupWindow:b2ff5368}] groupId=9 appFullscreen=truerequestedOrientation=-1 hiddenRequested=false clientHidden=falsewillBeHidden=false reportedDrawn=true reportedVisible=true numInterestingWindows=2 numDrawnWindows=2inPendingTransaction=false allDrawn=true (animator=true) startingData=http://www.mamicode.com/null removed=falsefirstWindowDrawn=true>注意AppWindowToken是对应Activity的,WindowState是对应窗口的。所以AppWindowToken和WindowState是1:n的关系。举例来说,上面第一项AppWindowToken,它包含了PopupWindow子窗口,所以有对应两个WindowState。一般地说,一个Activity可能包含多个窗口,如启动窗口,PopupWindow等,这些窗口在WMS就会组织在一个AppWindowToken中。AppWindowToken及WindowState间的从属结构及WindowState间的父子结构可以通过以下成员表示。
Task:上面提到AppWindowToken保存了属于它的WindowState的有序列表,而它本身也作为一个列表被管理在TaskStack中的mTasks成员中,并且是按历史顺序存放的,最老的Task在最底下。结合前面的AppWindowToken和WindowState之间的关系,可以了解到它们是以这样一个层级的关系组织起来的:
这个结构是不是很眼熟,AMS里也有类似的结构。WMS里的TaskStack,对应前面AMS中的ActivityStack,这两者及其子结构会保持同步。从中我们可以发现,WMS和AMS中的数据结构是有对应关系的,如AMS中的TaskRecord和WMS中的Task,AMS中的ActivityRecord和WMS中的AppWindowToken。另外WMS中TaskStack的mTasks需要和AMS中ActivityStack的mTaskHistory顺序保持一致。
DisplayContent:表示一个显示设备上的内容,这个显示设备可以是外接显示屏,也可以是虚拟显示屏。其中mWindows是一个WindowState的有序(Z-ordered,底部最先)列表。mStackBoxes包含了若干个StackBox,其中一个为HomeStack,另一个是App的StackBox。所有的StackBox被组织成二叉树,StackBox是其中的节点,其中有三个重要成员变量,mFirst和mSecond指向左和右子结点(也是StackBox),StackBox的成员mStack才是我们真正关心的东西-TaskStack。可以看到,为了要把TaskStack存成树的结构,需要一个容器,这个容器就是StackBox。DisplayContent,StackBox和TaskStack的关系如下:
StackBox信息可以用am stack boxes或dumpsys window displays命令查看:
$ adb shell am stackboxes WARNING: linker:libdvm.so has text relocations. This is wasting memory and is a security risk.Please fix. Box id=2 weight=0.0vertical=false bounds=[0,33][800,1216] Stack= Stack id=2 bounds=[0,33][800,1216] taskId=3:com.android.contacts/com.android.contacts.activities.PeopleActivity Box id=0 weight=0.0vertical=false bounds=[0,33][800,1216] Stack= Stack id=0 bounds=[0,33][800,1216] taskId=1:com.android.launcher/com.android.launcher2.Launcher mStackId=2 {taskId=3appTokens=[AppWindowToken{b3332498 token=Token{b33006c0 ActivityRecord{b32ecbb0u0 com.android.contacts/.activities.PeopleActivity t3}}}]}上面是正常情况下的,用am stack create命令可以创建分屏窗口(详见http://androidinternalsblog.blogspot.com/2014/03/split-screens-in-android-exist.html),如:
$ adb shell am stackcreate 7 3 0 0.5 createStack returnednew stackId=4然后再查看stack信息就变成了这样:
$ adb shell am stackboxes Box id=3 weight=0.5vertical=false bounds=[0,0][1280,736] First child= Box id=4 weight=0.0 vertical=falsebounds=[0,0][640,736] Stack= Stack id=4 bounds=[0,0][640,736] taskId=7:com.android.camera/com.android.camera.Camera Second child= Box id=5 weight=0.0 vertical=falsebounds=[640,0][1280,736] Stack= Stack id=3 bounds=[640,0][1280,736] taskId=6:com.example.android.apis/com.example.android.apis.ApiDemos Box id=0 weight=0.0vertical=false bounds=[0,0][1280,736] Stack= Stack id=0 bounds=[0,0][1280,736] taskId=3:com.android.launcher/com.android.launcher2.Launcher可见,在分屏情况下,这个结构以二叉树的形式分裂,形成这样的结构:
除了TaskStack,DisplayContent中的成员mTaskHistory也包含了一个有序的Task列表。结合上面几个概念,可以得到下面的关系:
逻辑上,一个Task(Task)可以包含多个Activity(对应AppWindowToken),每个Activity可以包含多个窗口(对应WindowState),而每个窗口都是可能被放在任意一个显示屏上的(想象一个Windows操作系统中外接显示器的情况),因此就有了上面这个结构。从这个结构可以看出,WMS的主要任务之一就是维护各窗口的Z-order信息。Z轴可看作是屏幕法向量方向上的坐标轴,值越大的层意味着离用户越近,会把值小的窗口给盖住。一方面,WMS需要知道各窗口的遮挡关系来做layout和分配释放Surface,另一方面,这个Z-order信息会转化为窗口对应Surface的layer属性输出到SF,指导SF的渲染。
那么,这个列表是怎么管理的呢?我们知道,每个窗口在WMS都有对应的WindowState,因此,本质上我们需要维护一个Z-order排序的WindowState列表。首先,TaskStack中包含了历史序的Task,每个Task又包含了Z-ordered的AppWindowToken,AppWindowToken的成员windows又包含了一个Z-ordered的WindowState列表。前面提到过,一个AppWindowToken对应AMS中的一个ActivityRecord,因此这个列表包含了这个Activity中的所有窗口,子窗口,开始窗口等。另一方面,DisplayContent中也有一个成员windows,指向一个Z-ordered的WindowState列表,它描述的是单个显示屏上的窗口集合。在添加窗口时会通过addAppWindowToListLocked()函数往这个窗口堆栈插入元素。由于插入过程要考虑子窗口,开始窗口等的偏移量,往DisplayContent的windows插入元素时需考虑WindowToken中的windows列表。得到DisplayContent中的windows列表后,之后会调用assignLayersLocked()来根据这个Z-order列表信息得到每个窗口的layer值。WindowState的列表可以用dumpsys window windows命令查看:
WINDOW MANAGERWINDOWS (dumpsys window windows) Window #7 Window{b32b2110 u0 SearchPanel}: mDisplayId=0 mSession=Session{b32369b81326:u0a10007} mClient=android.os.BinderProxy@b3222788 mOwnerUid=10007 mShowToOwnerOnly=falsepackage=com.android.systemui appop=NONE mAttrs=WM.LayoutParams{(0,0)(fillxfill)gr=#800053 sim=#31 ty=2024 fl=#1820100 fmt=-3 wanim=0x10301f5} Requested w=800 h=1216 mLayoutSeq=37 mHasSurface=falsemShownFrame=[0.0,0.0][0.0,0.0] isReadyForDisplay()=false WindowStateAnimator{b32f06d8 SearchPanel}: mShownAlpha=0.0 mAlpha=1.0 mLastAlpha=0.0 ... Window #2 Window{b3282e18 u0com.example.android.apis/com.example.android.apis.ApiDemos}: mDisplayId=0 mSession=Session{b32cfba03137:u0a10045} mClient=android.os.BinderProxy@b31faaf8 mOwnerUid=10045 mShowToOwnerOnly=truepackage=com.example.android.apis appop=NONE mAttrs=WM.LayoutParams{(0,0)(fillxfill)sim=#110 ty=1 fl=#1810100 pfl=0x8 wanim=0x10302f5} Requested w=800 h=1216 mLayoutSeq=82 mHasSurface=truemShownFrame=[0.0,0.0][800.0,1216.0] isReadyForDisplay()=true WindowStateAnimator{b32df438com.example.android.apis/com.example.android.apis.ApiDemos}: Surface: shown=true layer=21010 alpha=1.0rect=(0.0,0.0) 800.0 x 1216.0 ...总结一下,WMS服务端的类结构图:
设计中比较灵活的一个地方是其中的WindowManagerPolicy这个类,它采用了Strategy模式,将策略相关的部分抽象出来,用PhoneWindowManager实现。它也是前面提到的Policy工厂模式的一部分。虽然现在PhoneWindowManager是唯一的继承类,这种结构看似可有可无,但如果以后厂商要加其它的策略,或更改已有策略,这种设计就能够提供良好的灵活性。
以上就是AMS和WMS的大体框架。粗糙地说,AMS管理Activity,Service和Process等信息,WMS管理应用和系统窗口。这两者既有联系又有很大不同,Activity一般有窗口,Service可以有也可以没有窗口,而窗口不一定非要对应Activity或者Service。只是很多时候一个Activity中就一个顶层视图,对应WMS一个窗口,所以会给人一一对应的错觉,但这两者其实没有直接关系。这就造就了AMS,WMS两大模块,虽然其中数据结构有很多是保持同步的,但是设计上让它们分开,各司其职,异步工作。从窗口管理的流程来说,App负责视图树的管理和业务逻辑。AMS管理和调度所有App中的组件,通知WMS组件的状态信息。WMS帮App到SF申请和调整Surface,同时计算维护窗口的布局,z-order等信息,另外当显示状态发生变化时,WMS还要通知App作出调整。SF从WMS拿到各窗口对应Surface的属性和layer信息,同时从App拿到渲染好的图形缓冲区,进行进一步的合并渲染,放入framebuffer,最后用户就能在屏幕上看到App的窗口了。