第六篇 ANDROID窗口系统机制之显示机制

ANDROID的显示系统是整个框架中最复杂的系统之一,涉及包括窗口管理服务、VIEW视图系统、SurfaceFlinger本地服务、硬件加速等。窗口管理服务与SurfaceFlinger本地服务都属于系统服务,客户端采用远程代理模式访问服务,而这部分机制在上一篇博文《窗口管理服务实现机制》已经分析过,本篇主要解析视图如何绘制相关的部分。

窗口中显示的页面和控件以树的形式组织成一颗以主视图为根的视图树,系统要显示输出时统一调用主视图的draw 函数,由主视图的draw 函数负责各个子视图(如Layout和Widgets等)的递归绘制和效果处理。

主视图的draw 函数由ViewRootImpl对象的draw 函数调用,在draw 函数中根据硬件加速与否采用不同的方式创建不同的画布,并作为参数调用相同的相同的视图绘制函数(视图的draw 接口)。

在硬件加速打开的情况下, 采用HardwareRenderer对象创建的画布进行硬件加速绘制,否则则采用ViewRootImpl对象对应的Surface对象创建的画布进行软绘制,画布由Surface对象的lockCanvas函数经过JNI调用获取绘制输出缓冲区后初始化一个bitmap,并赋值给Surface对象相关的Canvas对象中的mNativeCanvas, 然后返回Canvas对象。以后视图的绘制实际上是在Canvas对象的bitmap上绘制。

在硬件加速的情况下绘制分两步,第一步是把视图的各种绘制函数作为绘制指令(包含操作指令和 绘制参数)写到DisplayListRenderer对象的SkWriter32对象中,第二步是读取SkWriter32对象中保存的绘制指令调用OPENGL相关函数完成实际绘制。把视图的各种绘制函数翻译成绘制指令保存起来,可以达到重用的目的,在视图绘制过一次且没有或很少发生改变的情况下,在视图重绘时,可以重用原先DisplayListRenderer对象保存的操作指令,不用再按照原先复杂的操作顺序(每一步都需要经过JAVA对象的操作函数通过JNI调用C++本地对象的操作函数)继续重新绘制一遍,而只需对于发生改变的部分按照上面的顺序进行录制及绘制,而对于没有发生改变的视图把原先保存的操作指令重新读取出来重放一次就可以了,提高视图的显示速度。

整个视图绘制相关的JAVA类图如下图:

视图绘制相关的对象主要由五个对象完成。视图(view)和画布(canvas)对象,视图在具体画布对象上完成各种绘制图形操作,根据不同需求视图绘制可以使用不同的画布对象(当前有三个具体的画布对象,两个硬件加速使用的继承于HardwareCanvas的GLES20Canvas和GLES20RecordingCanvas,不使用硬件加速的CompatibleCanvas)。通过使用抽象接口和桥接设计模式(Bridge模式)视图的绘制操作可以不管具体的具体画布对象是什么,也就是不论是否使用硬件加速与否,统一由一个绘制函数完成当前视图及子视图的递归绘制,只是根据函数参数传进去不同的画布对象。

另外三个对象是Gl20Renderer、GLES20DisplayList及GLES20RenderLayer.

Gl20Renderer对象相当于硬件加速视图绘制模型的呈现引擎,负责整个与硬件加速相关的视图绘制过程,包括创建具体的DisplayList及HardwareLayer对象,调用视图相关函数完成DisplayList命令的录制和在HardwareLayer上的绘制,DisplayList录制命令的重放及HardwareLayer在主显示缓冲区上的复合等工作。Gl20Renderer对象对应的画布为GLES20Canvas类型,在Gl20Renderer对象画布上的绘制实际绘制在OPENGL绘制上下文对应的主缓冲区上。Gl20Renderer派生自GlRenderer,GlRenderer又派生自HardwareRenderer。HardwareRenderer可以是说是DisplayList及HardwareLayer对象的工厂,采用了工厂方法创建了具体的DisplayList及HardwareLayer对象,也采用简单工厂方法实例化其子类Gl20Renderer。Gl20Renderer对象对应的GLES20Canvas画布也是采用工厂方法实例化的。

GLES20DisplayList及GLES20RenderLayer具体负责视图的绘制流程。

