#### 1.SampleWindow的实现
在这一节里将编写一个最简单的Java程序SampleWindow,仅使用WMS的接口创建并渲染一个动画窗口。此程序将抛开Activity、Wallpaper等UI架构的复杂性,直接了当地揭示WMS的客户端如何申请、渲染并注销自己的窗口。同时这也初步地反应了WMS的工作方式。
这个例子很简单,只有三个文件:
- SampleWindow.java 主程序源代码。
- Android.mk 编译脚本。
- sw.sh 启动器。
分别看一下这三个文件的实现:
**SampleWindow.java::SampleWindow**
```
package understanding.wms.samplewindow;
......
public class SampleWindow {
publicstatic void main(String[] args) {
try {
//SampleWindow.Run()是这个程序的主入口
new SampleWindow().Run();
} catch (Exception e) {
e.printStackTrace();
}
}
//IWindowSession 是客户端向WMS请求窗口操作的中间代理,并且是进程唯一的
IWindowSession mSession = null;
//InputChannel 是窗口接收用户输入事件的管道。在第5章中将对其进行详细的探讨
InputChannel mInputChannel = new InputChannel();
// 下面的三个Rect保存了窗口的布局结果。其中mFrame表示了窗口在屏幕上的位置与尺寸
// 在4.4中将详细介绍它们的作用以及计算原理
RectmInsets = new Rect();
RectmFrame = new Rect();
RectmVisibleInsets = new Rect();
Configuration mConfig = new Configuration();
// 窗口的Surface,在此Surface上进行的绘制都将在此窗口上显示出来
SurfacemSurface = new Surface();
// 用于在窗口上进行绘图的画刷
PaintmPaint = new Paint();
// 添加窗口所需的令牌,在4.2节将会对其进行介绍
IBindermToken = new Binder();
// 一个窗口对象,本例演示了如何将此窗口添加到WMS中,并在其上进行绘制操作
MyWindowmWindow = new MyWindow();
//WindowManager.LayoutParams定义了窗口的布局属性,包括位置、尺寸以及窗口类型等
LayoutParams mLp = new LayoutParams();
Choreographer mChoreographer = null;
//InputHandler 用于从InputChannel接收按键事件做出响应
InputHandler mInputHandler = null;
booleanmContinueAnime = true;
publicvoid Run() throws Exception{
Looper.prepare();
// 获取WMS服务
IWindowManager wms = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
// 通过WindowManagerGlobal获取进程唯一的IWindowSession实例。它将用于向WMS
// 发送请求。注意这个函数在较早的Android版本(如4.1)位于ViewRootImpl类中
mSession= WindowManagerGlobal.getWindowSession(Looper.myLooper());
// 获取屏幕分辨率
IDisplayManager dm = IDisplayManager.Stub.asInterface(
ServiceManager.getService(Context.DISPLAY_SERVICE));
DisplayInfo 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更适合做动画的循环器[1]
mChoreographer= Choreographer.getInstance();
// 开始处理第一帧的动画
scheduleNextFrame();
// 当前线程陷入消息循环,直到Looper.quit()
Looper.loop();
// 标记不要继续绘制动画帧
mContinueAnime= false;
// 卸载当前Window
uninstallWindow(wms);
}
publicvoid initLayoutParams(Point screenSize) {
// 标记即将安装的窗口类型为SYSTEM_ALERT,这将使得窗口的ZOrder顺序比较靠前
mLp.type = LayoutParams.TYPE_SYSTEM_ALERT;
mLp.setTitle("SampleWindow");
// 设定窗口的左上角坐标以及高度和宽度
mLp.gravity = Gravity.LEFT | Gravity.TOP;
mLp.x = screenSize.x / 4;
mLp.y = screenSize.y / 4;
mLp.width = screenSize.x / 2;
mLp.height = screenSize.y / 2;
// 和输入事件相关的Flag,希望当输入事件发生在此窗口之外时,其他窗口也可以接受输入事件
mLp.flags = mLp.flags | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
publicvoid installWindow(IWindowManager wms) throws Exception {
// 首先向WMS声明一个Token,任何一个Window都需要隶属与一个特定类型的Token
wms.addWindowToken(mToken,WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
// 设置窗口所隶属的Token
mLp.token = mToken;
// 通过IWindowSession将窗口安装进WMS,注意,此时仅仅是安装到WMS,本例的Window
// 目前仍然没有有效的Surface。不过,经过这个调用后,mInputChannel已经可以用来接受
// 输入事件了
mSession.add(mWindow,0, mLp, View.VISIBLE, mInsets, mInputChannel);
/*通过IWindowSession要求WMS对本窗口进行重新布局,经过这个操作后,WMS将会为窗口
创建一块用于绘制的Surface并保存在参数mSurface中。同时,这个Surface被WMS放置在
LayoutParams所指定的位置上 */
mSession.relayout(mWindow,0, mLp, mLp.width, mLp.height, View.VISIBLE,
0, mFrame, mInsets,mVisibleInsets, mConfig, mSurface);
if(!mSurface.isValid()) {
thrownew RuntimeException("Failed creating Surface.");
}
// 基于WMS返回的InputChannel创建一个Handler,用于监听输入事件
//mInputHandler一旦被创建,就已经在监听输入事件了
mInputHandler= new InputHandler(mInputChannel, Looper.myLooper());
}
publicvoid uninstallWindow(IWindowManager wms) throws Exception {
// 从WMS处卸载窗口
mSession.remove(mWindow);
// 从WMS处移除之前添加的Token
wms.removeWindowToken(mToken);
}
publicvoid scheduleNextFrame() {
// 要求在显示系统刷新下一帧时回调mFrameRender,注意,只回调一次
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION
, mFrameRender, null);
}
// 这个Runnable对象用以在窗口上描绘一帧
publicRunnable mFrameRender = new Runnable() {
@Override
publicvoid run() {
try{
// 获取当期时间戳
long time = mChoreographer.getFrameTime() % 1000;
// 绘图
if (mSurface.isValid()) {
Canvas canvas = mSurface.lockCanvas(null);
canvas.drawColor(Color.DKGRAY);
canvas.drawRect(2 * mLp.width * time / 1000
- mLp.width, 0, 2 *mLp.width * time
/ 1000, mLp.height,mPaint);
mSurface.unlockCanvasAndPost(canvas);
mSession.finishDrawing(mWindow);
}
if(mContinueAnime)
scheduleNextFrame();
} catch (Exception e) {
e.printStackTrace();
}
}
};
// 定义一个类继承InputEventReceiver,用以在其onInputEvent()函数中接收窗口的输入事件
classInputHandler extends InputEventReceiver {
Looper mLooper = null;
publicInputHandler(InputChannel inputChannel, Looper looper) {
super(inputChannel,looper);
mLooper= looper;
}
@Override
publicvoid onInputEvent(InputEvent event) {
if(event instanceof MotionEvent) {
MotionEvent me = (MotionEvent)event;
if (me.getAction() ==MotionEvent.ACTION_UP) {
// 退出程序
mLooper.quit();
}
}
super.onInputEvent(event);
}
}
// 实现一个继承自IWindow.Stub的类MyWindow。
classMyWindow extends IWindow.Stub {
// 保持默认的实现即可
}
}
```
由于此程序使用了大量的隐藏API(即SDK中没有定义这些API),因此需要放在Android源码环境中进行编译它。对应的Android.mk如下:
**Android.mk**
```
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := samplewindow
include $(BUILD_JAVA_LIBRARY)
```
将这两个文件放在$TOP/frameworks/base/cmds/samplewindow/下,然后用make或mm命令进行编译。最终生成的结果是samplewindow.jar,文件位置在out/target/<ProductName>/system/framework/下。将该文件通过adb push到手机的/system/framework/下。
>[info] **提示**:读者可使用Android4.2模拟器来运行此程序。
然而,samplewindow.jar不是一个可执行程序,。故,需借助Android的app\_process工具来加载并执行它。笔者编写了一个脚本做为启动器:
**sw.sh**
```
base=/system
export CLASSPATH=$base/framework/samplewindow.jar
exec app_process $base/binunderstanding.wms.samplewindow.SampleWindow "$@"
```
**注意** app\_process其实就是大名鼎鼎的zygote。不过,只有使用--zygote参数启动时它才会给改名为zygote\[2\],否则就像java –jar命令一样,运行指定类的main静态函数。
在手机中执行该脚本,其运行结果是一个灰色的方块不断地从屏幕左侧移动到右侧,如图4-1所示。
:-: ![](http://img.blog.csdn.net/20150814130201736?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
图 4-1 SampleWindow在手机中的运行效果
#### 2.初识窗口的创建、绘制与销毁
SampleWindow的这段代码虽然简单,但是却很好地提炼了一个窗口的创建、绘制以及销毁的过程。注意,本例没有使用任何 WMS以外的系统服务,也没有使用Android系统四大组件的框架,也就是说,如果你愿意,可以利用WMS实现自己的UI与应用程序框架,这样就可以衍生出一个新的平台了。
总结在客户端创建一个窗口的步骤:
- 获取IWindowSession和WMS实例。客户端可以通过IWindowSession向WMS发送请求。
- 创建并初始化WindowManager.LayoutParams。注意这里是WindowManager下的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的原理与使用方法,请参考《卷 I》第8章“深入理解Surface系统”。
* * * * *
这是对Surface作画的标准方法。在客户端也可以通过OpenGL进行作画,不过这超出了本书的讨论范围。另外,在SampleWindow例子中使用了Choreographer类进行了动画帧的安排。Choreographer意为编舞指导,是Jelly Bean新增的一个工具类。其用法与Handler的post()函数非Z且不会再显示新的窗口,则需要从WMS将之前添加的显示令牌一并删除。
#### 3.窗口的概念
在SampleWindow例子中,有一个名为mWindow(类型为IWindow)的变量。读者可能会理所当然地认为它就是窗口了。其实这种认识并不完全正确。IWindow继承自Binder,并且其Bn端位于应用程序一侧(在例子中IWindow的实现类MyWindow就继承自IWindow.Stub),于是其在WMS一侧只能作为一个回调,以及起到窗口Id的作用。
那么,窗口的本质是什么呢?
是进行绘制所使用的画布:Surface。
当一块Surface显示在屏幕上时,就是用户所看到的窗口了。客户端向WMS添加一个窗口的过程,其实就是WMS为其分配一块Surface的过程,一块块Surface在WMS的管理之下有序地排布在屏幕上,Android才得以呈现出多姿多彩的界面来。所以从这个意义上来讲,WindowManagerService被称之为SurfaceManagerService也说得通的。
于是,根据对Surface的操作类型可以将Android的显示系统分为三个层次,如图4-2所示。
:-: ![](http://img.blog.csdn.net/20150814130227326?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
图 4-2 Android显示系统的三个层次
在图4-2中:
- 第一个层次是UI框架层,其工作为在Surface上绘制UI元素以及响应输入事件。
- 第二个层次为WMS,其主要工作在于管理Surface的分配、层级顺序等。
- 第三层为SurfaceFlinger,负责将多个Surface混合并输出。
经过这个例子的介绍,相信大家对WMS的功能有了一个初步的了解。接下来,我们要进入WMS的内部,通过其启动过程一窥它的构成。
- 前言
- 推荐序
- 第1章 开发环境部署
- 1.1获取Android源代码
- 1.2Android的编译
- 1.3在IDE中导入Android源代码
- 1.3.1将Android源代码导入Eclipse
- 1.3.2将Android源代码导入SourceInsight
- 1.4调试Android源代码
- 1.4.1使用Eclipse调试Android Java源代码
- 1.4.2使用gdb调试Android C/C 源代码
- 1.5本章小结
- 第2章 深入理解Java Binder和MessageQueue
- 2.1概述
- 2.2Java层中的Binder分析
- 2.2.1Binder架构总览
- 2.2.2初始化Java层Binder框架
- 2.2.3窥一斑,可见全豹乎
- 2.2.4理解AIDL
- 2.2.5Java层Binder架构总结
- 2.3心系两界的MessageQueue
- 2.3.1MessageQueue的创建
- 2.3.2提取消息
- 2.3.3nativePollOnce函数分析
- 2.3.4MessageQueue总结
- 2.4本章小结
- 第3章 深入理解AudioService
- 3.1概述
- 3.2音量管理
- 3.2.1音量键的处理流程
- 3.2.2通用的音量设置函数setStreamVolume()
- 3.2.3静音控制
- 3.2.4音量控制小结
- 3.3音频外设的管理
- 3.3.1 WiredAccessoryObserver 设备状态的监控
- 3.3.2AudioService的外设状态管理
- 3.3.3音频外设管理小结
- 3.4AudioFocus机制的实现
- 3.4.1AudioFocus简单的例子
- 3.4.2AudioFocus实现原理简介
- 3.4.3申请AudioFocus
- 3.4.4释放AudioFocus
- 3.4.5AudioFocus小结
- 3.5AudioService的其他功能
- 3.6本章小结
- 第4章 深入理解WindowManager-Service
- 4.1初识WindowManagerService
- 4.1.1一个从命令行启动的动画窗口
- 4.1.2WMS的构成
- 4.1.3初识WMS的小结
- 4.2WMS的窗口管理结构
- 4.2.1理解WindowToken
- 4.2.2理解WindowState
- 4.2.3理解DisplayContent
- 4.3理解窗口的显示次序
- 4.3.1主序、子序和窗口类型
- 4.3.2通过主序与子序确定窗口的次序
- 4.3.3更新显示次序到Surface
- 4.3.4关于显示次序的小结
- 4.4窗口的布局
- 4.4.1从relayoutWindow()开始
- 4.4.2布局操作的外围代码分析
- 4.4.3初探performLayoutAndPlaceSurfacesLockedInner()
- 4.4.4布局的前期处理
- 4.4.5布局DisplayContent
- 4.4.6布局的阶段
- 4.5WMS的动画系统
- 4.5.1Android动画原理简介
- 4.5.2WMS的动画系统框架
- 4.5.3WindowAnimator分析
- 4.5.4深入理解窗口动画
- 4.5.5交替运行的布局系统与动画系统
- 4.5.6动画系统总结
- 4.6本章小结
- 第5章 深入理解Android输入系统
- 5.1初识Android输入系统
- 5.1.1getevent与sendevent工具
- 5.1.2Android输入系统简介
- 5.1.3IMS的构成
- 5.2原始事件的读取与加工
- 5.2.1基础知识:INotify与Epoll
- 5.2.2 InputReader的总体流程
- 5.2.3 深入理解EventHub
- 5.2.4 深入理解InputReader
- 5.2.5原始事件的读取与加工总结
- 5.3输入事件的派发
- 5.3.1通用事件派发流程
- 5.3.2按键事件的派发
- 5.3.3DispatcherPolicy与InputFilter
- 5.3.4输入事件的派发总结
- 5.4输入事件的发送、接收与反馈
- 5.4.1深入理解InputChannel
- 5.4.2连接InputDispatcher和窗口
- 5.4.3事件的发送
- 5.4.4事件的接收
- 5.4.5事件的反馈与发送循环
- 5.4.6输入事件的发送、接收与反馈总结
- 5.5关于输入系统的其他重要话题
- 5.5.1输入事件ANR的产生
- 5.5.2 焦点窗口的确定
- 5.5.3以软件方式模拟用户操作
- 5.6本章小结
- 第6章 深入理解控件系统
- 6.1 初识Android的控件系统
- 6.1.1 另一种创建窗口的方法
- 6.1.2 控件系统的组成
- 6.2 深入理解WindowManager
- 6.2.1 WindowManager的创建与体系结构
- 6.2.2 通过WindowManagerGlobal添加窗口
- 6.2.3 更新窗口的布局
- 6.2.4 删除窗口
- 6.2.5 WindowManager的总结
- 6.3 深入理解ViewRootImpl
- 6.3.1 ViewRootImpl的创建及其重要的成员
- 6.3.2 控件系统的心跳:performTraversals()
- 6.3.3 ViewRootImpl总结
- 6.4 深入理解控件树的绘制
- 6.4.1 理解Canvas
- 6.4.2 View.invalidate()与脏区域
- 6.4.3 开始绘制
- 6.4.4 软件绘制的原理
- 6.4.5 硬件加速绘制的原理
- 6.4.6 使用绘图缓存
- 6.4.7 控件动画
- 6.4.8 绘制控件树的总结
- 6.5 深入理解输入事件的派发
- 6.5.1 触摸模式
- 6.5.2 控件焦点
- 6.5.3 输入事件派发的综述
- 6.5.4 按键事件的派发
- 6.5.5 触摸事件的派发
- 6.5.6 输入事件派发的总结
- 6.6 Activity与控件系统
- 6.6.1 理解PhoneWindow
- 6.6.2 Activity窗口的创建与显示
- 6.7 本章小结
- 第7章 深入理解SystemUI
- 7.1 初识SystemUI
- 7.1.1 SystemUIService的启动
- 7.1.2 状态栏与导航栏的创建
- 7.1.3 理解IStatusBarService
- 7.1.4 SystemUI的体系结构
- 7.2 深入理解状态栏
- 7.2.1 状态栏窗口的创建与控件树结构
- 7.2.2 通知信息的管理与显示
- 7.2.3 系统状态图标区的管理与显示
- 7.2.4 状态栏总结
- 7.3 深入理解导航栏
- 7.3.1 导航栏的创建
- 7.3.2 虚拟按键的工作原理
- 7.3.3 SearchPanel
- 7.3.4 关于导航栏的其他话题
- 7.3.5 导航栏总结
- 7.4 禁用状态栏与导航栏的功能
- 7.4.1 如何禁用状态栏与导航栏的功能
- 7.4.2 StatusBarManagerService对禁用标记的维护
- 7.4.3 状态栏与导航栏对禁用标记的响应
- 7.5 理解SystemUIVisibility
- 7.5.1 SystemUIVisibility在系统中的漫游过程
- 7.5.2 SystemUIVisibility发挥作用
- 7.5.3 SystemUIVisibility总结
- 7.6 本章小结
- 第8章 深入理解Android壁纸
- 8.1 初识Android壁纸
- 8.2深入理解动态壁纸
- 8.2.1启动动态壁纸的方法
- 8.2.2壁纸服务的启动原理
- 8.2.3 理解UpdateSurface()方法
- 8.2.4 壁纸的销毁
- 8.2.5 理解Engine的回调
- 8.3 深入理解静态壁纸-ImageWallpaper
- 8.3.1 获取用作静态壁纸的位图
- 8.3.2 静态壁纸位图的设置
- 8.3.3 连接静态壁纸的设置与获取-WallpaperObserver
- 8.4 WMS对壁纸窗口的特殊处理
- 8.4.1 壁纸窗口Z序的确定
- 8.4.2 壁纸窗口的可见性
- 8.4.3 壁纸窗口的动画
- 8.4.4 壁纸窗口总结
- 8.5 本章小结