图解Android - Android GUI 系统 (2) - 窗口管理 (View, Canvas, Window Manager)

 

Android 的窗口管理系统 (View, Canvas, WindowManager)

图解Android - Zygote 和 System Server 启动分析一 文里,咱们已经知道Android 应用程序是怎么建立出来的,大概的流程是 ActivityManagerService -> Zygote -> Fork App, 而后应用程序在ActivityThread 中的进入loop循环等待处理来自AcitivyManagerService的消息。若是一个Android的应用有Acitivity, 那它起来后的第一件事情就是将本身显示出来,这个过程是怎样的? 这就是本章节要讨论的话题。html

 

Android 中跟窗口管理相关(不包括显示和按键处理)主要有两个进程,Acitivty所在进程 和 WndowManagerService 所在进程(SystemServer).  上图中用不一样颜色区分这两个进程,黄色的模块运行在Activity的进程里,绿色的模块则在System Server内部,本文主要讨论的是WindowManager Service。它们的分工是,Activity进程负责窗口内View的管理,而WindowManager Service 管理来自与不一样Acitivity以及系统的的窗口。java

1. Acitivty显示前的准备工做

图解Android - Zygote, System Server 启动分析中咱们已经知道,一个新的应用被fork完后,第一个调用的方法就是 ActivityThread的main(),这个函数主要作的事情就是建立一个ActivityThread线程,而后调用loop()开始等待。当收到来自 ActivityManager 的 LAUNCH_ACTIVITY 消息后,Activity开始了他的显示之旅。下图描绘的是Activity在显示前的准备流程。android


 

图分为三部分, 右上角是Acitivity应用的初始化。中间部分是Acitivity 与WindowManager Service的交互准备工做,左下角是window显示的开始。本文主要描述后两部分,而Activity的启动会放在图解Android - Android GUI 系统 (4) - Activity的生命周期里讲解。shell

  1. Activity内部的准备过程,这里面有一个重要对象,ContextImpl 生成,它是Context类的具体实现,它里面封装了应用程序访问系统资源的一些基本API, 好比说,链接某一个服务并获取其IBinder,发送Intent, 获取应用程序的信息,访问数据库等等,在应用看来,它就是整个AndroidSDK的入口。ContextImpl 除了实现函数,里面还维护成员变量,其中有一个mDisplay,表明当前应用输出的显示设备,若是应用没有特别指定,通常指向系统的默认显示输出,好比手机的液晶屏。
  2. 图解Android - Android GUI 系统 (1) - 概论中咱们已经介绍过ViewRootImpl 的地位至关与MVC架构中的C,Controller是链接View和Modal的关键,因此须要首先建立它。当addView(view, param)被调用的时候,一个ViewRoot就被建立出来,addView()的实现以下:
    public void addView(View view, ViewGroup.LayoutParams params) {
            mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow){
           ...
           root = new ViewRootImpl(view.getContext(), display);
           ...
    }

    这里的参数View是想要添加到WindowManagerService 的“window", 通常一个Activity只须要一个’Window', 因此,Acitivy的默认实现是将DecorView做为”Window" 交给Window Manager Service 进行管理。Params是Layout相关的参数,里面包含有长,宽,边缘尺寸(Margin)等信息,mDisplay就是这个窗口想要输出的Display设备编号,由ContextImpl传递过来。mParentWindow 就是Activity的成员变量mWindow,从最上面的类图能够很容易看出来,对于手机而言,就是一个PhoneWindow对象,对于GoogleTV,就是TVWindow对象。数据库

  3. ViewRootImpl 在构造过程成初始化一些重要的成员变量,包括一个Surface对象(注意这是一个空的Surface对象,没有赋给任何有效的值,后面会经过CopyFromParcel来填充),还有mChoreophaer定时器(Singleton对象,每一个进程只有一个),与此同时,ViewRootImp经过WindowManagerGlobal建立了一个和WindowManagerService 的通话通道,接下来会利用这条通道作进一步的初始化工做。
  4. 仍是在addView()里,WindowManagerImpl拿到ViewRoot对象后调用它的setView方法,将view, layout参数交给ViewRootImpl开始接管。在setView()里ViewRootImpl作进一步的初始化工做,包括建立一个InputChannel接收用户按键输入,enable图形硬件加速,请求第一次的Layout等等,这里只介绍跟WindowManagerService 有关系的一件事,就是向WindowManager service 报道,加入到WindowManager的窗口管理队列中。这个函数是 addToDisplay(),
    int addToDisplay(in IWindow window,  //提供给WMS的回调接口
         in int seq, 
         in windowManager.LayoutParams attrs,  // layout参数
         in int viewVisibility, in int layerStackId,     // display ID
         out Rect outContentInsets,                      // WMS计算后返回这个View在显示屏上的位置
         out InputChannel outInputChannel);       // 用户输入通道Handle

    addToDisplay() 最终会调到WindowManager Service的addWindow() 接口。windows

  5. addWindow() 里首先生成了一个WindowState对象,它是ViewRootImpl 在WindowManager Service端的表明。在它的构造函数里,WindowState 会生成IWindowId.Stub 对象和DeathRecipient对象来分别监听Focus和窗口死亡的信息,根据用户传进来的Window Type计算出窗口的mBaseLayer,mSubLayer和mLastLayer值,分别对应于主窗口,主窗口上弹出的子窗口(如输入法),以及动画时分别对应的ZOrder值,(在本文后面会具体介绍),生成一个WindowStateAnimation 负责整个Window的动画,并在内部将windowToken, appWindowToken等关联起来。
  6. WindowManager Service 调用openInputChannelPair() and RegisterInputChannel(), 建立用于通讯的SocketPair , 将其传给InputManagerService, 用于接下来的用户输入事件对应的响应窗口(参考Android的用户输入处理),
  7. 最后,WindowManagerService 调用WindowState的attach(),建立了一个Surface Session 并将Surface Session,WindowSession 还有WindowState 三者关联起来.
  8. WindowManager Service 调用 assignLayersLocked()计算全部Window的Z-Order。
  9. addToDisplay() 返回,ViewRootImpl 和 WindowManager Service 内部的准备工做就绪。ActivityThread会发送ACTIVITY_RESUMED消息告诉Activity显示开始。能够是图尚未画,不是吗?对的,此刻Surface尚未真正初始化(咱们前面说过ViewRootImpl只是New了一个空的对象,须要有人往里面填东西)。底层存放绘制结果的Buffer也没有建立,可是最多16ms之后这一切就会开始。

2. Choreographer 和 Surface的建立

全部的图像显示输出都是由时钟驱动的,这个驱动信号称为VSYNC。这个名词来源于模拟电视时代,在那个年代,由于带宽的限制,每一帧图像都有分红两次传输,先扫描偶数行(也称偶场)传输,再回到头部扫描奇数行(奇场),扫描以前,发送一个VSYNC同步信号,用于标识这个这是一场的开始。场频,也就是VSYNC 频率决定了帧率(场频/2). 在如今的数字传输中,已经没有了场的概念,但VSYNC这一律念得于保持下来,表明了图像的刷新频率,意味着收到VSYNC信号后,咱们必须将新的一帧进行显示。api

