多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
#### 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的内部,通过其启动过程一窥它的构成。