WindowManagerService
1.概述
1.1 窗口的定义
“窗口”是一种通用的描述,指一个独立的界面,比如一个对话框窗口,一个Activity交互的窗口,一个菜单窗口等;
“Window”是一个类,其实现类是PhoneWindow类,Activity类实现了Window.Callback接口,从而成了具有通用操作方式的窗口,所谓的通用操作方式包括,当用户按下Menu键后会弹出一个菜单,按Back键会退出当前Activity等。
View也是一个类,通常翻译为视图,只一个独立的交互元素,比如一个按钮,一个文本框等。
在WMS中,窗口由两个部分构成。一部分是描述该窗口的类WindowState,另一部分是该窗口在屏幕上对应的界面Surface。Surface仅仅用于在屏幕上画一点界面,而负责将用户输入的触摸消息及按键消息派发到正在交互的窗口则与Surface一点关系都没有。
1.2 几个操作的概念
在WMS的内部逻辑中,有三个常见的操作,分别为assign layer , perform layout以及place surface。
assign layer的语义是,为窗口分配层值。在WMS中,每个窗口使用WindowState类来描述,而窗口在界面上显示时,需要指定窗口的层值。从用户的角度看,层值越大,其窗口越靠近用户,窗口之间的层叠正式按照层值进行的。
perform layout的语义是,计算窗口的大小。每个窗口对象都必须有一个大小,即窗口大小,perform layout将根据状态栏大小、输入法窗口的状态,窗口动画状态计算该窗口的大小。
place surface的语义是,调整surface对象的属性,并将其重新显示在屏幕上。由于assign layout和perform layout的执行结果影响的仅仅是WindowState中的参数,而能够显示到屏幕上的窗口都包含一个Surface对象,因此只有将以上执行结果中的窗口层值,大小设置到Surface对象中,屏幕上才能看到该窗口的变化。place surface的过程就是将这些值赋值给Surface对象,并告诉Surface Flinger服务重新显示这些Surface对象。
1.3 WMS的接口结构
WMS的接口结构是指WMS功能模块与其他功能模块之间的交互接口,其中主要包括与AMS模块及应用程序客户端的接口。交互过程如下:
1.应用程序在Activity中添加,删除窗口。具体实现就是通过调用WindowManager类的addView()和removeView()函数完成,这会转而调用ViewRoot类的相关方法,然后通过IPC调用到WMS中的相关方法完成添加,删除过程。
2.当AMS通知ActivityThread销毁某个Activity时,ActivityThread会直接调用WindowManager中的removeView()方法删除窗口。
3.AMS中直接调用WMS,这种调用一般都不是请求请求WMS创建或者删除窗口,而是告诉WMS一些其他信息。比如某个新的Activity的就要启动了,从而WMS会保存一个该Activity记录的引用。
而在WMS的内部,则全权接管了输入消息的处理和屏幕的绘制。其中输入消息的处理是借助于InputManager类完成的,而绘制屏幕则是借助于SurfaceFlinger完成的,SurfaceFlinger是Linux的一个驱动,它内部会使用芯片的图形加速引擎完成完成对界面的绘制。
2.WMS的主要内部类
WindowState: 用来描述一个窗口
WindowToken: 描述的是窗口的token对应的属性
AppWindowToken: 当一个窗口对应一个Activity时,该窗口同时对应一个AppWindowToken对象
InputMonitor: 当底层InputDispatcher线程接收到消息之后,首先会回调InputManager的一个Callback对象的相应函数,而这些函数内部大多数是调用WMS类中的InputMonitor对象的相应函数,然后对窗口信息进行调整。InputWindow类是在输入消息传递中用来保存窗口信息的类,与WindowState的区别是,前者仅仅保存了一些为了寻找焦点窗口所需的各种信息,而WindowSate则保存了一个窗口中的所有信息,以及窗口应有的各种函数。
PolicyThread: 用来包装WindowManagerPolicy,因为Policy中有些函数需要异步执行。PolicyThread的run函数中使用了Looper.prepare()和Looper.loop()函数对,这样就可以在该线程内部使用Handler。
Session:和SurfaceFlinger直接打交道的是SurfaceSession.Session类可以看成是SurfaceSession类的包装,Session中包含的成员变量有SurfaceSession,uid和pid等。Session对象是调用WMS的openSession()函数创建的,是应用程序在ViewRootImpl中调用openSession创建的,Session对象被创建好后,应用程序需要执行和窗口管理相关的操作时,都是通过IPC调用该Session对象中的相关函数来实现的。
WMThread: 当SystemServer创建WMS对象时,希望WMS对象是在一个独立的线程中运行的,因为WMS内部会有一些需要异步执行的函数。原理同PolicyThread一样。
3.窗口的创建和删除
3.1 创建窗口的时机与过程
创建窗口的时机可以分为两种,第一种是程序员主动调用WindowManager类的addView方法,另一种是当用户启动一个新的Activity或者显示一个对话框的时候,这些类的内部会间接调用addView函数。
客户端通过ViewRootImpl的setView方法添加窗口,该方法会通过IPC方式调用WMS的内联类Session的add方法。Session的add方法会间接调用WMS的addWindow方法,该方法内部可以粗略分为三个小过程。第一个过程是进行前置处理,即首先判断参数的合法性,以确保接下来的添加操作能够顺利进行,第二个过程是具体添加和窗口相关的数据,第三个过程是后置处理,即添加窗口会引起相关状态的变化,因此需要把这些变化反映到相关的数据中。
第一个过程如下图所示:
前置检查完成后,就开始真正的窗口添加,过程如下入所示:
完成以上和窗口相关的变量的赋值之后,由于新建窗口后会引起其他的一些效果,比如输入法窗口可能需要重新寻找目标视图,因此接下来需要做一些后置处理。其过程如下图所示:
3.2 assignLayersLocked()
该函数的作用是根据窗口的类型以及mBaseLayer的值,计算新的mLayer值。
3.3 addWindowToListInOrderLocked()的过程
该函数的作用是将新建的WindowState对象添加到mWindows列表中。
决定新窗口在mWindows列表中次序的因素有三个,这三个因素是“或”的关系,而不是“与”的关系。
- 添加的顺序。比如两个类型相同的窗口,后添加的将处于前者的上面。
- 添加的类型。Policy定义了13种不同的类型,比如系统窗口总是位于应用类窗口的上面,因此,就算应用窗口是后添加的,它依然处于系统窗口之后。
- 子窗口类型。这种情况仅适用于当新窗口是一个子窗口时,该函数会根据不同的子窗口类型确定子窗口的顺序,这有点类似于第二种情况,只是范围更小而已。
该函数的内部过程反应了三个问题:
- 全局变量mWindows中列表顺序的意义是,列表最后面的窗口在最上面,即最靠近用户
- WindowState中变量mSubLayer表示,当该窗口为子窗口时,相对于父窗口的层值
- mAppTokens列表中包含的都是AppWindowToken对象,其顺序表示了其对应窗口的顺序。AppWindowToken类中的allAppWindows变量保存了对该token的Activity相关的窗口,比如一个Activity的启动窗口、关闭窗口以及Activity本身的窗口
- 子窗口并不是Activity窗口所特有的,任何非子窗口理论上都可以拥有子窗口,比如状态栏,输入法窗口等,它们都可以有子窗口。
3.4 删除窗口的时机
Android中的窗口与Windows中的窗口有一个明显的区别,就是窗口的右上角没有关闭按钮,即用户不能直接关闭窗口。从SDK的角度看,SDK希望程序员不要直接去操作窗口,SDK已经对窗口进行了各种封装,Activity、菜单、对话框等,这些控件的背后都对应一个窗口,然而对于程序员来讲, 不需要关心这些控件背后的窗口,而是直接使用这些控件类。
删除窗口的时机可以大致分为两类:
第一类是隐性删除,即不直接调用WindowManager类的removeView()来删除窗口,比如Activity窗口,程序只要调用finish()方法,后台便会删除该Activity对应的窗口。
第二类是显性删除,即直接调用WindowManager的removeView()来删除窗口。
Activity中隐性删除的常见情况包括:当用户按下Back键以后,AMS会间接调用destoryActivity(),当程序员调用finish()函数后,AMS会间接调用到destoryActivity(),当系统内存低时,AMS会调用到destoryActivity()。destoryActivity()的具体执行过程如下图所示:
3.5 删除窗口的过程
删除窗口会调用WMS的removeWindow(),removeWindow的执行过程如下图所示:
4.计算窗口大小
窗口大小的变化过程可以简要描述为下图所示:
无论是添加输入法窗口还是非输入法窗口,都是经过ViewRoot的,图中将IMMS和ViewRoot分开的原因仅仅是为了显示两者的区别。
当添加一个普通窗口(非输入法窗口)时,会调用WMS.Session类的relayout()函数,该函数中包含了窗口希望的大小,而这个大小则是对窗口中的View对象进行measure时获得的。如下代码所示:
在这段代码中mView.mMeasureWidth和mView.mMeasureHeight就是该窗口希望的大小,而其他参数mWinFrame、mPendingContentInsets等矩形变量都是输出函数。当WMS执行完该函数后,为填充这些矩形变量,告知该窗口实际上应该在屏幕上的相应尺寸。
以上就是普通窗口的初始大小,在程序运行过程中,当该窗口需要和输入法窗口进行交互时,假如该窗口中包含一个编辑框,当用户点击编辑框后,IMMS中会添加一个输入法窗口,此时WMS需要根据输入法窗口重新调整目标窗口大小,并将通过IPC回调ViewRoot.W类的resized()函数,通知目标窗口新的窗口大小,此时目标窗口会自动重新进行measure和layout操作,并重绘。
而当输入法消失时,WMS同样会重新计算所有窗口的尺寸,并通过客户端窗口进行resize操作。
5.切换窗口
“切换”是指当有新窗口出现时,输入焦点需要从老窗口移动到新窗口,或者是当前窗口关闭被关闭时,上一个窗口应该获得输入焦点。
5.1 切换需要解决的问题
Android中的窗口管理系统中,首先在AMS中保存所有与Activity相关的信息,在Activity启动和关闭时,AMS会通知WMS同步Activity窗口的状态,而在WMS中使用WindowState类保存一个窗口的信息,这些窗口信息需要根据Activity的状态而动态改变。在WMS中有一个叫InputManager对象, 该对象内部保存了输入消息处理时所需要的窗口信息,借助这些信息,InputDispatcher能够决定输入消息对应哪个窗口,WindowState类的信息必须与InputManager内部的窗口信息也保持同步。
切换过程中要解决的问题可归纳为三类,第一类是状态同步问题,即AMS如何把状态传递给WMS,WMS如何保存这些状态,又如何把这些状态传递给InputManager;第二类问题是屏幕绘制问题,即当AMS启动或者关闭一个Activity时,用户一般会看到一个动画,那么WMS如何定义这个动画,并如何在动画绘制前先隐藏目标窗口,直到动画结束后才显示目标窗口,而要实现这种动画的绘制就需要一种特别的变量来保存动画窗口和目标窗口的关系;第三类问题是消息处理问题,即在窗口切换过程中,是老窗口应该继续捕获用户消息还是新窗口?
首先来看状态同步问题。
在AMS中使用ActivityRecord类来保存一个Activity相关的信息,ActivityRecord类本身是一个Binder类,名称为IApplicationToken.Stub。每个ActivityRecord都会在WMS中对应一个AppWindowToken类,该类保存了和ActivityRecord相关的所有窗口信息,比如启动窗口、实际窗口、关闭窗口等。当启动一个新的Activity时,AMS中会先创建一个ActivityRecord对象,并请求WMS中也创建一个AppWindowToken对象;当销毁一个Activity时,AMS会请求WMS删除AppWindowToken对象。
AppWindowToken中包含的窗口对象在WMS中的mWindows等列表变量中都有记录,当新窗口启动时,必须保证这些列表中的对象和AppWindowToken中保存的窗口信息之间的同步。
WMS中有一个InputManager对象mInputManager,该对象中保存了输入消息处理所需的窗口信息,当有新窗口添加或者旧窗口被删除时,该对象中的窗口信息同时需要被更新。
下面再来看绘制问题,当新建一个窗口时,AMS会首先判断该窗口是同一个Task的Activity还是一个新的Task。如果是同一个Task,则会指定一个Activity切换效果的动画,而如果是一个新的Task,则会指定一个Task切换的动画效果,这些动画效果的实施都是在WMS中完成的。每一个东湖呀实际上都仅仅是一个窗口而已,动画的过程可以简单地理解为对同一个窗口进行不同的变化,并在连续的时间内将其显示出来,从而形成动画。而在动画的过程中,如果目标窗口也已经创建好,则在动画结束之前不能显示目标窗口,只有当动画结束后才能显示目标窗口;而如果在动画结束后目标窗口还没有被创建,则启动窗口不消失,知道目标窗口被创建好才消失。这个逻辑的具体实现是在perforLayoutAndPlaceSurfacesLockInner()函数中完成的。
最后看看消息处理问题。当要添加一个新窗口时,如果该窗口需要启动动画,那么在动画结束前,应该是哪个窗口获得输入消息呢?在WMS中提供了注入startAppFreezingScreen(),topAppFreezingScreen(),resumeKeyDispatching(),pauseKeyDispatching(),setEventDispatch(true/false)
这样的接口,用来暂停、恢复消息处理,这些函数将影响在动画过程前后的输入消息处理。
5.2 InputManager和WMS接口
WMS中的InputManager对象被封装到了一个内部类InputMonitor中,从InputManager的角度看,InputDispatcher有两个特点:
- 可以暂停整个Dispatcher的消息派发,该情况下,所有窗口都得不到输入消息了。该暂停属性保存在InputDispatcher内的全局变量中。
- 也可以暂停对某个窗口消息派发,在该情况下,除被禁止的窗口外,其他窗口的消息派发都正常。该禁止属性保存在每一个窗口的内部。
基于以上两点,InputMonitor中提供了两个函数,分别是updateInputDispatchModeLw()和updateInputWindowLw(),前者用于设置InputDispatcher整体的消息暂停属性,后者用于设置某个窗口的暂停属性。
6.perforLayoutAndPlaceSurfacesLockInner()的执行过程
该函数是WMS中最核心的函数,该函数的设计目标是把屏幕绘制到屏幕上。主要三个步骤。
- 首先计算每一个窗口的大小尺寸,这段代码主要是调用performLayoutLockedInner()函数。
- 计算完所有的窗口尺寸之后,接下来就需要决定哪个应该显示、哪个应该隐藏的问题了。该步骤中包含了处理进入窗口和退出窗口的动画。当然,本步骤执行完毕后,界面上还不能反映出这些变化,因为本步骤仅仅是给和隐藏、显示相关的变量中赋值。
- 此时,已经获知窗口的大小和显示的状态,剩下的事情就是根据窗口变量中的这些信息将窗口真正地体现到屏幕上。其核心的部分就是调用SurfaceFlinger的服务,绘制屏幕。
本文地址:https://blog.csdn.net/u010457514/article/details/107089743