VSYNC通常由硬件产生,也能够由软件产生(若是够准确的话),Android 中VSYNC来着于HWComposer,接收者没错,就是Choreographer。Choreographer英文意思是编舞者,跳舞很讲究节奏不是吗,必需要踩准点。Choreographer 就是用来帮助Android的动画,输入,仍是显示刷新按照固定节奏来完成工做的。看看Chroreographer 和周边的类结构。数组

 

 

从图中咱们能够看到, Choreographer 是ViewRootImpl 建立的(Choreographer是一个sigleton类,第一个访问它的ViewRootImpl建立它),它拥有一个Receiver, 用来接收外部传入的Event,它还有一个Callback Queue, 里面存放着若干个CallbackRecord, 还有一个FrameHandler,用来handleMessage, 最后,它还跟Looper有引用关系。再看看下面这张时序图,一切就清楚了,缓存

 

 

首先Looper调用loop() 后,线程进入进入睡眠,直到收到一个消息。Looper也支持addFd()方法,这样若是某个fd上发生了IO操做(read/write), 它也会从睡眠中醒来。Choreographer的实现用到了这两种方式,首先他经过某种方式获取到SurfaceFlinger 进程提供的fd,而后将其交给Looper进行监听,只要SurfaceFlinger往这个fd写入VSync事件,looper便会唤醒。Lopper唤醒后,会执行onVsync()时间,这里面没有作太多事情,而是调用Handler接口 sendMessageAtTime() 往消息队列里又送了一个消息。这个消息最终调到了Handler (实际是FrameHandler)的handleCallback来完成上层安排的工做。为何要绕这么大个圈?为何不在onVSync里直接handleCallback()? 毕竟onVSync 和 handleCallback() 都在一个线程里。这是由于MessageQueue 不光接收来自SurfaceFlinger 的VSync 事件,还有来自上层的控制消息。VSync的处理是至关频繁的,若是不将VSync信号送人MessageQueue进行排队,MessageQueue里的事件就有可能得不到及时处理,严重的话会致使溢出。固然了,若是由于VSync信号排队而致使处理延迟,这就是设计的问题了,这也是为何Android文档里反复强调在Activity的onXXX()里不要作太耗时的工做,由于这些回调函数和Choreographer运行在同一个线程里,这个线程就是所谓的UI线程。安全

言归正传,继续往前,VSync事件最终在doFrame()里调了三次doCallbacks()来完成不一样的功能, 分别处理用户输入事件,动画刷新(动画就是定时更新的图片), 最后执行performTraversals(),这个函数里面主要是检查当前窗口当前状态,好比说是否依然可见,尺寸,方向,布局是否发生改变(多是由前面的用户输入触发的),分别调用performMeasure(), performLayout, performDraw()完成测量,布局和绘制工做。咱们会在后面详细学习这三个函数,这里咱们主要看一下第一次进入performTraversals的状况,由于第一次会作些初始化的工做,最重要的一件就是如本节标题,建立Surface对象。

回看图2,咱们能够看到Surface的建立不是在Activity进程里,而是在WindowManagerService完成的(粉颜色)。当一个Activity第一次显示的时候,Android显示切换动画,所以Surface是在动画的准备过程当中建立的,具体发生在类WindowStateAnimator的createSurfaced()函数。它最终建立了一个SurfaceControl 对象。SurfaceControl是Android 4.3 里新引进的类,Google从以前的Surface类里拆出部分接口,变成SurfaceControl,为何要这样? 为了让结构更清晰,WindowManagerService 只能对Surface进行控制,但并不更新Surface里的内容,分拆以后,WindowManagerService 只能访问SurfaceControl,它主要控制Surface的建立,销毁,Z-order,透明度,显示或隐藏,等等。而真正的更新者,View会经过Canvas的接口将内容画到Surface上。那View怎么拿到WMService建立的Surface,答案是下面的代码里,surfaceControl 被转换成一个Surface对象,而后传回给ViewRoot, 前面建立的空的Surface如今有了实质内容。Surface经过这种方式被建立出来,Surface对应的Buffer 也相应的在SurfaceFlinger内部经过HAL层模块(GRAlloc)分配并维护在SurfaceFlinger 内部,Canvas() 经过dequeueBuffer()接口拿到Surface的一个Buffer,绘制完成后经过queueBuffer()还给SurfaceFlinger进行绘制。

                    SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();
                    if (surfaceControl != null) {
                        outSurface.copyFrom(surfaceControl);
                        if (SHOW_TRANSACTIONS) Slog.i(TAG,
                                "  OUT SURFACE " + outSurface + ": copied");
                    } else {
                        outSurface.release();
                    }

到这里,咱们知道了Activity的三大工做,用户输入响应,动画,和绘制都是由一个定时器驱动的,Surface在Activity第一次启动时由WindowManager Service建立。接下来咱们具体看一下View是如何画在Surface Buffer上的,而Surface Buffer的显示则交由图解Android - Android GUI 系统 (3) - Surface Flinger 来讨论。

3. View的Measure, Layout 和 Draw

直接从前面提到的performMeasure()函数开始.

 由于递归调用,实际的函数调用栈比这里显示的深得不少,这个函数会从view的结构树顶(DecorView), 一直遍历到叶节点。中间会通过三个基类,DecorView, ViewGroup 和 View, 它们的类结构以下图所示:

 

全部可见的View(不包括DecorView 和 ViewGroup)都是一个矩形,Measure的目的就是算出这个矩形的尺寸, mMeasuredWidth 和 mMeasuredHeight (注意,这不是最终在屏幕上显示的尺寸),这两个尺寸的计算受其父View的尺寸和类型限制,这些信息存放在 MeasureSpec里。MeasureSpec 里定义了三种constraints,

  •         /*父View对子View尺寸没有任何要求,其能够设任意尺寸*/
            public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
            /* 父View为子View已经指定了大小*/
            public static final int EXACTLY     = 1 << MODE_SHIFT;
    
            /*父View没有指定子View大小,但其不能超过父View的边界 */
            public static final int AT_MOST     = 2 << MODE_SHIFT;

widthMeasureSpec 和 heightMeasureSpec 做为 onMeasure的参数出入,子View根据这两个值计算出本身的尺寸,最终调用 setMeasuredDimension() 更新mMeasuredWidth 和 mMeasuredHeight.

performMeasure() 结束后,全部的View都更新了本身的尺寸,接下来进入performLayout().

performLayout() 的流程和performMeasure基本上同样,能够将上面图中的measure() 和 onMeasure 简单的换成 layout() 和 onLayout(), 也是遍历整课View树,根据以前算出的大小将每一个View的位置信息计算出来。这里不作太多描述,咱们把重心放到performDraw(), 由于这块最复杂,也是最为重要的一块。

