Android系统的流畅性一直被拿来与iOS比较,并且认为不如后者。这一方面与Android设备硬件质量参差不齐有关,另一方面也与Android系统的实现有关。例如在3.0前,Android应用程序UI绘制不支持硬件加速。不过从4.0开始,Android系统一直以“run fast, smooth, and responsively”为目标对UI进行优化。本文对这些优化进行简要介绍和制定学习计划。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

注意,上面我们说Android系统不支持硬件加速的UI 绘制,针对的是Android应用程序2D UI绘制。对于3D UI,例如游戏,一直是支持硬件加速渲染的。此外,从前面Android应用程序与SurfaceFlinger服务的关系概述和学习计划、Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划和Android应用程序窗口(Activity)实现框架简要介绍和学习计划这三个系列的文章可以知道,Android系统的UI从绘制到显示到屏幕是分两步进行的:第一步是在Android应用程序进程这一侧进行的;第二步是在SurfaceFlinger进程这一侧进行的。前一步将UI绘制一个图形缓冲区中,并且将该图形缓冲区交给后一步进行合成以及显示在屏幕中。其中,后一步的UI合成一直都是以硬件加速方式完成的。

在支持Android应用程序UI硬件加速渲染之前,Android应用程序UI的绘制是以软件方式进行的,为了更好地理解Android应用程序UI硬件加速渲染技术,我们先回顾在Android应用程序窗口(Activity)实现框架简要介绍和学习计划这个系列的文章提及的软件渲染技术,如图1所示:


图1 Android应用程序UI软件渲染过程

在Android应用程序进程这一侧,每一个窗口都关联有一个Surface。每当窗口需要绘制UI时,就会调用其关联的Surface的成员函数lock获得一个Canvas,其本质上是向SurfaceFlinger服务Dequeue一个Graphic Buffer。Canvas封装了由Skia提供的2D UI绘制接口,并且都是在前面获得的Graphic Buffer上面进行绘制的。绘制完成之后,Android应用程序进程再调用前面获得的Canvas的成员函数unlockAndPost请求显示在屏幕中,其本质上是向SurfaceFlinger服务Queue一个Graphic Buffer,以便SurfaceFlinger服务可以对Graphic Buffer的内容进行合成,以及显示到屏幕上去。

接下来我们再来看Android应用程序UI硬件加速渲染技术,如图2所示:


图2 Android应用程序UI硬件加速渲染过程

这里我们首先要明确什么是硬件加速渲染,其实就是通过GPU来进行渲染。GPU作为一个硬件,用户空间是不可以直接使用的,它是由GPU厂商按照Open GL规范实现的驱动间接进行使用的。也就是说,如果一个设备支持GPU硬件加速渲染,那么当Android应用程序调用Open GL接口来绘制UI时,Android应用程序的UI就是通过硬件加速技术进行渲染的。因此,在接下来的描述中,我们提及到GPU、硬件加速和Open GL时,它们表达的意思都是等价的。

从图2可以看到,硬件加速渲染和软件渲染一样,在开始渲染之前,都是要先向SurfaceFlinger服务Dequeue一个Graphic Buffer。不过对硬件加速渲染来说,这个Graphic Buffer会被封装成一个ANativeWindow,并且传递给Open GL进行硬件加速渲染环境初始化。在Android系统中,ANativeWindow和Surface可以是认为等价的,只不过是ANativeWindow常用于Native层中,而Surface常用于Java层中。另外,我们还可以将ANativeWindow和Surface看作是像Skia和Open GL这样图形渲染库与操作系统底层的图形系统建立连接的一个桥梁。

Open GL获得了一个ANativeWindow,并且进行了硬件加速渲染环境初始化工作之后,Android应用程序就可以调用Open GL提供的API进行UI绘制了,绘制出来内容就保存在前面获得的Graphic Buffer中。当绘制完毕,Android应用程序再调用libegl库提供的一个eglSwapBuffer接口请求将绘制好的UI显示到屏幕中,其本质上与软件渲染过程是一样的,都是向SurfaceFlinger服务Queue一个Graphic Buffer,以便SurfaceFlinger服务可以对Graphic Buffer的内容进行合成,以及显示到屏幕上去。