GLES20DisplayList类是DisplayList的具体类,GLES20DisplayList对象创建具体的DisplayList对象及绘制用的画布(GLES20RecordingCanvas画布),完成视图绘制操作的DisplayList命令录制等工作。

GLES20RenderLayer类是HardwareLayer的具体类,负责创建硬件Layer层和绘制用到的画布(GLES20Canvas)等工作。为了有效支持视图的多层绘制,在3.0以上版本视图对象可以通过创建一个HardwareLayer层完成视图的图形在硬件纹理上的绘制操作或者其它特效操作,这就是GLES20RenderLayer对象的作用,创建独立的层并返回相应的画布供视图绘制使用。GLES20RecordingCanvas画布类是GLES20Canvas画布类的派生类。

DisplayList及HardwareLayer类与相关画布的关系也相当于工厂关系,也是采用了工厂方法由其子类实例化具体的画布,DisplayList的子类GLES20DisplayList还采用了对象池的方法返回对应画布的实例

JAVA对象创建的每种画布与C++ Render对象一一对应,通过JNI 调用相应的Render 对象完成实际绘制工作。GLES20DisplayList对象创建的画布对应下面的DisplayLis呈现对象(DisplayListRenderer),实际负责视图绘制操作的录制,视图的绘制命令最后保存在本地DisplayList对象中,绘制命令的重放由本地DisplayList对象的replay函数完成。在GLES20RenderLayer对象创建的画布上的绘制由下面的Layer呈现对象(LayerRender)完成。在Gl20Renderer创建的画布上的绘制由其对应的呈现对象(OpenGLRenderer)完成,负责在OPENGL绘制上下文对应的主缓冲区上的绘制。下面是C++层的类图。GLES20DisplayList和Gl20Renderer都派生自OpenGLRenderer。

GLES20DisplayList和GLES20RenderLayer对应的画布对象及Gl20Renderer对应的画布对象分别由GLES20Canvas类的不同构造函数进行实例化。

如下是三个画布对象的构造函数代码片断。

/**

* Creates a canvas to render directly on screen.

这个构造函数构造直接呈现在屏幕上的画布。

该构造函数由Gl20Renderer对象调用createCanvas函数调用。

*/

GLES20Canvas(boolean translucent) {

this(false, translucent);

}

/**

* Creates a canvas to render into an FBO.

这个构造函数创建一个呈现在硬件纹理 FramebuferObject(FBO)上的画布,其通过nCreateLayerRenderer(layer) JNI本地接口创建一个LayerRender C++ 对象。

该构造函数由GLES20RenderLayer对象实例化时调用,由GLES20RenderLayer对象的start函数返回其创建的画布供视图绘制使用。

*/

GLES20Canvas(int layer, boolean translucent) {

mOpaque = !translucent;

mRenderer = nCreateLayerRenderer(layer);

setupFinalizer();

}

/*

这个构造函数在参数record为true时创建一个视图绘制用的DisplayList画布。构造函数通过nCreateDisplayListRenderer JNI本地接口创建一个GLES20DisplayList C++ 对象。GLES20RecordingCanvas对象的实例化就是这种情况,GLES20RecordingCanvas对象由GLES20DisplayList对象的start函数获取并返回。

参数record为flase时创建一个直接呈现在屏幕上的画布 。构造函数调用nCreateRenderer() JNI本地接口创建一个OpenGLRenderer C++ 对象。

*/

protected GLES20Canvas(boolean record, boolean translucent) {

mOpaque = !translucent;

if (record) {

mRenderer = nCreateDisplayListRenderer();

} else {

mRenderer = nCreateRenderer();

}

setupFinalizer();

}

/*

这是上面构造函数调用的对应的JNI接口,分别创建了不同C++ 呈现对象。

*/

static OpenGLRenderer* android_view_GLES20Canvas_createLayerRenderer(JNIEnv* env,

jobject clazz, Layer* layer) {

if (layer) {

return new LayerRenderer(layer);

}

return NULL;

}

static OpenGLRenderer* android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env,

jobject clazz) {

return new DisplayListRenderer;

}

static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject clazz) {

RENDERER_LOGD("Create OpenGLRenderer");

return new OpenGLRenderer;

}