很早就玩过Android手机的同窗应该能体会到Android2.3 到 Android 4.0 (其实Android3.0就有了,只是这个版本只在平板上有)的性能的巨大提高,UI界面的滑动效果一下变得顺滑不少,究竟是framework的什么改动带来的?咱们立刻揭晓。。。(这块非本人工做领域,网上相关的资料也不多,因此纯凭我的砖研,欢迎拍砖指正)

Android Graphics Hardware Acceleration

OK, 如标题所述,最根本的缘由就是引入了硬件加速, GPU是专门优化图形绘制的硬件单元,不少GPU(至少手机上的)都支持OpenGL,一种开放的跨平台的3D绘图API。Android3.0之前,几乎全部的图形绘制都是由Skia完成,Skia是一个向量绘图库,使用CPU来进行运算, 因此它的performance是一个问题(固然,Skia也能够用GPU进行加速,有人在研究,但好像GPU对向量绘图的提高不像对Opengl那么明显),因此从Android3.0 开始,Google用hwui取代了Skia,准确的说,是推荐取代,由于Opengl的支持不彻底,有少许图形api仍由Skia完成,另外还要考虑到兼容性,硬件加速的功能并非默认打开,须要程序在AndroidManifests.xml 或代码里控制开关。固然,大部分Canvas的基本操做都经过hwui重写了,hwui下面就是Opengl和后面的GPU,这也是为何Android 4.0的launcher变得异常流畅的缘故。OK,那咱们接下来的重点就是要分析HWUI的实现了。

在此以前,简单的介绍一下OpenGL的一些概念,不然很难理解。要想深刻理解Opengl,请必读经典的红包书:http://www.glprogramming.com/red/

Opengl说白了,就是一组图形绘制的API。 这些API都是一些很是基本的命令,经过它,你能够构造出很是复杂的图形和动画,同时,它又是跟硬件细节无关的,因此无需改动就能够运行在不一样的硬件平台上(前提是硬件支持所需特性)。OpenGL的输入是最基本几何元素(geometric primitives), 点(points), 线(lines), 多边形(polygons), 以及bitmap和pixle data, 他的输出是一个或两个Framebuffer(真3D立体). 输入到输出的流程(rendering pipeline)以下图所示:

 这里有太多的概念,咱们只描述跟本文相关的几个:

 vertex data
     
全部的几何元素(点线面)均可以用点(vertics)来描述, 每一个点都对应三维空间中的一个坐标(x,y,z), 以下图所示,改变若干点的位置,咱们即可以构造出一个立体的图形。    

Triangles

     OpenGL只能画非凹(nonconvex)的多边形,但是现实世界中存在太多的凹性的物体,怎么办呢?经过连线能够将凹的物体分红若干个三角形,三角形永远都是凸(convex)的。同时三角形还有一个特性,三个点能够惟一肯定一个平面,因此用尽量多的三角形就能够逼近现实世界中复杂的曲线表面,好比下图的例子,三角形的数目越多,球体的表示就越逼真。这也是为何咱们常常看到显卡的性能评测都以三角形的生成和处理做为一个很是重要的指标。

 Display List

      全部的Vertex和Pixel信息都可以存在Display List 里面,用于后续处理,换句话说,Display List 就是OpenGL命令的缓存。Display List的使用对OpenGL的性能提高有很大帮助。这个很容易理解,想象一个复杂的物体,须要大量的OpenGL命令来描绘,若是画一次都须要从新调用OpenGL API,并把它转换成Vertex data,显然是很低效的,若是把他们缓存在Display List里,须要重绘的时候,发一个个命令通知OpenGL直接从Display List 读取缓存的Vertex Data,那势必会快不少,若是考虑到Opengl是基于C/S架构,能够支持远程Client,这个提高就更大了。Display也能够缓存BitMap 或 Image, 举个例子,假设要显示一篇文章,里面有不少重复的字符,若是每一个字符都去字库读取它的位图,而后告诉Opengl去画,那显然是很慢的。但若是将整个字库放到Display List里,显示字符时候只须要告诉Opengl这个字符的偏移量,OpenGL直接访问Display List,那就高效多了。

Pixel Data

      Pixle data 包括位图(bitmap), Image, 和任何用于绘制的Pixel数据(好比Fonts)。一般是以矩阵的形式存放在内存当中。经过Pxiel data, 咱们避免大量的图形绘制命令。同时经过现实世界中获取的纹理图片,能够将最终的物体渲染得更逼真。好比说画一堵墙,若是没有pixel data,咱们须要将每块砖头都画出来,也就是说须要大量的Vertex。但是若是经过一张现实生活中拍摄的砖墙的图片,只须要4个点画出一个大矩形,而后上面贴上纹理,显然,速度和效果都要好得多。

FrameBuffer

      Framebuffer就是Opengl用来存储结果的buffer。Opengl的frameBuffer类型有几种。Front Buffer 和 Back Buffer, 分别用于显示和绘制,二者经过swapBuffer 进行交换。Left Buffer 和 Right buffer, 用于真立体(须要带眼镜的那种) 图像的左右眼Buffer,Stencil buffer, 用于禁止在某些区域上进行绘制,想像一下若是在一件T恤上印上图案,你是否是须要一个镂空的纸板?这个纸板就是stencil buffer.

OK, 对Opengl rendering pipeline简单介绍到此,有兴趣的同窗能够阅读opengl的红包书或运行一些简单的例子来深刻理解Opengl。回到主题,仅仅使用Opengl 和 GPU 取代Skia 就可以大幅提高性能?答案固然不是,性能的优化很大程度上取决于应用,应用必须正确的使用Opengl命令才能发挥其最大效能。Android从pipeline 角度提供了两种机制来提高性能,一个就是咱们刚才说到的Display List,另外一个叫 Hardware Layer, 其实就是缓存的FrameBuffer, 好比说Android的墙纸,通常来讲,他是不会发生变化的,所以咱们能够将它缓存在Hardware Layer里,这张就不须要每次进行拷贝和重绘,从而大幅提高性能。

说白了,优化图形性能的核心在于 1)用硬件来减小CPU的参与,加速图形计算。 2)从软件角度,经过Display List 和 Hardware Layer, 将已经完成的工做尽量的缓存起来,只作必需要作的事情,尽量的减小运算量。

接下来看实现吧。

Canvas, Renderer, DisplayList, HardwareLayer 实现

这块代码至关的复杂,花了两天时间才把下面的图整理出来,但仍是没有把细节彻底吃透,简单的介绍一下框架和流程吧,若是有须要你们能够下来细看代码。  

 
 

 

图中上半部为Java 代码,下半部为Native层。先介绍里面出现的一些概念:

 Canvas

Canvas是Java层独有的概念,它为View提供了大部分图形绘制的接口。这个类主要用于纯软件的绘制,硬件加速的图形绘制则由HardwareCanvas取代。

HardwareCanvas,GLES20Canvas, GLES20RecordingCanvas