关于Android应用程序UI的硬件加速渲染过程中涉及到Open GL环境初始化和绘制的简化版本,可以参考前面Android系统的开机画面显示过程分析一文提到的Android系统开机动画的实现。在Android系统的开机画面显示过程分析这篇文章中,开机动画其实是由一个/system/bin/bootanimation程序实现的。这个程序可以看成是一个没有使用Android SDK来开发的一个Native应用程序。

在这个系列的文章中,我们将通过Android 5.0的源码来分析Android应用程序UI的硬件加速渲染技术。不过为了更好地理解Android 5.0的硬件加速渲染实现,我们有必要先了解从Android 3.0以来,Android应用程序UI硬件加速渲染的进化历史:

1. Android 3.0,也就是Honeycomb版本,开始引用OpenGLRenderer图形渲染库,支持Android应用程序UI可选地使用硬件加速渲染。

2. Android 4.0,也就是Ice Cream Sandwich版本,要求设备默认支持Android应用程序UI硬件加速渲染,并且增加一个TextureView控件,该控件直接支持以Open GL纹理的形式来绘制UI。

3. Android 4.1、4.2和4.3,也就是Jelly Bean版本,加入了Project Butter(黄油计划)的特性,包括:A. 通过Vsync信号来同步UI绘制和动画,使得它们可以获得一个达到60fps的固定的帧率;B. 三缓冲支持,改善GPU和CPU之间绘制节奏不一致的问题;C. 将用户输入,例如touch event,同步到下一个Vsync信号到来时再处理;D. 预测用户的touch行为,以获得更好的交互响应;E. 每次用户touch屏幕时,进行CPU Input Boost,以便减少处理延时。

4. Android 4.4,也就是KitKat版本,一方面通过优化内存使用,另一方面是可选地支持使用ART运行时替换Dalvik虚拟机,来提高应用程序的运行效率,使得其UI更流畅。

5. Android 5.0,也就是Lollipop版本,ART运行时引进了Compacting GC,进一步优化了Android应用程序的内存使用,并且ART运行时正式替换了Dalvik虚拟机,同时,Android应用程序增加了一个Render Thread,专门负责UI渲染和动画显示。

从Android应用程序UI硬件加速渲染的进化历史可以看出,Android系统确实是在践行"run fast, smooth, and responsively"的宏伟计划,并且也是做到了。

有了前面的基础知识之后,我们接下来再来Android 5.0的窗口和动画是如何通过硬件加速技术来渲染的,如图3所示:


图3 Android应用程序窗口和动画的硬件加速渲染框架

在Android应用程序窗口中,每一个View都抽象为一个Render Node,而且如果一个View设置有Background,这个Background也被抽象为一个Render Node。这是由于在OpenGLRenderer库中,并没有View的概念,所有的一切可绘制的元素都抽象为一个Render Node。

每一个Render Node都关联有一个Display List Renderer。这里又涉及到另外一个概念——Display List。注意,这个Display List不是Open GL里面的Display List,不过它们在概念上是差不多的。Display List是一个绘制命令缓冲区。也就是说,当View的成员函数onDraw被调用时,我们调用通过参数传递进来的Canvas的drawXXX成员函数绘制图形时,我们实际上只是将对应的绘制命令以及参数保存在一个Display List中。接下来再通过Display List Renderer执行这个Display List的命令,这个过程称为Display List Replay。