视图在GLES20DisplayList及GLES20RenderLayer对象的画布上的绘制流程相似,视图在DisplayList对象对应画布上的绘制(绘制指令的录制)通过调用视图的getDisplayList函数完成;视图在GLES20RenderLayer对象(HardwareLayer)对应画布上的绘制通过调用视图的getHardwareLayer函数完成。

支持硬件加速的VIEW图形绘制序列图

1、在ViewRootImpl对象的draw 函数调用过程中,在硬件加速打开的情况下, 调用Gl20Renderer 的draw 函数(mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this, currentDirty)), draw 函数第一个参数为要显示的视图,第二个参数传进去的是ViewRootImpl对象的mAttachInfo对象,第三个参数需要类型为HardwareDrawCallbacks的回调,而HardwareDrawCallbacks在ViewRootImpl类中定义,因此这里传进去this,第四个参数为要绘制的脏区域;

2、draw 函数调用onPreDraw(dirty)函数,其实际调用与Gl20Renderer对象对应的画布对象(类型为GLES20Canvas)的onPreDraw函数,在 画布对象的onPreDraw函数中根据dirty是否为NULL调用JNI本地接口nPrepareDirty或者nPrepare,通过本地对象OpenGLRenderer设置要呈现的脏区域 ;

3、draw 函数接着先调用画布的保存函数(save)保存当前画布的上下文,接着调用回调的onHardwarePreDraw函数,里面调用画布translate 函数进行坐标定位工作;

4、draw 函数接着调用视图的getDisplayList函数进行视图绘制操作的录制工作,通过视图在DisplayList对象创建的画布上的绘制完成操作指令的录制,也就是把操作指令保存到C++层的DisplayList对象中的;

5、在视图的getDisplayList函数中,首先判断当前视图的DisplayList对象是否存在和是否有效(本视图以前已经正常绘制过,操作指令已经保存) ,如果是则调用ViewGroup的dispatchGetDisplayList函数来循环调用各个子视图的getDisplayList函数,然后返回跳到第15步执行;

6、getDisplayList函数调用Gl20Renderer 对象的createDisplayList函数创建DisplayList对象,具体类型为GLES20DisplayList;

7、getDisplayList函数接着调用DisplayList对象的start函数返回画布对象,实际画布类型为GLES20RecordingCanvas,对应的C++呈现对象为DisplayListRenderer;

8、getDisplayList函数调用GLES20RecordingCanvas画布对象的setViewport函数;

9 、getDisplayList函数接着调用GLES20RecordingCanvas对象的onPreDraw函数,通过 JNI 的nPrepare本地接口调用本地对象DisplayListRenderer设置要显示的脏区域,参数为null设置为整个显示区域;

10、getDisplayList函数接着调用视图的computeScroll函数计算滚动位置;

11、getDisplayList函数接着调用GLES20RecordingCanvas画布对象的translate 函数,该函数实际一方面通过JNI接口调用本地DisplayListRenderer对象把该操作翻译成操作指令保存到本地mWriter对象中,另一方面还调用父类OpenGLRenderer的translate(dx, dy)函数完成坐标转换工作;对于显示区域方面的操作DisplayListRenderer对象都是先保存操作指令,然后调用OpenGLRenderer类的相同函数完成实际工作;

12、getDisplayList函数接着调用视图的dispatchDraw(canvas)函数或着draw(canvas)函数进行视图的图形元素在画布上(GLES20RecordingCanvas)的递归绘制,实际通过JNI调用DisplayListRenderer对象的相应函数把操作翻译成操作指令保存到mWriter对象中;

13、视图递归绘制结束后,getDisplayList函数接着调用GLES20RecordingCanvas画布对象的onPostDraw()函数,其通过JNI 的nFinish本地接口调用本地对象DisplayListRenderer的finish完成一些绘制完成后的操作;

14、在getDisplayList函数最后调用DisplayList对象的end()函数,DisplayList对象的end()函数又调用GLES20RecordingCanvas画布对象的end函数;GLES20RecordingCanvas画布对象的end函数通过JNI调用本地DisplayListRenderer对象的getDisplayList函数实例化一个本地DisplayList对象(或者重用原先已创建的本地DisplayList对象),把上面录制的操作指令保存到该对象中,mWriter对象中录制的操作指令复制到如本地DisplayList对象的mReader中;本地DisplayListRenderer对象的getDisplayList函数返回实例化的本地DisplayList对象引用保存到GLES20DisplayList对象中供下次重用;DisplayList对象的end()函数接着调用画布的recycle函数通过JNI调用把DisplayListRenderer对象的mWriter对象复位,资源释放。视图绘制结束,getDisplayList函数返回;