hardwareCanvas是一个抽象类,若是系统属性和应用程序指定使用硬件加速(现已成为默认),它将会被View(经过AttachInfo,如 下图所示) 引用来完成全部的图形绘制工做。GLES20Canvas 则是hardwareCanvas的实现,但它也只是一层封装而已,真正的实如今Native 层,经过jni (andriod_view_gles20Canvas.cpp)接口来访问底层的Renderer, 进而执行OpenGL的命令。 此外,GLES20Canvas还提供了一些静态接口,用于建立各种Renderer对象。

GLES20RecordingCanvas 继承GLES20Canvas, 经过它调用的OpenGL命令将会存储在DisplayList里面,而不会当即执行。

HardwareRenderer, GLRender, GL20Renderer

这三个类都是Java的Wrapper类,经过访问各类Canvas来控制绘制流程。详见下面两张时序图。

OpenGLRenderer, DisplayListRenderer, HardwareLayerRenderer

Java的HardwareCanvas 和 HardwareRenderer在底层的对应实现。OpenGLRenderer是基类,只有它直接访问底层OpenGL库。DisplayListRenderer 将View经过GLES20Canvas传过来的OpenGL 命令存在OpenGL的DisplayList中。而HardwareLayerRenderer 管理HardwareLayer的资源。

GLES20
      就是OpenGL ES 2.0 的API。它的实现通常由GPU的设计厂家提供,能够在设备的/system/lib/egl/ 找到它的so,名字为 libGLES_xxx.so, xxx 就是特定设备的代号,好比说,libGLES_gc.so 就是Vivante公司的GPU实现,libGLESv2_mali.so 就是ARM公司提供的Mali GPU的实现。它也能够由软件实现,好比说 libGLES_android.so, 是Google提供的软件实现。

EGL
      虽然对于上层应用来讲OpenGL接口是跨平台的,可是它的底层(GPU)实现和平台(SoC)是紧密相关的,因而OpenGL组织定义一套接口用来访问平台本地的窗口系统(native platform window system),这套接口就是EGL,好比说 eglCreateDisplay(), eglCreateSurface(), eglSwapBuffer()等等。EGL的实现通常明白libEGL.so, 放在/system/lib/egl/ 下面。

View, Canvas, Renderer, DisplayList, HardwareLayer 的关系以下图所示:

每一个View都对应一个DisplayList, 在Native层代码里管理。每一个View经过GLESRecordingCanvas 以及Native层对应的DisplayRenderer 将OpenGL命令存入DisplayList.最后View 经过GLES20Canvas 通知OpenGLRenderer 执行这些DisplayList 里面的OpenGL 命令。

 

 他们的生命周期以下图所示 (粉红表明 New, 黑色表明 Delete, 黄色表明Java类,蓝色表明C++, 绿色表明JNI).

 

  1. 若是系统支持硬件加速,ViewRootImpl首先建立一个GL20Renderer, 存在成员变量 mHardwareRenderer 里。
  2. Surafce是绘画的基础,若是不存在,HardwareRenderer会调用GLRenderer->createEglSurface() 建立一个新的Surface。
  3. Surface建立好后,接着生成Canvas,由于大部分应用程序不会直接操做Surface。
  4. 在Canvas的构造函数里,会依次建立Native层对应的OpenGLRenderer对象,它会直接访问库 libGLES_xxx提供的OpenGL ES API,来负责整个View树的绘制工做。
  5. 与此同时,一个CanvasFinalizer 成员对象也会被建立,它保存刚刚建立出来的OpenGLRenderer 指针,当它的finalizer()函数被调用是,负责将其销毁,回收资源。
  6. 在运行过程当中,若是窗口死掉或者不在可见,ViewRootImpl 会调用DestroyHardwareResource() 来释放资源。这里会最终将底层建立的Hardware Layer回收。
  7. 同时Java端的GLES20Layer 对象也会由于被赋值 NULL 被GC在未来回收。
  8. 接下来,ViewRootImpl调用 destroyHardwareRenderer() 将以前建立的Native Renderer(DisplayListRenderer,OpenGLRenderer)依次回收。
  9. 最后将Java 层的mHardwareRenderer 赋空,GC将会回收最开始建立的GL20Renderer对象。支持,一个View树的生命周期完成,全部资源清楚干净。

等等!好像少了点什么,怎么没有DisplayList? 前面不是说它是性能优化的帮手之一吗?对了,上面只介绍了绘制的开始和结尾,在View的生命周期中,还有最重要的一步,Draw 尚未被介绍,DisplayList 相关的操做就是在Draw()里面完成的。

 Draw 流程

  绕了好大一圈,终于回到最初的话题,Android是怎样将View画出来的? 让咱们按照图中的序号一一进行讲解。(黄色:Java, 绿色:C++,蓝色:JNI,粉色:New, 黑色:Delete).

 

  1. 首先,ViewRootImpl直接访问的HardwareRenderer 对象,首先在BeginFrame() 里获取EGLDisplay(用于显示) 和 初始化一个EGLSurface,OpenGL将在这个Surface上进行绘图。关于EGLDisplay 和 EGLSurface 将在 图解Android - Android GUI 系统 (3) - Surface Flinger 里详细描述。
  2. 咱们前面说过,Android 4.0 带来的图形性能的提高,有很大程度是DisplayList 带来的,因此,HardwareRenderer接下来就经过buildDisplayList() 建立整个View树的DisplayList. 最开始,GL20Renderer为View生成了一个GLES20DisplayList, 这是一个影子类,没有实际用途,主要用来管理Native层的DisplayList的销毁。
  3. View 调用刚刚生成的GLES20DisplayList的 start() 方法,真正开始构建DisplayList的上下文。obtain() 函数里会判断是否mCanvas 已经存在,若是没有则New 一个新的GLES20RecordingCanvas对象。
  4. 建立与GLES20RecordingCanvas 一一对应的Native层的 DisplayListRenderer。
  5. 3,4是一个递归的过程,直到全部的View的DisplayList上下文生成,View才真正开始Draw(), 这里,OpenGL命令(Op)不会被当即执行,而是被存储到刚刚生成的DisplayListRenderer里。
  6. 接着View调用GLESDisplayList的 end() 方法,这里Native层的DisplayList 对象才真正被建立出来,Java 和 Native 的 DisplayList 对象一一对应起来。
  7. end() 方法最后,调用GLES20RecordingCanvas.recycle() 方法,首先将Native的DisplayListRender 进行从新初始化,而后将刚才建立出来的临时对象赋NULL值(GLES20RecordingCanvas 和 GLES20DisplayList), 由于它们的使命已经完成,将View的OpenGL命令存储在Native层的DisplayList对象里。
  8. GC() 被调用后,GLES20RecordingCanvas 和 GLES20DisplayList 被释放,相应的Finalizer 对象方法会被调用,进而调用Natice层的deleteDisplayListDefered(), 将以前不用的DisplayList 送入 Caches的Gabage 队列中,等待回收。(这是在Native层实现的相似Java GC的机制)
  9. 到此,全部的DisplayList 上下文准备就绪。进入preDraw 状态,在GL20Renderer的 onPreDraw() 方法里,最终调用到底层的clearGarbage将上一次绘图操做的DisplayList在底层释放。
  10. HardwareRenderer调用 GLES20Canvas 的drawDisplayList(), 通知 Native层的 OpenGLRenderer,其最终调用每一个DisplayList的Replay() 方法执行OpenGL命令。
  11. 全部OpenGL命令执行完后, 图形被绘制到第一步生成的EGLSurface, hardwareRender 调用 GLES20Canvas 的 eglSwapBuffers() 交换Buffer,交由SurfaceFlinger 在下一个VSync到来时进行显示。