引进Display List的概念有什么好处呢?主要是两个好处。第一个好处是在下一帧绘制中,如果一个View的内容不需要更新,那么就不用重建它的Display List,也就是不需要调用它的onDraw成员函数。第二个好处是在下一帧中,如果一个View仅仅是一些简单的属性发生变化,例如位置和Alpha值发生变化,那么也无需要重建它的Display List,只需要在上一次建立的Display List中修改一下对应的属性就可以了,这也意味着不需要调用它的onDraw成员函数。这两个好处使用在绘制应用程序窗口的一帧时,省去很多应用程序代码的执行,也就是大大地节省了CPU的执行时间。

注意,只有使用硬件加速渲染的View,才会关联有Render Node,也就才会使用到Display List。我们知道,目前并不是所有的2D UI绘制命令都是GPU可以支持的。这一点具体可以参考官方说明文档:http://developer.android.com/guide/topics/graphics/hardware-accel.html。对于使用了GPU不支持的2D UI绘制命令的View,只能通过软件方式来渲染。具体的做法是将创建一个新的Canvas,这个Canvas的底层是一个Bitmap,也就是说,绘制都发生在这个Bitmap上。绘制完成之后,这个Bitmap再被记录在其Parent View的Display List中。而当Parent View的Display List的命令被执行时,记录在里面的Bitmap再通过Open GL命令来绘制。

另一方面,对于前面提到的在Android 4.0引进的TextureView,它也不是通过Display List来绘制。由于它的底层实现直接就是一个Open GL纹理,因此就可以跳过Display List这一中间层,从而提高效率。这个Open GL纹理的绘制通过一个Layer Renderer来封装。Layer Renderer和Display List Renderer可以看作是同一级别的概念,它们都是通过Open GL命令来绘制UI元素的。只不过前者操作的是Open GL纹理,而后者操作的是Display List。

我们知道,Android应用程序窗口的View是通过树形结构来组织的。这些View不管是通过硬件加速渲染还是软件渲染,或者是一个特殊的TextureView,在它们的成员函数onDraw被调用期间,它们都是将自己的UI绘制在Parent View的Display List中。其中,最顶层的Parent View是一个Root View,它关联的Root Node称为Root Render Node。也就是说,最终Root Render Node的Display List将会包含有一个窗口的所有绘制命令。在绘制窗口的下一帧时,Root Render Node的Display List都会通过一个Open GL Renderer真正地通过Open GL命令绘制在一个Graphic Buffer中。最后这个Graphic Buffer被交给SurfaceFlinger服务进行合成和显示。

以上就是Android应用程序窗口和动画的硬件加速渲染框架,里面提到的Render Thread还需要进一步解释。Render Thread是在Android 5.0中引进的,它用来分担Android应用程序的Main Thread的工作。在Android 5.0之前,Android应用程序的Main Thread不仅负责渲染UI,还负责处理用户输入。通过引进Render Thread,我们就可以将UI渲染工作从Main Thread释放出来,交由Render Thread来处理,从而也使得Main Thread可以更专注高效地处理用户输入,这样使得在提高UI绘制效率的同时,也使得UI具有更高的响应性。

Main Thread与Render Thread的交互模型如图4所示:

图4 Android应用程序Main Thread与Render Thread的交互模型

Main Thread主要是负责调用View的成员函数onDraw来构造它们的Display List,然后在下一个Vsync信号到来时,再通过一个Render Proxy对象向Render Thread发出一个drawFrame命令。Render Thread内部有一个Task Queue,从Main Thread发送过来的drawFrame命令就会保存在Render Thread的Task Queue,等待Render Thread处理。