15、Gl20Renderer 的draw 函数接着调用对应画布的drawDisplayList函数,其通过JNI接口调用OpenGLRenderer对象的drawDisplayList函数;在OpenGLRenderer对象的drawDisplayList函数中调用在上一步实例化的本地DisplayList对象的replay函数,完成上面保存的操作指令的重放;

16、Gl20Renderer 的draw 函数接着先调用回调的onHardwarePreDraw函数,然后调用画布的restoreToCount函数进行第三步保存的画布场景的恢复。在onHardwarePreDraw函数中如果mResizeBuffer不为NULL时(mResizeBuffer对象是一个HardwareLayer对象,是在ViewRootImpl对象在执行performTraversals函数中在视图界面需要resize时创建和绘制的),调用画布的drawHardwareLayer 函数 ,把mResizeBuffer层和Gl20Renderer 对应的画布(对应主显示缓冲区)进行复合;

17、draw 函数接着调用onPostDraw函数,进行主画布绘制结束后的一些操作;

18 最后调用EGL10的eglSwapBuffers函数把主画布的缓冲区刷新到硬件屏幕上,完成整个视图绘制和刷新显示工作。

子视图的layerType 为 LAYER_TYPE_HARDWARE时的绘制序列图

视图在独立HardwareLayer层上的绘制,实际是在GLES20RenderLayer对象对应的本地LayerRender对象的FBO(硬件纹理)上绘制输出的。在完成层画布绘制后或者调用GLES20DisplayList对象对应画布的drawHardwareLayer 函数在DisplayList对象中添加层绘制指令,或者调用Gl20Renderer 对应画布的drawHardwareLayer 函数 ,把层和Gl20Renderer 对应的主显示画布进行复合。

上图为在视图递归绘制时碰到子视图的layerType 为 LAYER_TYPE_HARDWARE时的绘制序列图,由ViewGroup 对象的drawChild函数触发。视图在硬件层上的绘制工作主要通过视图的getHardwareLayer函数完成。getHardwareLayer函数的绘制流程和getDisplayList函数的绘制流程相似。整个流程如下:

1、 在视图的mHardwareLayer对象没有创建时首先要通过Gl20Renderer 对象的createHardwareLayer函数创建一个,实际类型为GLES20RenderLayer;

2、 先保存当前画布,然后调用GLES20RenderLayer对象的start 函数返回一个画布作为当前画布,返回的画布类型为GLES20Canvas类型,但底层对应的呈现对象为LayerRender对象;

3、 和主视图的绘制相似,接着首先调用上一步返回的画布对象的setViewpor函数、onPreDraw函数,然后调用画布的save()函数保存画布场景,然后调用视图computeScroll函数,接着调用画布的translate函数;

4、 接着调用视图的dispatchDraw(canvas)函数或draw(canvas)函数进行子视图的图形元素在GLES20RenderLayer对象对应画布上(GLES20Canvas)的递归绘制,实际是通过JNI调用LayerRender对象的相应函数进行视图界面元素在硬件层上的绘制;

5、 完成子视图在HardwareLayer层上的递归绘制后,先调用画布的restoreToCount函数恢复原先保存的画布场景,再调用画布的onPostDraw()进行绘制前的结束清理工作,接着调用GLES20RenderLayer对象的end函数结束本次绘制操作,并恢复第二步保存的旧的画布后返回;

最后一步调用GLES20DisplayList对象对应画布的drawHardwareLayer 函数,在DisplayListRenderer对象中添加层绘制指令,结束本次流程。