Hardware Layer

即使是使用了DisplayList, 对于复杂的图形,仍然须要执行大量的OpenGL命令,若是须要对这一部分进行优化,就须要使用到 HardwareLayer对绘制的图形进行缓存,若是图形不发生任何变化,就不须要执行任何OpenGL命令,而是将以前缓存在GPU内存的Buffer 直接与其余View进行合成,从而大大的提升性能。这些存储在GPU内部的Buffer就称为 Hardware Layer。除了Hardware Layer, Android 还支持Software Layer,和Hardware Layer 不一样之处在于,它不存在于GPU内部,而是存在CPU的内存里,所以它不通过前面所说的 Hardware Render Pipeline, 而是走Android最初的软件Render pipeline。无论是Hardware Layer 仍是 Software Layer, 在Draw() 内部均称为Cache,只要有Cache的存在,相对应的View将不用重绘,而是使用已有的Cache。Hardware Layer, Software Layer 和 Display List 是互斥的,同时只能有一种方法生效(固然,Hardware Layer的第一次绘制仍是经过Display List 完成),下表总结了它们的差异, 从中能够看到,Hardware Layer 对性能的提高是最大的,惟一的问题是占用GPU的内存(这也是为何显卡的内存变得愈来愈大的缘由之一),因此通常来讲,Hardware Layer使用在那些图片较为复杂,但不常常改变,有动画操做或与其余窗口有合成的场景,好比说WallPaper, Animation 等等。

 

  Enabled if GPU accelerated? Cached in

Performance(from Google I/O 2001)

Usage
Display List Hardware Accelerated = True Y GPU DisplayList 2.1 Complex View
Hardware Layer LayerType = HARDWARE Y GPU Memory 0.009 Complex View, Color Filter(颜色过滤), Alpha blending (透明度设置), etc. 
Software Layer LayerType = SOFTWARE N CPU Memory 10.3 No Hardware Accelerated, Color filter, Alpha blending, etc.

 

 

 

 

 

 

 

重绘 - Invaliate

前面介绍了View的第一次绘制的过程。可是一个View在运行中终究是要发生变化的,好比说,用户在TextView的文字发生了改变,或者动画致使View的尺寸发生变化,再或者说一个对话框弹出而后又消失,被遮挡的部分从新露了出来,这些都须要对View进行重绘。在介绍重绘以前,咱们先了解一下View内部的一些Flag 定义。

 

Flags  == 1 Set at Clear at
PFFLAG_HAS_BOUNDS

1: View has size and Position set.
0: setFrame() was not called yet

View::setFrame()  
PFFLAG_DRAWN

1: Has been drawn, only after which, invalidate() is valid.
0: not drawn, need to draw() later to show it.

ViewGroup::addViewInLayout
ViewGroup::attachViewToParent
View::draw()
View::buildDrawingCache()
View::getHardwareLayer()
View::invalidate()
View::setLeft/Right/Top/Bottom()

View::invalidate()

PFFLAG_DRAWING_CACHE_VALID

1: Has DisplayList / Hardware Layer / Software Layer  
0: No Cache, using Software rendering path.

View::GetHardwareLayer()
View::GetDisplayList()

View::invalidate(true)
View::invalidate(rect)
View::invalidateChild()

PFFLAG_INVALIDATED   View is specifically invalidated, not just dirty(child for instance).
DisplayList will be recreated if set. 
ViewGroup::addViewInLayout
ViewGroup::attachViewToParent
View::force/requestLayout()
View::invalidate()

View::draw()
PFLAG_DIRTY   Set to indicate that the view need to be redrawn.
(use displaylist cache if PFFLAG_INVALIDATED flag is false)
View::invalidate()

ViewGroup::addViewInLayout
ViewGroup::attachViewToParent
View::draw()
View::buildDrawingCache()
View::getHardwareLayer()
View::invalidate()
View::setLeft/Right/Top/Bottom()
View::getDisplayList()

PFLAG_DIRTY_OPAQUE DIRTY because of OPAQUE (hidden by others). View::invalidateChild()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

从上表能够看出,View经过内部这些Flag来控制重绘。基本上重绘分两种状况,一种是须要从新生成DisplayList, 另一种是使用以前已有的Cache,包括DisplayList或者是Hardware Layer。使用哪一种重绘方式由当前View的Flags,以及应用程序传入的参数决定。控制它的就是一组Invalidate() 函数。

 