上面分析的应用程序UI绘制机制还没有涉及到动画。当一个View需要以动画的形式显示时,我们可以通过调用这个View的成员函数animate获得一个ViewPropertyAnimator。ViewPropertyAnimator可以通过两种方式来显示动画。第一种方式是旧的方式,动画的每一帧由Main Thread进行计算,然后再由Render Thread进行渲染。第二种方式是Android 5.0引进的,Main Thread将动画注册到Render Thread中去,然后由Render Thread计算和显示动画的每一帧。第二种方式在动画的显示期间,完全不需要Main Thread参与,不过前提是Main Thread不想参与。如果Main Thread需要参与,例如,Main Thread想在动画开始之前,将View的Layer Type设置为LAYER_TYPE_HARDWARE,以便它可以以Open GL的Frame Buffer Object(FBO)的形式进行渲染,或者Main Thread想侦听动画的每一帧显示,那么就不能将动画的计算和显示完全交给Render Thread来做了。将动画的计算和显示完全交给Render Thread来做的好处就是使得动画的显示不影响Main Thread响应用户的其它输入。

上面提到,在显示一个View的动画之前,Main Thread可以将View的Layer Type设置为LAYER_TYPE_HARDWARE,这样View可以以FBO的形式进行渲染更,这是通过调用与View关联的ViewPropertyAnimator的成员函数withLayer来实现的。这是对动画显示的另一种优化。回忆TextureView的特点,它是直接通过Open GL纹理来绘制,这样可以省去Display List这一中间步骤。同样的,对于Layer Type为LAYER_TYPE_HARDWARE的View,它将直接通过FBO来实现,这样也是可以提高渲染效率。等到动画结束的时候,ViewPropertyAnimator会将View的Layer Type将恢复为原来设置的类型。

上面描述的两种动画显示优化涉及到Main Thread与Render Thread的交互 如图5所示:


图5 Android应用程序Main Thread与Render Thread的动画交互模型

从View获得一个用来显示动画的ViewPropertyAnimator之后,调用它的成员函数withLayer,就会导致Main Thread向Render Thread发送一个buildLayer的命令。Render Thread执行这个buildLayer的命令的时候,就会为与View关联的Render Node设置一个Layer。以后Render Thread就以Layer的方式,即FBO的方式,来渲染View的动画。

另一方面,如果一个View的动画显示不需要Main Thread的参与,那么从View获得一个ViewPropertyAnimator会将动画注册到Render Thread里面的一个AnimatorManager中。Render Thread通过AnitmatorManager检测注册到它里面的动画是否还没有结束。如果还没有结束,那么Render Thread就自动地计算和显示动画的下一帧,直到动画显示结束为止。

至此,Android应用程序UI的硬件加速渲染涉及到的关键概念我们就介绍完成了,接下来我们还会按照以下五个情景进一步分析它的实现:

1.Android应用程序UI硬件加速渲染的环境初始化过程分析;

2. Android应用程序UI硬件加速渲染的预加载资源地图集创建过程分析;

3.Android应用程序UI硬件加速渲染的Display List构建过程分析;

4. Android应用程序UI硬件加速渲染的Display List渲染过程分析;

5. Android应用程序UI硬件加速渲染的动画执行过程分析。

其中,第二个情景是第四个情景涉及到的一个重要优化,因此我们将它单独列出来分析。通过这五个情景的学习,我们就可以深入地掌握Android应用程序UI的硬件加速渲染技术了,敬请期待!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。

更多相关文章

  1. Android(安卓)matrix 控制图片的旋转、缩放、移动
  2. Android中dispatchDraw分析
  3. 锁屏界面
  4. 关于Android(安卓)Studio3.2新建项目Android(安卓)resource link
  5. Android(安卓)- Manifest 文件 详解
  6. Android之应用程序基础
  7. Android四大组件的理解
  8. Android官方入门文档[1]创建一个Android项目
  9. 第三章 Android程序设计基础

随机推荐

  1. android 长按和点击监听事件 谁先执行
  2. Android手势库
  3. Android开发环境搭建和简单介绍
  4. Android Back Home键监听
  5. Android 再按一次退出程序三种办法
  6. android SDK升级后错误
  7. ScrollView 嵌套 RecyclerView 显示不完
  8. Android快速SDK(15)二维码扫描Scanner【傻
  9. 常用Android应用程序中的Intent动作
  10. Android -- 过滤器相关