第六篇 ANDROID窗口系统机制之显示机制的更多相关文章

  1. Android窗口系统第三篇---WindowManagerService中窗口的组织方式

    Android窗口系统第一篇—Window的类型与Z-Order确定 Android窗口系统第二篇—Window的添加过程 上面文章梳理了一个窗口的添加过程,系统中有很多应用,每个应用有多个Activ ...

  2. Android窗口管理服务WindowManagerService显示窗口动画的原理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8611754 在前一文中,我们分析了Activi ...

  3. Android窗口系统第一篇---Window的类型与Z-Order确定

    Android的窗口系统是UI架构很重要的一部分,数据结构比较多,细节比较多.本篇文章主要介绍窗口相关数据结构和抽象概念理解,关于[窗口部分的博客]计划如下. 1.窗口Z-Order的管理 2.应用程 ...

  4. Android窗口系统第二篇---Window的添加过程

    以前写过客户端Window的创建过程,大概是这样子的.我们一开始从Thread中的handleLaunchActivity方法开始分析,首先加载Activity的字节码文件,利用反射的方式创建一个Ac ...

  5. Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8577789 在Android系统中,Activ ...

  6. 《安卓网络编程》之第六篇 Android中的WIFI和蓝牙

    关于WIFI就不多介绍啦,直接来个段子吧. 问:“WiFi对人体有伤害么?” 答:“不清楚,反正没有WiFi我就浑身不舒服. 比较重要的一点就是WifiManager  wm=(WifiManager ...

  7. 第十六篇:Linux系统编程中环境变量的使用

    前言 在 UNIX Like 系统中,存有各类系统/应用程序的环境变量,可通过修改之改变系统/应用程序的执行效果:除此之外,用户还可以定义自己的环境变量,供自己写的程序使用. 本文将说明如何在程序中设 ...

  8. Android窗口机制分析与UI管理系统

    类图关系 在看Android的窗口机制之前,先看看其主要的类图关系以及层级之间的依赖与调用关系 1.window在当前的android系统的中的呈现形式是PhoneWindow (frameworks ...

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

    Android 的窗口管理系统 (View, Canvas, WindowManager) 在图解Android - Zygote 和 System Server 启动分析一 文里,我们已经知道And ...

随机推荐

  1. leetcode 之Search in Rotated Sorted Array(三)

    描述    Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 ...

  2. AtCoder Non-decreasing(数学思维)

    题目链接:https://abc081.contest.atcoder.jp/tasks/arc086_b 题目大意:有n个数,最多可以执行2*n次操作,每次可以选择将ai加到aj上,最终使得该序列满 ...

  3. Codeforces 813B The Golden Age(数学+枚举)

    题目大意:如果一个数t=x^a+y^b(a,b都是大于等于0的整数)那就是一个unlucky数字.给你x,y,l,r(2 ≤ x, y ≤ 10^18, 1 ≤ l ≤ r ≤ 10^18),求出l到 ...

  4. Jmeter组件和属性(二)

    Jmeter脚本开发原则 简单.正确.高效.简单:去除无关的组件,同时能复用的尽量复用.正确:对脚本或者业务正确性进行必要的判断,不能少也不能多.(200),业务错误的情况下,也可能返回200,必须用 ...

  5. IEEEXtreme 10.0 - Inti Sets

    这是 meelo 原创的 IEEEXtreme极限编程大赛题解 Xtreme 10.0 - Inti Sets 题目来源 第10届IEEE极限编程大赛 https://www.hackerrank.c ...

  6. 第三方登陆微博、qq、微信

    源文:http://blog.csdn.net/tivonalh/article/details/60954373 假设是已经申请完成各平台开发者账号. 先来简单的,微博和QQ 微博: 引入微博JS ...

  7. grep 同时排除多个关键字

    不说废话, 例如需要排除 abc.txt 中的  mmm   nnn grep -v 'mmm\|nnn' abc.txt 再举个例子,需要确定mac 的本机ip地址,  显然直接可以输入 ifcon ...

  8. eclipse 创建 maven 项目时如何修改 web 的版本和 jdk 的版本

    eclipse 创建 maven 项目时如何修改 web 的版本和 jdk 的版本 在使用 eclipse 创建 maven 项目的时候,默认的 web.xml 的版本时 2.3,默认 jre 的版本 ...

  9. react篇章-React 组件-向组件传递参数

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title&g ...

  10. mac linux 命令笔记 - 权限管理

    壹 权限 在使用命令行工具时,可能需要临时切换到管理员/root权限,如何切换呢? 正文 进入 root 权限: sudo -i 提示输入密码,这个密码就是锁屏的解锁密码. 在操作完成之后,使用 ex ...