void invalidate(boolean invalidateCache) {
        if (skipInvalidate()) { //若是View不可见,而且再也不动画退出过程当中(fade out),将不执行Invalidate().
            return;
        }
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || //DRAWN -> 已经被Draw()过
            (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||  //有Cache,且被要求从新刷新Cache
            (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) //没有正在Invalidate()中
        {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN;
            mPrivateFlags |= PFLAG_DIRTY;
            if (invalidateCache) { 
                mPrivateFlags |= PFLAG_INVALIDATED; 
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; //标记未来清除Cache,若是为false,则有系统根据Dirty Region决定是否须要从新生成DisplayList。
            }
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (!HardwareRenderer.RENDER_DIRTY_REGIONS) { //系统不支持Dirty Region,必须重绘整个区域, 基本不会进去
            }
            if (p != null && ai != null) {
                final Rect r = ai.mTmpInvalRect;
                r.set(0, 0, mRight - mLeft, mBottom - mTop);
                p.invalidateChild(this, r); //通知兄弟view(有共同的ViewParent(ViewGroup 或者 ViewRoot)进行 Invalidate. 
            }
        }
    }

假如全部的条件都支持重绘,便会调用到ViewParent的invalidateChild()方法。(ViewParent是一个接口类,它的实现类是ViewGroup 和 ViewRootImpl。)这个方法会从当前View开始,向上遍历到ViewRoot 或者 到某个ViewGroup的区域与当前View的Dirty区域没有重叠为止。途中的每一个ViewGroup都会被标记上Dirty。在接下来VSYNC的performDraw()里,ViewRootImpl 会遍历全部标记Dirty的ViewGroup,而后找到里面标记Dirty的View,只有这些View的DisplayList 被重建,而其余实际上没有变化的View(虽然它们在同一个ViewGroup里面),若是没有Hardware Layer, 只需从新执行对应Display List 里面的OpenGL 命令。经过这种方式,Android只重绘须要重绘的View,从软件层面将GPU的输入最小化,从而优化图形性能。

 

4. Windows 的管理

到此,咱们已经了解了一个Acitivty(Window)是如何画出来的,让咱们在简要重温一下这个过程:

  1. Acitivity建立, ViewRootImpl将窗口注册到WindowManager Service,WindowManager Service 经过SurfaceFlinger 的接口建立了一个Surface Session用于接下来的Surface 管理工做。
  2. 由Surface Flinger 传上来的VSYNC事件到来,Choreographer 会运行ViewRootImpl 注册的Callback函数, 这个函数会最终调用 performTraversal 遍历View树里的每一个View, 在第一个VSYNC里,WindowManager Service 会建立一个SurafceControll 对象,ViewRootImpl 根据Parcel返回的该对象生成了Window对应的Surface对象,经过这个对象,Canvas 能够要求Sruface Flinger 分配OpenGL绘图用的Buffer。
  3. View树里的每一个View 会根据须要依次执行 measure(),layout() 和 draw() 操做。Android 在3.0以后引入了硬件加速机制,为每一个View生成DisplayList,并根据须要在GPU内部生成Hardware Layer,从而充分利用GPU的功能提高图形绘制速度。
  4. 当某个View发生变化,它会调用invalidate() 请求重绘,这个函数从当前View 出发,向上遍历找到View Tree中全部Dirty的 View 和 ViewGroup, 根据须要从新生成DisplayList, 并在drawDisplayList() 函数里执行OpenGL命令将其绘制在某个Surface Buffer上。
  5. 最后,ViewRootImpl 调用 eglSwapBuffer 通知OpenGL 将绘制的Buffer 在下一个VSync点进行显示。

注意的是,上面讨论的只是一个窗口的流程,而Android是个多窗口的系统,窗口之间可能会有重叠,窗口切换会有动画产生,窗口的显示和隐藏都有可能会致使资源的分配和释放,这一切须要有一个全局的服务进行统一的管理,这个服务就是咱们大名鼎鼎的Window Manager Service (简写 WMS).

其实Window Manager Service 的工做不只仅是管理窗口,还会跟不少其余服务打交道,如 InputManager Service, AcitivityManager Service 等等,但本章只讨论它在Window Manager 方面的工做,下图中红色标记部分。

 Layout

 首先来看Layout。Layout 是Window Manager Service 重要工做之一,它的流程以下图所示:

 

  • 每一个View将指望窗口尺寸交给WMS(WindowManager Service).
  • WMS 将全部的窗口大小以及当前的Overscan区域传给WPM (WindowPolicy Manager).
  • WPM根据用户配置肯定每一个Window在最终Display输出上的位置以及须要分配的Surface大小。
  • 返回这些信息给每一个View,他们将在给会的区域空间里绘图。

Android里定义了不少区域,以下图所示 

                                          

Overscan:
    Overscan 是电视特有的概念,上图中黄色部分就是Overscan区域,指的是电视机屏幕四周某些不可见的区域(由于电视特性,这部分区域的buffer内容显示时被丢弃),也意味着若是窗口的某些内容画在这个区域里,它在某些电视上就会看不到。为了不这种状况发生,一般要求UI不要画在屏幕的边角上,而是预留必定的空间。由于Overscan的区域大小随着电视不 同而不一样,它通常由终端用户经过UI指定,(好比说GoogleTV里就有肯定Overscan大小的应用)。

OverscanScreen, Screen: 
   
OverscanScreen 是包含Overscan区域的屏幕大小,而Screen则为去除Overscan区域后的屏幕区域, OverscanScreen > Screen.

Restricted and Unrestricted:
    某些区域是被系统保留的,好比说手机屏幕上方的状态栏(如图纸绿色区域)和下方的导航栏,根据是否包括这些预留的区域,Android把区域分为Unrestricted Area 和 Resctrited Aread, 前者包括这部分预留区域,后者则不包含, Unrestricted area > Rectricted area。

mFrame, mDisplayFrame, mContainingFrame
   
Frame指的是一片内存区域, 对应于屏幕上的一块矩形区域. mFrame的大小就是Surface的大小, 如上上图中的蓝色区域. mDisplayFrame 和 mContainingFrame 通常和mFrame 大小一致. mXXX 是Window(ViewRootImpl, Windowstate) 里面定义的成员变量.

mContentFrame, mVisibleFrame
   
一个Surface的全部内容不必定在屏幕上都获得显示, 与Overscan重叠的部分会被截掉, 系统的其余窗口也会遮挡掉部分区域 (好比短信窗口,ContentFrame是800x600(没有Status Bar), 但当输入法窗口弹出是,变成了800x352), 剩下的区域称为Visible Frame, UI内容只有画在这个区域里才能确保可见. 因此也称为Content Frame. mXXX也是Window(ViewRootImpl, WindowState) 里面定义的成员变量.

Insects
   
insets的定义如上图所示, 用了表示某个Frame的边缘大小.

Layout 在WMS 内部的时序以下图所示,外部调整Overscan参数或View内部主动调用requestLayout() 都会触发WMS的从新layout,layout完成后,WMS会经过IWindow的resized()接口通知ViewRoot, 最终会调用requestLayout(), 并在下一个VSYNC 事件到来时更新。

 计算Layout主要有图中三个红色的函数完成,它们代码不少,涉及到不少计算,但只要对着咱们上面给的三个图来看,不难看出它的意思,本文将不详细深刻。

Animation

Animation的原理很简单,就是定时重绘图形。下面的类图中给出了Android跟Animation相关的类。

Animation:
    Animation抽象类,里面最重要的一个接口就是applyTranformation, 它的输入是当前的一个描述进度的浮点数(0.0 ~ 1.0), 输出是一个Transformation类对象,这个对象里有两个重要的成员变量,mAlpha 和 mMatrix, 前者表示下一个动画点的透明度(用于灰度渐变效果),后者则是一个变形矩阵,经过它能够生成各类各样的变形效果。Android提供了不少Animation的具体实现,好比RotationAnimation, AlphaAnimation 等等,用户也能够实现本身的Animation类,只须要重载applyTransform 这个接口。注意,Animation类只生成绘制动画所需的参数(alpha 或 matrix),不负责完成绘制工做。完成这个工做的是Animator.

Animator:
   
控制动画的‘人’, 它一般经过向定时器Choreographer 注册一个Runnable对象来实现定时触发,在回调函数里它要作两件事情:1. 从Animation那里获取新的Transform, 2. 将Transform里的值更新底层参数,为接下来的重绘作准备。动画能够发生在Window上,也能够发生在某个具体的View。前者的动画会经过SurfaceControl直接在某个Surface上进行操做(会在SurfaceFlinger里详细描述),好比设置Alpha值。后者则经过OpenGL完成(生成咱们前面提过的DisplayList).

WindowStateAnimator, WindowAnimator,  AppWindowAnimator:
   
针对不一样对象的Animator. WindowAnimator, 负责整个屏幕的动画,好比说转屏,它提供Runnable实现。WindowStateAnimator, 负责ViewRoot,即某一个窗口的动画。AppWindowAnimator, 负责应用启动和退出时候的动画。这几个Animator都会提供一个函数,stepAnimationLocked(), 它会完成一个动画动做的一系列工做,从计算Transformation到更新Surface的Matrix.

 

具体来看一下Window的Animation和View的Animation

  1. WindowManagerService 的 scheduleAnimationLocked() 将windowAnimator的mAnimationRunnable 注册到定时器 Choreographer.
  2. 若是应用程序的res/anim/下有xml文件定义animation,在layout过程当中,会经过appTransition类的loadAnimation() 函数将XML转换成 Animation_Set 对象,它里面能够包含多个Animation。
  3. 当下一个VSYNC事件到来,刚才注册的Callback函数被调用,即WindowAnimator的mAnimationRunnable,里面调用 animateLocked(), 首先,打开一个SurfaceControl的动画会话,animationSession。
  4. 首先执行的动画是 appWindowAnimator, 若是刚才loadAnimation() 返回的animation不为空,便会走到Animation的getTransform() 获取动画的参数,这里可能会同时有多个动画存在,经过Transform的compose()函数将它们最终合为一个。
  5. 接下来上场的是DisplayContentsAnimator, 它主要用来实现灰度渐变和转屏动画。一样,首先经过stepAnimation() 获取动画变形参数,而后经过SurfaceControl将其更新到SrufaceFlinger内部对应的Layer. 这里首先完成的是转屏的动画
  6. 而后就是每一个窗口的动画。后面跟着的 perpareSurfaceLocked() 则会更新参数。
  7. Wallpaper的动画。
  8. 接下来,就是上面提到的DisplayContentsAnimator的第二部分,经过DimLayer实现渐变效果。
  9. Surface的控制完成后,关闭对话。而后scheduleAnimationLocked() 规划下一步动画。
  10. 接下来的performDraw()会把全部更新参数的View,或Surface交给OpenGL或HWcomposer进行处理,因而咱们就看到了动画效果。

 View 的动画实现步骤与Windows 相似,有兴趣的同窗能够去看View.java 的 drawAnimation() 函数。

 管理窗口

WMS 里面管理着各式各样的窗口, 以下表所示(在WindowManagerService.java 中定义)

   类型 用途
mAnimatingAppToken   ArrayList<AppWindowToken> 正在动画中的应用
mExistingAppToken ArrayList<AppWindowToken> 退出但退出动画尚未完成的应用。
mResizingWindows ArrayList<WindowState> 尺寸正在改变的窗口,当改变完成后,须要通知应用。
mFinishedStarting ArrayList<AppWindowToken> 已经完成启动的应用。
mPendingRemove ArrayList<WindowState> 动画结束的窗口。
mLosingFocus  ArrayList<WindowState>  失去焦点的窗口,等待得到焦点的窗口进行显示。
mDestorySurface    ArrayList<WindowState> 须要释放Surface的窗口。
mForceRemoves  ArrayList<WindowState> 须要强行关闭的窗口,以释放内存。
mWaitingForDrawn ArrayList<Pair<WindowState, IRemoteCallback>> 等待绘制的窗口
mRelayoutWhileAnimating ArrayList<WindowState> 请求relayout但此时仍然在动画中的窗口。
mStrictModeFlash StrictModeFlash 一个红色的背景窗口,用于提示可能存在的内存泄露。
mCurrentFocus   WindowState 当前焦点窗口
mLastFocus WindowState 上一焦点窗口
mInputMethodTarget WindowState 输入法窗口下面的窗口。
mInputMethodWindow  WindowState 输入法窗口
mWallpaperTarget WindowState 墙纸窗口
mLowerWallpaperTarget WindowState 墙纸切换动画过程当中Z-Order 在下面的窗口
mHigherWallpaperTarget WindowState 墙纸切换动画过程当中Z-Order 在上面的窗口
     
     

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

能够看到这里大量的用到了队列,不一样的窗口,或同一窗口在不一样的阶段,可能会出如今不一样的队列里。另外由于WindowManager Service 的服务可能被不少个线程同时调用,在这种复杂的多线程环境里,经过锁来实现线程安全很是难以实现,一不当心就可能致使死锁,因此在 WindowManager 内专门有一个执行线程(WM Thread)来将全部的服务请求经过消息进行异步处理,实现调用的序列化。队列是实现异步处理的经常使用手段。队列加Looper线程是Android 应用经常使用的设计模型。

此外,WindowManager还根据Window的类型进行了分类(在WindowManager.java),以下表,

类型 常量范围 子类 常量值 说明 例子
APPLICATION_WINDOW 1~99 TYPE_BASE_APPLICATION 1     
    TYPE_APPLICATION 2  应用窗口  大部分的应用程序窗口
    TYPE_APPLICATION_STARTING 3  应用程序的Activity显示以前由系统显示的窗口  
    LAST_APPLICATION_WINDOW  99    
SUB_WINDOW 1000~1999 FIRST_SUB_WINDOW 1000    
    TYPE_APPLICATION_PANEL 1000  显示在母窗口之上,遮挡其下面的应用窗口。  
    TYPE_APPLICATION_MEDIA 1001  显示在母窗口之下,若是应用窗口不挖洞,即不可见。 SurfaceView,在小窗口显示时设为MEDIA, 全屏显示时设为PANEL
    TYPE_APPLICATION_SUB_PANEL 1002         
    TYPE_APPLICATION_ATTACHED_DIALOG 1003    
    TYPE_APPLICATION_MEIDA_OVERLAY 1004  用于两个SurfaceView的合成,若是设为MEDIA,
则上面的SurfaceView 挡住下面的SurfaceView
 
SYSTEM_WINDOW   2000~2999 TYPE_STATUS_BAR   2000 顶部的状态栏  
    TYPE_SEARCH_BAR 2001 搜索窗口,系统中只能有一个搜索窗口  
    TYPE_PHONE 2002  电话窗口  
    TYPE_SYSTEM_ALERT 2003 警告窗口,在全部其余窗口之上显示   电量不足提醒窗口
    TYPE_KEYGUARD 2004 锁屏界面  
    TYPE_TOAST 2005 短时的文字提醒小窗口  
    TYPE_SYSTEM_OVERLAY 2006 没有焦点的浮动窗口  
    TYPE_PRIORITY_PHONE 2007 紧急电话窗口,能够显示在屏保之上  
    TYPE_SYSTEM_DIALOG   2008 系统信息弹出窗口  好比SIM插上后弹出的运营商信息窗口
    TYPE_KEYGUARD_DIALOG 2009 跟KeyGuard绑定的弹出对话框 锁屏时的滑动解锁窗口
    TYPE_SYSTEM_ERROR 2010 系统错误提示窗口  ANR 窗口
    TYPE_INPUT_METHOD 2011 输入法窗口,会挤占当前应用的空间  
    TYPE_INPUT_METHOD_DIALOG 2012 弹出的输入法窗口,不会挤占当前应用窗口空间,在其之上显示  
    TYPE_WALLPAPER 2013  墙纸  
    TYPE_STATUS_BAR_PANEL 2014 从状态条下拉的窗口  
    TYPE_SECURE_SYSTEM_OVERLAY 2015 只有系统用户能够建立的OVERLAY窗口  
    TYPE_DRAG 2016 浮动的可拖动窗口 360安全卫士的浮动精灵
    TYPE_STATUS_BAR_PANEL 2017    
    TYPE_POINTER 2018 光标  
    TYPE_NAVIGATION_BAR 2019    
    TYPE_VOLUME_OVERLAY 2020 音量调节窗口  
    TYPE_BOOT_PROGRESS 2021 启动进度,在全部窗口之上  
    TYPE_HIDDEN_NAV_CONSUMER 2022 隐藏的导航栏  
    TYPE_DREAM 2023 屏保动画  
    TYPE_NAVIGATION_BAR_PANEL 2024 Navigation bar 弹出的窗口 好比说应用收集栏
    TYPE_UNIVERSAL_BACKGROUND   2025    
    TYPE_DISPLAY_OVERLAY 2026 用于模拟第二显示设备  
    TYPE_MAGNIFICATION 2027 用于放大局部  
    TYPE_RECENTS_OVERLAY 2028 当前应用窗口,多用户状况下只显示在用户节目  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

windowManager Service 会根据窗口的类型值来决定Z-Order (于常量值无关,值大说明是后面Android版本添加的,好比说2025~2028就是4.3 新加的)。好比说SurfaceView.java 里的一个函数,

    public void setZOrderOnTop(boolean onTop) {
        if (onTop) {
            mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; //PANEL在上面
            // ensures the surface is placed below the IME
            mLayout.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
        } else {
            mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; //MEDIA类型窗口在应用窗口之下,应用必需挖洞(设Alpha值)才能露出它。
            mLayout.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
        }
    }

这些类型最终在WindowManager 内部转换成几个Z-Order 值,mBaseLayer, mSubLayer, mAnimationLayer, 分别代表主窗口,子窗口(附加在主窗口之上),和动画窗口的Z-Order值(越大越在上边)。不一样的窗口类型在不一样的硬件产品上有不一样的定义,所以它是实如今WindowManagerPolicy里的windowTypeToLayerLw(), 举PhoneWindowManager 为例,它的ZOrder 顺序是:

Univese background < Wallpaper < Phone < Search Bar < System Dialog < Input Method Window < Keyguard < Volume < System Overlay < Navigation < System Error <  < Display Overlay< Drag < Pointer < Hidden NAV consumer, 

因此,咱们若是要在手机锁屏时显示歌曲播放进度,就必须给这个窗口分配一个大于Keyguard的type,如 system overlay 等。

一个Window能够有若干个Sub Window, 他们和主窗口的ZOrder关系是

Media Sublayer(-2) < Media Overlay sublayer (-1) < Main Layer(0) < Attached Dialog (1) < Sub panel Sublayer (2)

经过 "adb shell dumpsys window" 能够查看系统当前运行的窗口的ZOrder 和 Visibility, 好比下面就是在短信输入界面下运行“dumpsys" 得到的结果,

   1  Window #0 Window{4ea4e178 u0 Keyguard}:
   2     mBaseLayer=121000 mSubLayer=0 mAnimLayer=121000+0=121000 mLastLayer=121000
   3     mViewVisibility=0x8 mHaveFrame=true mObscured=false
   4   Window #1 Window{4ea4aa7c u0 InputMethod}:
   5     mBaseLayer=101000 mSubLayer=0 mAnimLayer=21020+0=21020 mLastLayer=21020
   6     mViewVisibility=0x0 mHaveFrame=true mObscured=false
   7   Window #2 Window{4ec1a150 u0 com.android.mms/com.android.mms.ui.ComposeMessageActivity}:
   8     mBaseLayer=21000 mSubLayer=0 mAnimLayer=21015+0=21015 mLastLayer=21015
   9     mViewVisibility=0x0 mHaveFrame=true mObscured=false
  10   Window #3 Window{4ea7c714 u0 com.android.mms/com.android.mms.ui.ConversationList}:
  11     mBaseLayer=21000 mSubLayer=0 mAnimLayer=21010+0=21010 mLastLayer=21015
  12     mViewVisibility=0x8 mHaveFrame=true mObscured=true
  13   Window #4 Window{4eaedefc u0 com.android.launcher/com.android.launcher2.Launcher}:
  14     mBaseLayer=21000 mSubLayer=0 mAnimLayer=21005+0=21005 mLastLayer=21010
  15     mViewVisibility=0x8 mHaveFrame=true mObscured=true
  16   Window #5 Window{4ea17064 u0 jackpal.androidterm/jackpal.androidterm.Term}:
  17     mBaseLayer=21000 mSubLayer=0 mAnimLayer=21000+0=21000 mLastLayer=22000
  18     mViewVisibility=0x8 mHaveFrame=true mObscured=true

能够看到:

  1. 当前只有两个窗口可见, InputMethod 和 com.android.mms/com.android.mms.ui.ComposeMessageActivity, mViewVisibility = 0 (0在View.java定义是可见), 而其余 mViewVisibility=0x8 (定义在View.java里, 意思是”GONE").
  2. InputMethod(mLastLayer=21020) 在 ComposeMessageActivity(mLastLayer=21015) 之上. 细心的同窗可能会发现,InputMethod的mBaseLayer = 101000,为何mLastLayer小那么多?由于mLastLayer才是真正的z-order, 它通过了WidowManager的调整。当用户点击输入框,View会经过InputMethodManager 发送一个showSoftInput命令,通过InputManagerService的处理,输入法窗口(KeyboardView)会被加入到WndowManager Service里,WindowManager Service 会寻找它的目标窗口, 即须要输入的窗口,(遍历WindowList 而后根据窗口的Flags判断),而后将输入法窗口的mLayer值改成 目标窗口的mLayer + 5,这样,输入法窗口就显示在了目标窗口之上。在这里,输入法窗口存在于InputMethodManagerService 的上下文里,而不是某个Activity,因此他能够跟任何须要输入法的Activity绑定。其余一些应用,好比说PiP(三星的Galaxy S3能够在一个浮动的小窗口里显示视频)也是运用了相似的方法来实现的。Android的输入法是一个很是值得研究的模块,留到后面探讨。

因此,WindowManager Service 是经过调整窗口的mViewVisibility 和 mLayer 值来实现窗口重叠。最后给出跟Z-order相关的类图。

 

 图中序号表示输入法窗口找到它的目标窗口的过程:

  1. WindowManagerService 找到默认输出(Default Display) 的DisplayContents成员变量。
  2. 里面有一个数组WindowList-mWindows, 按照Z-Order顺序保存了当前在这个Display上输出的全部窗口WindowState。
  3. 遍历全部的WindowState,判断它的mAppToken是否和输入法窗口的mAppToken一致。呼起输入法窗口的窗口会将本身的mAppToken拷贝给它。
  4. 相同的Token下,可能有多个窗口,经过WindowToken.windows 或者 AppWindowToken.allAppWindows, 能够找到他们。

 

WindowManager Service的介绍暂告一段落,它与其余重要的Service,SurfaceFlinger, ActivityManager, InputManager, PowerManager, WatchDog 之间的关系将在其余文章介绍。

相关文章
相关标签/搜索