窗口是什么
Android系统中的窗口是屏幕上的一块用于绘制各种UI元素并可以响应应用户输入的一个矩形区域。
从原理上来讲,窗口的概念是独自占有一个 Surface 实例的显示区域。
例如Dialog、Activity的界面、壁纸、状态栏以及Toast等都是窗口。
一个 Activity 通过 Surface 来显示自己:
- Surface 是一块画布, 应用可以随心所欲地通过 Canvas 或者 OpenGL 在其上作画。
- 然后通过 SurfaceFlinger 将多块 Surface 的内容按照特定的顺序(z-order)进行混合并输出到 FrameBuffer,从而显示给用户。
WMS 这里充当一个管家的角色
既然每个窗口都有一块Surface供自己涂鸦,必然需要一个角色对所有窗口的Surface进行协调管理。于是,WMS便应运而生。WMS为所有窗口分配Surface,掌管Surface的显示顺序(Z-order)以及位置尺寸,控制窗口动画,并且还是输入系统的一重要的中转站。
说明
一个窗口拥有显示和响应用户输入这两层含义
本章将深入分析WMS的两个基础子系统的工作原理:
- 布局系统(Layout System) :计算与管理窗口的位置、层次。
- 动画系统(Animation System) :根据布局系统计算的窗口位置与层次渲染窗口动画。
例子
// SampleWindow.java
public class SampleWindow {
public static void main(String[] args) {
try {
// 程序主入口
new SampleWindow().Run();
} catch (Exception e) {
e.printStackTrace);
}
}
// IWindowSession 是客户端向WMS请求窗口操作的中间代理,并且是进程唯一的
IWindowSession mSesison = null;
// InputChannel 是窗口接收用户输入事件的管道
InputChannel mInputChannel = new InputChannel();
// 下面的三个 Rect 保存了窗口的布局结果。其中 mFrame 表示了窗口在屏幕上的位置与尺寸
Rect mInsets = new Rect();
Rect mFrame = new Rect();
Rect mVisibleInsets = new Rect();
Configuration mConfig = new Configuration();
// 窗口的 Surface ,在此 Surface 上进行的绘制都将在此窗口上显示出来
Surface mSurface = new Surface();
// 用于在窗口上进行绘图的画刷
Paint mPaint = new Paint();
// 添加窗口所需的令牌
IBinder mToken = new Binder();
// 一个窗口对象,本例演示了如何将此窗口添加到 WMS 中,并在其上进行绘制操作
MyWindow mWindow = new MyWindow();
// WindowManager.LayoutParams 定义窗口的布局属性,包括位置、尺寸以及窗口类型等
LayoutParams mLp = new LayoutParams();
Choreographer mChoreographer = null;
// InputHandler 用于从InputChnanel 接收案件事件并作出响应
InputHandler mInputHandler = null;
boolean mContinueAnime = true;
public void Run() throws Exception {
Looper.prepare();
// 获取 WMS 服务
IWindowManager wms = IWindowManager.Stub.asInterface (
ServiceManager.getService(Context.WINDOW_SERVICE));
// 通过WindowManagerGlobal 获取进程唯一的IWindowSession 实例。
// 用于向 WMS 发送请求。
mSession = WindowManagerGlobal.getWindowSession(Looper.myLooper());
// 获取屏幕分辨率
IDisplayManager dm = IDisplayManager.Stub.asInterface(
ServiceManager.getService(Context.DISPLAY_SERVICE));
DispalyInfo di = dm.getDisplayInfo(Display.DEFAULT_DISPLAY);
Point scrnSize = new Point(di.appWidth, di.appHeight);
// 初始化WindowManager.LayoutParams
initLayoutParams(scrnSize);
// 将新窗口添加到 WMS
installWindow(wms);
// 初始化 Choreographer 的实例,此实例为线程唯一。
// 这个类的用法与 Handler 类似,不过它总是在 VSYC 同步时回调,所以比 Handler 更适合做动画的循环器
mChoreographer = Choreographer.getInstance();
// 开始处理第一帧的动画
scheduleNextFrame();
// 当前线程陷入消息循环,直到 Looper.quit()
Looper.loop();
// 标记不要继续绘制动画帧
mContinueAnime = false;
// 卸载当前 Window
unistallWindow(wms);
}
...
}
总结
在客户端创建一个窗口的步骤:
-
获取 IWindowSession 和 WMS 实例。 客户端可以通过 IWindowSession 向 WMS 发送请求。
-
创建并初始化 WindowManager.LayoutParams , 这里 LayoutParams 继承自 ViewGroup.LayoutParams类,并且扩展了一些属性。
其中最重要的就是type属性。这个属性描述了窗口的类型,窗口类型是 WMS 对多个窗口进行 ZOrder 排序的依据。
- 向 WMS 添加一个窗口令牌(WindowToken),描述了一个显示行为,并且 WMS 要求每一个窗口必须隶属于某一个显示令牌。
- 向 WMS 添加一个窗口。必须在LayoutParams中指明此窗口所隶属于的窗口令牌,否则在某些情况下添加操作会失败。 在SampleWindow中,不设置令牌也可成功完成添加操作,因为窗口的类型被设为TYPE_SYSTEM_ALERT,它是系统窗口的一种。而对于系统窗口,WMS会自动为其创建显示令牌,故无须客户端操心。
- 向WMS申请对窗口进行重新布局(relayout)。所谓的重新布局,就是根据窗口新的属性去调整其Surface相关的属性,或者重新创建一个Surface(例如窗口尺寸变化导致之前的Surface不满足要求)。向WMS添加一个窗口之后,其仅仅是将它在WMS中进行注册而已。只有经过重新布局之后,窗口才拥有WMS为其分配的画布。有了画布,窗口之后就可以随时进行绘制工作了。
窗口的绘制过程:
- 通过 Surface.lock() 函数获取可以在其上作画的Canvas实例。
- 使用Canvas实例进行作画。
- 通过Surface.unlockCanvasAndPost()函数提交绘制结果。
窗口的本质
Surface 画布
当一块Surface显示在屏幕上时,就是用户所看到的窗口了。客户端向WMS添加一个窗口的过程,其实就是WMS为其分配一块Surface的过程,一块块Surface在WMS的管理之下有序地排布在屏幕上,Android才得以呈现出多姿多彩的界面。所以从这个意义上讲,WindowManagerService被称为SurfaceManagerService也说得通。