在图解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
在图解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
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对象。数据库
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
全部的图像显示输出都是由时钟驱动的,这个驱动信号称为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 来讨论。
直接从前面提到的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的什么改动带来的?咱们立刻揭晓。。。(这块非本人工做领域,网上相关的资料也不多,因此纯凭我的砖研,欢迎拍砖指正)
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, 将已经完成的工做尽量的缓存起来,只作必需要作的事情,尽量的减小运算量。
接下来看实现吧。
这块代码至关的复杂,花了两天时间才把下面的图整理出来,但仍是没有把细节彻底吃透,简单的介绍一下框架和流程吧,若是有须要你们能够下来细看代码。
图中上半部为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).
等等!好像少了点什么,怎么没有DisplayList? 前面不是说它是性能优化的帮手之一吗?对了,上面只介绍了绘制的开始和结尾,在View的生命周期中,还有最重要的一步,Draw 尚未被介绍,DisplayList 相关的操做就是在Draw()里面完成的。
绕了好大一圈,终于回到最初的话题,Android是怎样将View画出来的? 让咱们按照图中的序号一一进行讲解。(黄色:Java, 绿色:C++,蓝色:JNI,粉色:New, 黑色:Delete).
即使是使用了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. |
前面介绍了View的第一次绘制的过程。可是一个View在运行中终究是要发生变化的,好比说,用户在TextView的文字发生了改变,或者动画致使View的尺寸发生变化,再或者说一个对话框弹出而后又消失,被遮挡的部分从新露了出来,这些都须要对View进行重绘。在介绍重绘以前,咱们先了解一下View内部的一些Flag 定义。
Flags | == 1 | Set at | Clear at |
PFFLAG_HAS_BOUNDS | 1: View has size and Position set. |
View::setFrame() | |
PFFLAG_DRAWN | 1: Has been drawn, only after which, invalidate() is valid. |
ViewGroup::addViewInLayout |
View::invalidate() |
PFFLAG_DRAWING_CACHE_VALID | 1: Has DisplayList / Hardware Layer / Software Layer |
View::GetHardwareLayer() |
View::invalidate(true) |
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 |
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的输入最小化,从而优化图形性能。
到此,咱们已经了解了一个Acitivty(Window)是如何画出来的,让咱们在简要重温一下这个过程:
注意的是,上面讨论的只是一个窗口的流程,而Android是个多窗口的系统,窗口之间可能会有重叠,窗口切换会有动画产生,窗口的显示和隐藏都有可能会致使资源的分配和释放,这一切须要有一个全局的服务进行统一的管理,这个服务就是咱们大名鼎鼎的Window Manager Service (简写 WMS).
其实Window Manager Service 的工做不只仅是管理窗口,还会跟不少其余服务打交道,如 InputManager Service, AcitivityManager Service 等等,但本章只讨论它在Window Manager 方面的工做,下图中红色标记部分。
首先来看Layout。Layout 是Window Manager Service 重要工做之一,它的流程以下图所示:
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的原理很简单,就是定时重绘图形。下面的类图中给出了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
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
能够看到:
因此,WindowManager Service 是经过调整窗口的mViewVisibility 和 mLayer 值来实现窗口重叠。最后给出跟Z-order相关的类图。
图中序号表示输入法窗口找到它的目标窗口的过程:
WindowManager Service的介绍暂告一段落,它与其余重要的Service,SurfaceFlinger, ActivityManager, InputManager, PowerManager, WatchDog 之间的关系将在其余文章介绍。