Android显示模块如何使用软件windows10渲染模块(非硬件加

Android系统图像渲染简介
Android系统UI从绘制到显示至屏幕一般为如图过程:先从相应的图片解码获得位图数据放到内存。然后使用图形引擎将位图数据按一定方式,渲染到可用于显示的图形内存上。最后系统SurfaceFlinger汇集所有图层的信息,采用离线合成或在线合成的方式,将图形内存的内容最后投射到屏幕显示出来。而本文就其中所用到的渲染方式作下简单介绍。
Android程序中图形绘制单元为一个Surface。每个Surface都有一个Canvas对象,它封装了由Skia提供的2D UI绘制接口,用来完成View在Surface上的绘制操作。每个Surface上的绘制内容就存储在DequeueBuffer一个Graphic Buffer中。每当窗口绘制时,通过函数lockCanvas获得一个Canvas,然后从顶层View开始,按树递归调用View的draw方法,在draw方法中,所有View中的onDraw实现被调用,以完成整个View的绘制,最后调用函数unlockAndPost将数据送至QueueBuffer一个Graphic Buffer中,以便SurfaceFlinger可以对Graphic Buffer的内容进行合成后显示到屏幕上去。
软件渲染流程中,Android并不会计算哪些View需要重绘,不只绘制了dirty的View,而是让所有View执行onDraw方法,这样效率不高,一定程度上影响了程序相应速度。而硬件渲染,通过GPU对软件图形图像的处理来减轻CPU的负担,从而使图像能够以更快的速度被处理,以达到程序提速的目的。
硬件渲染即通过GPU,对厂商按照Open GL的规范实现的驱动间接调用来进行渲染。硬件渲染和软件渲染主要流程一样,在开始渲染之前,先获得一个Graphic Buffer但是这个Graphic Buffer会被封装成一个ANativeWindow。如同可以将Surface看作是Skia图形渲染库与操作系统底层图形系统的一个桥梁,同样可以将它看做是Open GL与底层图形系统建立的一个连接。然后将这个ANativeWindow传递给Open GL进行硬件加速渲染环境初始化。之后就可以调用Open GL的API进行UI绘制了,并将绘制出来内容就保存在前面获得的Graphic Buffer中。当绘制完毕,Android应用程序再调用libegl库提供的eglSwapBuffer接口将绘制好的UI显示到屏幕中。
其中与软件渲染不同的是,硬件渲染并不是立即执行绘制命令,它只是将对应的绘制命令以及参数保存在一个Display List中,接下来再通过Display List Renderer执行这个Display List的命令。使用Display List的目的是,先把视图的各种绘制函数翻译成绘制指令保存起来,对于没有发生改变的视图把原先保存的操作指令重新读取出来重放一次就可以了,提高了视图的显示速度,而对于需要重绘的View,则更新显示列表,然后再调用Open GL完成绘制。
虽然有了GPU的介入,分担了CPU的工作,同时有了Display List优化了图形渲染处理的流程,但是布局和事件响应还是全部集中在主线程,有时还是比较容易造成阻塞,于是又有了非主线程渲染。
非主线程渲染
在UI的主线程中更新画面很容易造成主线程的堵塞,造成程序的长时间无响应,于是便有了SurfaceView。
SurfaceView是View的一个特殊子类,它拥有专有的Surface,即请求Window Manager创建一个新窗口,并改变窗口之间的深度信息来显示。如果SurfaceView的Window显示在主窗口的后面,SurfaceView将主窗口相应的位置设置成透明来使可见。其中的SurfaceHolder,可以把它当成surface的控制器,用来操纵surface,处理它的Canvas上画的效果和动画,控制表面,大小,像素等。
为了总体提高系统显示性能,Android系统也是不断的对渲染性能这块进行改进。
在Android 1.5中,GLSurfaceView作为SurfaceView的补充,加入了EGL的管理,并自带了渲染线程。此前要在SurfaceView中要用GPU渲染,只好自已建上下文,而如今只要在回调中调用Open GL接口就可以了。
从Android 3.0开始支持启用Open GL硬件绘制加速。同时引入了SurfaceTexture,它是一个Texture, 也就是纹理,可以用来捕获一个图像流的一帧来作为OpenGL 的纹理。这个图片流主要是来自相机的预览或视频的解码。
之后Android 4.0中又增加一个TextureView控件,跳过Display List中间层,支持直接以Open GL纹理的形式来绘制UI,从而提高效率。同时它不会创建独立的window,它与常规的View一样,弥补了SurfaceView在进行普通View的旋转、缩放等变化时,其内容并不跟着一起变化的缺陷。
Android 4.3 增加了对Open GL ES 3.0 的支持,这个版本增加了对多缓冲区对象的支持;着色语言支持32 位整数和浮点数据类型以及操作;增加了对多个纹理与多重目标渲染的支持。这些新特新大幅提高了移动平台的渲染性能。
到了Android 5.0,更是增加了一个专门负责UI渲染UIRender Thread,用来分担Android Main Thread的工作,在此之前,Android的Main Thread不仅负责渲染UI,还负责处理用户输入,而通过引进Render Thread来处理渲染工作,Main Thread可以更高专注高效地处理用户输入,这样使得在提高UI绘制效率的同时,也使得UI具有有更高的响应性。
Android 6.0后又提供了一个lockHardwareCanvas方法,用此方法可以直接得到硬件加速的Canvas。
在Android7.0之前,Surfaceview的显示也不受View的属性控制,所以不能进行平移,缩放等变换,要实现上述功能,要借助于TextureView。而到了Android 7.0时,它对SurfaceView自身和它的内容改变做了同步处理,比如通过SurfaceView实现视频播放时,对视频进行缩放变换SurfaceView会同步变化。
Android 应用图像展示的流畅性直接影响着用户体验,而本文从软件渲染,硬件渲染,非主线程渲染三个方面,对其各自的特点和流程作了简单介绍,在以后需要优化渲染性能时,我们可以选择从其一方面或多方面结合入手,来满足自己的需求。
本文作者:钱康(点融黑帮),就职于点融大前端,android开发工程师。
责任编辑:
声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
今日搜狐热点Android图形显示系统——上层显示2:硬件加速实现
Android界面绘制的硬件加速实现
Android的界面绘制的硬件加速采取上下整合的一套流程实现
一、代码结构
(一)Java
HardwareRenderer-&ThreadedRenderer:组织硬件加速渲染的类,下发创建显示列表和回放的指令。
GLES20RecordngCanvas GLES20Canvas HardWareCanvas:与Canvas平级的UI渲染引擎支持,但这个Canvas只能存储命令到显示列表中,并在ThreadedRenderer中的渲染线程辅助下运行。
RenderNode:所有View对应一个构成一个RenderNode
RenderNodeAnimator:动画用
HardwareLayer:调saveLayer时产生,缓存绘制内容为一个Layer
(二)Hwui
DisplayListRenderer:对应于HardWareCanvas,创建显示列表的类
RenderNode:一个渲染节点,包含绘制命令和相关资源
BaseRenderNodeAnimator:动画用
RenderProxy:由于OpenGL上下文是线程私有的,需要使用到OpenGL的操作都必须在同一线程。这个类的作用就是按Commander模式做一个中转,把事务转移到持有上下文的线程中执行。
Layer:对应于上层的HardwareLayer,实际上是缓存绘制过的内容到一张纹理上,至于如何缓存,有fbo方式和copytex方式
OpenGLRenderer:这个类并不直接被上层调用,但它是执行实际渲染任务的入口类,定位性能问题一般直接从这个类看起。
PS:建议仔细看看RenderProxy.cpp里面的Commander模式实现方法,确实相当之精妙简洁,不过感觉C11有匿名函数后不需要这么麻烦了。
二、Hwui引擎设计
(一)显示列表
1、显示列表设计难题
很多介绍显示列表机制的文章都是一带而过,仿佛得到一个显示列表并回放是很简单的事情。但真正动手写时,就会发现有很多问题:
1、如何存储每个API及相关参数?为每个API创建一个类,回放时调类方法?还是把API及参数作一个编码,然后回放时解码,用虚拟机的方式执行?用前者实现比较简单,扩展相对容易,但每个API建一个类,需要非常大的代码量(越多的代码意味着越容易出错);用后者,需要构建编码解码的逻辑,总体代码量较少,但是虚拟机处理中switch case代码冗长,且不容易作扩展。
2、资源怎么处理?这个是最为棘手的,如果拷贝资源,会大幅降低效率,不可取,但如果不拷贝,上层在传入资源后马上修改这些资源,回放时结果会是错误的(如传入Bitmap A之后,调用drawBitmap之后,马上修改A的内容,回放时A就是修改后的)。原则上自然是不拷贝,但如何约束上层行为呢?
2、Hwui的显示列表设计
DrawOp即产生渲染效果的算符,StateOp为产生状态变更的算符,须与后续的DrawOp配合使用。
之所以采用独立成类的设计方式,是为了满足批处理优化的需要。
Resouces保留在对应的ResouceCaches中。
(1)DrawOp
DrawOp为渲染算符,分的子类较多,主要是以下几顶:
callDrawGLFunction-&DrawFunctorOp:
用于WebKit/chromium的硬件加速渲染,WebKit/chromium浏览器内核中将基于opengl的渲染代码封装为函数Functor,传入hwui引擎中执行。
DrawSomeTextOp:
绘制文本的算符,归结为一个算符的原因是文本解析的步骤是统一的
DrawColorOp:
将区域刷成指定颜色的算符
DrawBoundedOp:
drawRect、drawBitmap及一般的drawPath均继承于此算符,其特点是渲染存在边界。可以设法判断是否覆盖
DrawLayerOp:
DrawShadowOp:
(2)StateOp
保存/恢复状态/建层:SaveOp/RestoreToCountOp/SaveLayerOp
矩阵变换相关:TranslateOp/RotateOp/SkewOp/SetMatrixOp/ConcatMatrixOp
设置裁剪区域的算符:ClipOp/ClipRectOp/ClipPathOp/ClipRegionOp
设置Paint的采样模式:ResetPaintFilterOp/SetupPaintFilterOp
(二)渲染缓存
Hwui的缓存是比较复杂的,一方面,由于采用基于显示列表的异步渲染机制,用于渲染的资源本身需要在列表中缓存。另一方面,由于GPU/显卡渲染的异构性,其所需要的资源必须要由显示列表中的资源上传或映射而来,上传的资源和映射关系本身构成显存的缓存。
Caches 作为单例,存储了所有的渲染缓存,主要内容如下:
TextureCache textureC
LayerCache layerC
RenderBufferCache renderBufferC
GradientCache gradientC
ProgramCache programC
PathCache pathC
PatchCache patchC
TessellationCache tessellationC
TextDropShadowCache dropShadowC
FboCache fboC
ResourceCache resourceC
这种单例设计模式自然完全没有考虑同一进程中可能有多个线程使用Hwui的情况,因此如果要将Hwui改成支持多线程分别使用,需要作不少手术。
如图所示:
上层Canvas的API中所夹带的资源,创建显示列表时在ResourceCache中缓存一次(Bitmap仅引用,其余的全部拷贝),在回放显示列表时再继续构建各自对应的Cache。
Caches中的所有缓存,除resourceCache之外的不妨统称为EngineCache。这个缓存关系就是:
API(Java Virtual Machine)——ResourceCache——EngineCache
ResourceCache
缓存匹配的查询机制都是依靠指针,由于Path、Paint等资源中会夹带Effect、Shader等特效,当应用层修改这些东西后,由于指针没变,缓存无法感知其变化而更新。
因此在Skia里面为SkPath、SkPaint加入了generationId,当它们附带的特效发生改变时,这个id同时修改,依此来校验API-ResourceCache,ResourceCache—EngineCache是否一致,若不一致自然是要重新再拷贝一遍/重新生成一次Cache。
由于ResourceCache不复制Bitmap,必须要防止在渲染过程中上层把Bitmap给释放/修改掉。
但它只防止了释放,并没有阻止修改的实现,因此这个只能靠应用开发者自觉。
代码见 frameworks/base/core/jni/android/graphics/Bitmap.cpp
static jboolean Bitmap_recycle(JNIEnv* env, jobject, jlong bitmapHandle) {
SkBitmap* bitmap = reinterpret_cast&SkBitmap*&(bitmapHandle);
#ifdef USE_OPENGL_RENDERER
if (android::uirenderer::Caches::hasInstance()) {
result = android::uirenderer::Caches::getInstance().resourceCache.recycle(bitmap);
return result ? JNI_TRUE : JNI_FALSE;
bitmap-&setPixels(NULL, NULL);
return JNI_TRUE;
ResourceCache不包含Bitmap(虽然会阻止上层回收),占用内存还是很少的,缓存大头还在 EngineCache
Paint/Shader——ProgramCache
Hwui用的是2.0以上的OpenGLES版本,着色器的构建是很重要的部分。不过,2D绘图的着色器相对也较简单。
这里的设计思想是先翻译 SkPaint 及其中的 SkShader为 ProgramDescription 结构,然后由 ProgramCache 去根据这个结构,选择合适的着色器语言片断,拼装起来,组成 GLProgram
SkShader 是一个父类,包含 Bitmap shader,Gradient shader 等好几类,因此这里对每一类都要有对应的函数去解析。
主要函数:
SkiaShader::describe
ProgramCache::generateVertexShader
ProgramCache::generateFragmentShader
至于ProgramCache的着色器代码怎么写的,用的正交投影还是透视投影,纹理贴图怎么实现等,这里就不详述了。
图片——TextureCache
(1)普通图片——SkBitmap
代码参考TextureCache::get 和
TextureCache::generateTexture
基础的纹理上传,不多述。
(2)资源图集——AssetAtlas
这个是Android 4.3起引入的机制,将预加载所得的图片,先整合到一张 GraphicBuffer 上,转变为一张EGLImage。然后各应用在使用硬件加速渲染UI时,将此EGLImage映射为自身的OpenGL纹理,从而免去这部分资源纹理上传的过程,且由于应用间共享纹理,节省了内存。
详细看老罗的博客吧,虽然个人感觉把这一个简单的功能讲太细了:
文字——FontRenderer
文字/文本绘制对任何一个2D渲染引擎来说,都是一个棘手的事。主要是因为文本解析本身需要大量的时间,肯定需要缓存,但使用缓存的话,由于各个文字在各种字体下的解析结果都不一样,全缓存进来内存耗费极高,是不可能的。
没有什么完美的方案去设计一个文本缓存机制,正好比没有绝对正确的企业管理模式。
Hwui中是这么处理的:
缓存的设计(这个是每一个FontRenderer都包含的):
代码见FontRenderer::initTextTexture
对Skia解析出来的字形SkGlyph,会按PixelBuffer 由小到大逐次去找一个对应位置,然后复制上去,如果是有变换需要(mGammaTable存在),则在这个过程顺便把gammatable变换做了。
在后面渲染时,PixelBuffer会上传为Texture,然后GPU就可以使用字形渲染的结果了。
至于 mGammaTable,可详细看 GammaFontRenderer 和 Lookup3GammaFontRenderer 类。
PixelBuffer 根据 设备支持的OpenGL ES 版本和属性配置(ro.hwui.use_gpu_pixel_buffers)选用CpuPixelBuffer或GpuPixelBuffer(需要3.0以上版本和属性开关开启)。CpuPixelBuffer就是malloc出来的内存,GpuPixelBuffer是PBO。OpenGLES 3.0 标准有PBO映射为CPU内存的API(glMapBufferRange),会提升缓存过程中上传的效率。
(注:GpuPixelBuffer这一段代码也是使用PBO的好教材,需要了解PBO如何使用的可以参考下)。
在渲染时先根据已经缓存好的字形位置,算出纹理采样的坐标,塞进对应cache的vbo,然后遍历所有的cacheTexture,渲染包含有待渲染文字的cache即可。
代码见 FontRenderer::issueDrawCommand
延迟渲染模式下,不管是多少个字,始终是根据cache数来调drawCall,这样,drawCall的调用次数就比较少了。
出于内存优化的考虑,中间一层 PixelBuffer 是可以不要的,但相应地就要在外面把 gammaTable 映射做掉,逻辑会复杂一些。
路径Path和TessellationCache
Hwui引擎中实现drawPath时,没有自己去计算路径点,而是调用skia的drawPath接口绘制一张A8的模板,然后按模板把Shader混合进去。对应的PathCache就是存储这个模板的。
在Android 4.0时,绘制圆角矩形、圆形等特殊形状时,是按drawPath的方式,生成模板再混合,这种方式需要占用内存,且不是很效率,因此后面Hwui中加入了处理形体的功能,这就是曲纹细分器Tessellator,它通过解析SkPath,生成一系列顶点来描述形体。
目前主要支持凸形状(详见PathTessellator的实现),目前细分过程仍然是靠CPU实现的,在未来手机上的GPU支持曲纹细分的Shader后,可以把这部分工作转移到GPU上。
TessellationCache就是曲纹细分器生成的vbo(vetex buffer object),相对于模板(一张A8纹理)而言节省不少内存,且执行时一般效率更高(曲纹细分方式由于顶点数多,Vertex Shader负荷较大,但相对于模板方式,Fragment Shader负荷较小,内存带宽占用较少)。
Layer——LayerCache
(略,以后有空再补)
三、基本流程
关于显示列表的创建过程可以参考老罗博客:
延迟渲染是在回放显示列表时,先做一步预处理(defer),然后再执行处理后的命令(flush)。
status_t OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags) {
DeferredDisplayList deferredList(*currentClipRect(), avoidOverdraw);
DeferStateStruct deferStruct(deferredList, *this, replayFlags);
renderNode-&defer(deferStruct, 0);
return deferredList.flush(*this, dirty) |
看这段回放的主代码可以知道,延迟渲染是先创建一个延迟渲染列表,然后把显示列表中的命令全部往里面加进去(这个过程中做预处理),然后交由延迟渲染列表去回放(flush)。
预处理的作用主要是:
(1)合并渲染,减少drawCall调用
(2)避免部分的过度绘制
过度绘制/OverDraw是指同一个像素被渲染多次的情形。解决OverDraw的方法要使用命令列表(显示列表),对列表中每个绘制命令计算其涵盖区域。然后是计算重复渲染的区域,设法将这个区域上面的绘制命令合并
Defer信息获取
DrawOp算子需要实现onDefer这个方法,为 DeferredDisplayList 提供两个信息:DeferInfo和DeferredDisplayState。
DeferInfo反映这个DrawOp算子本身的性质(能否合并,是否透明,归属哪一类),DeferredDisplayState则是结合算子所处的矩阵变换状态,反映该算子在最终显示屏的地位(渲染边界、矩阵变换)
Defer的作用体现
Hwui中,避免过度绘制的条件很苛刻,需要完全不透明且完全覆盖,因此Defer作用主要体现在合并渲染上了。
支持合并渲染的DrawOp需要实现一个特殊的multiDraw函数,用以将同类一系列DrawOp的渲染在同一函数完成。
目前所看到合并渲染仅限于绘制AssetAtlas资源的操作合并与多次绘制文字绘制的合并。
当应用内存不足时,会尽量去回收内存,其中Hwui所占的Cache在回收的范围之内,最终调用Caches::flush回收,有三种模式:
kFlushMode_Layers:
清除LayerCache和RenderBufferCache
kFlushMode_Moderate:
除上面外,清除部分字体缓存、图片纹理、路径纹理
kFlushMode_Full:
字体缓存全清,再把fbo、dither清除掉
Program是不清的。在内存依然紧张时,会在上层直接摧毁OpenGL上下文。
四、Android硬件加速机制评价
(一)优点
1、完备的GPU绘制流程,在上层API不变的前提下,妥善解决了2D渲染的性能问题
2、延迟渲染合并了大量的渲染指令,drawCall调用少效率高,且有一定的防止过度绘制的功能
3、有一层一层回收缓存的机制
4、相当好的基于OpenGLES 2D 的引擎范本,很多代码(比如:纹理上传、PBO、曲纹细分)很有参考价值。
(二)槽点
1、上下层耦合关系严重,对上依赖于Java层的合理调用,对下依赖于Skia,不容易提供单独的基于硬件加速的2D渲染引擎,容易出现内存/资源泄露
2、资源在CPU和GPU中均做Cache,占用内存较多:显示列表中的图片资源和纹理图片同时存在,字体三重缓存
3、延迟渲染机制做得还是不够好,消除过度绘制的能力有限,而且每帧都要算一次延迟渲染信息。
Android 如何使用GPU硬件加速
Android图形显示系统——下层显示3:窗口系统
Android显示系统框架原理介绍
Android6.0 显示系统(六) 图像的输出过程
Android中图形参数及图形内存信息获取
Android底层开发之字符绘制TextLayoutCache
android中如何使用GPU实现硬件加速,3D渲染
Android OpenGL 硬件加速.
没有更多推荐了,Android-硬件加速
转载请注明来源:
从3.0(API level 11)开始,Android 2D渲染pipeline开始支持硬件加速,这意味着所有的绘制操作都是由使用了GPU的canvas来完成的。
但是启用硬件加速会增加app所需要的资源,app会消耗更多的RAM。
如果你的Target API level是&=14的,那么默认是启用硬件加速度的,但是,你也可以明确的手动来启用。
如果你的app只是使用了标准的view和Drawable,开启硬件加速不会带来任何不好的绘制效果。
但是,因为并不是所有的2D绘制都支持硬件加速,打开硬件加速可能会影响一些自定义的view或者是绘制调用,经常出现的问题有元素不可见、异常、错误渲染的像素。
为了修正这些问题,Android提供了一些选项用来在不同级别上启用或者是禁用硬件加速。
参考“控制硬件加速”章节
如果你的app要自定义绘制,你要在实际的硬件设备上打开硬件加速来测试下,然后找到可能存在的问题。
“不支持的绘制操作”这个章节描述了硬件加速存在的已知的问题和如何来使用它。
控制硬件加速
你可以在下列级别上控制硬件加速:
Application
Application级别
在你的manifest文件中,设置&application&标签的如下属性给整个app启用硬件加速:
&application android:hardwareAccelerated="true" ...&
Activity级别
如果全局启用了硬件加速的情况下,你的应用无法正常工作,你也可以控制单个activity是否启用硬件加速。
在activity级别启用或者禁用硬件加速,可以给&activity&添加android:hardwareAccelerated属性。
下面的例子在整个app级别启用了硬件加速,但是在某个activity上禁用了硬件加速。
&application android:hardwareAccelerated="true"&
&activity ... /&
&activity android:hardwareAccelerated="false" /&
&/application&
Window级别
如果你需要更细粒度的控制,你可以用下面的代码给指定的window启用硬件加速:
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
注意: 当前在window级别不能禁用硬件加速。
不可以用下面的代码在运行时设置单个view禁用硬件加速:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
注意:当前不能在view级别启用硬件加速。View的layer不仅可以禁用硬件加速,有其他的用途。
更多具体的使用信息请参考“View的layer”章节。
view是否被硬件加速了
有时候,知道app是否被硬件加速了是很有用的,尤其是对一些自定义的view来说。
如果你的app做了很多自定义的绘制并且新的渲染pipeline有可能并不支持所有的绘制操作的时候会更有用。
有两种检查app是否被硬件加速的方式:
View.isHardwareAccelerated()返回true,如果view被attached到了硬件加速的window上。
Canvas.isHardwareAccelerated()返回true,如果Canvas是被硬件加速的。
如果你必须要在你的绘制代码中做检查的话,尽可能的使用Canvas.isHardwareAccelerated()而不是View.isHardwareAccelerated()。
当view被attached到一个硬件加速的window上,它仍然可以使用非硬件加速的Canvas来做绘制。
比如说,当为了缓存而把view绘制到bitmap的时候,就会发生这样的情况。
Android的绘图模型
当启用了硬件加速的时候,Android框架会使用一个新的绘图模型,它会使用显示列表(display list)来把app渲染到屏幕上。
为了彻底的理解显示列表和显示列表是如何影响你的app的,首先要明白在没有硬件加速的时候,android是如何绘制view的。
下面的章节介绍了基于软件的和基于硬件加速的绘图模型。
基于软件的绘图模型
在基于软件的绘图模型中,view 的绘制遵循下面两个步骤:
让view树失效(Invalidate the hierarchy)
绘制view树(Draw the hierarchy)
每当app需要更新UI的一部分的时候,它会在包含了更改内容的view上调用invalidate()方法(或者是它的一个变种方法)。
这个invalidation的消息会沿着view的树形结构一直往上传递,然后计算出屏幕上需要被重绘的区域(dirty区)。
然后,Android系统会绘制view树上所有和dirty区有交集的view。很不幸的是,这种绘图模型有两种弊端:
首先,在这种模型下,每一个绘制路径都需要执行大量的代码。比如,如果你的app在一个Button上调用了invalidate(),
如果这个Button是位于另一个view的上面的,android系统也会重绘这个view,尽管它并没有发生任何变化。
第二个问题是这种绘图模型可能会隐藏你app里面的bug。因为android系统会重绘跟dirty区有交集的view,
那么就有可能发生一个你改变了内容的view在你还没有调用invalidate()之前就已经被重绘的情况。
当发生这种情况的时候,你需要依赖其他正在被invalidated的view才能获取正确的行为。
每次你修改你的app的时候都可能发生这样的事。
正是因为这个原因,一旦修改了影响view绘制的数据或者状态的时候,总是需要在自定义的view上调用invalidate()。
注意:Android的view在属性发生变化的时候,会自动调用invalidate(),比如:背景颜色,TextView的文本内容。
硬件加速绘图模型
Android系统仍然使用invalidate()和draw()来请求屏幕更新和渲染view,但是,实际的绘制是不一样的。
android系统会在显示列表内部记录要执行的绘制操作而不是立即就执行这些绘制操作,显示列表包含了view树绘制代码的输出。
另一个优化是,android系统只需要记录和更新通过调用invalidate()标记为dirty的view的显示列表,
那些没有invalidated的view可以简单地通过之前记录的显示列表来进行重绘。
新的绘图模型包含以下三个步骤:
让view树失效(Invalidate the hierarchy)
记录和更新显示列表(Record and update display lists)
绘制显示列表(Draw the display lists)
在这种模型下,就不能依赖view与dirty区有交集来执行draw()方法。为了确保android系统能记录view的显示列表,必须要调用invalidate()。
如果忘记调用,会导致view看起来没有任何变化,就算是view确实被改变了。
使用显示列表对动画的性能也有好处,因为设置特定的属性(比如:alpha或者是rotation)不需要invalidating目标view(这是自动完成的)。
这个优化也应用在有显示列表的view上(启用了硬件加速的app里面的任意的view)
举个例子:假如有一个LinearLayout,包含了一个ListView,下面有一个Button。LinearLayout的显示列表大概是这个样子:
DrawDisplayList(ListView)
DrawDisplayList(Button)
假如现在你想要改变ListView的透明度。在ListView上调用setAlpha(0.5f)以后,显示列表会变成:
SaveLayerAlpha(0.5)
DrawDisplayList(ListView)
DrawDisplayList(Button)
ListView的复杂的挥绘制代码并没有执行,相反,系统只需要简单的更新LinearLayout的显示列表。
在没有启用硬件加速的app中,list和它的parent的绘制代码都需要重新执行一遍。
不支持的绘制操作
启用硬件加度的情况下,2D渲染pipeline支持:大多数常见和不常见的Canvas绘制操作,android自带的所有的用来渲染应用的绘制操作,默认的组件和布局,还有一些高级的视觉效果,比如:映像,文理。下面的表格展示了不同API level对不同操作的支持级别:
Canvas缩放
硬件加速2D渲染pipeline一开始只支持不缩放的绘制,因为大的缩放值会严重的降低绘制操作的质量,
比如GPU在以scale 1.0做纹理绘制的时候。在API level小于17的时候,这些操作会导致缩放伪影。
下面的表格展示了什么时间实现的能正确处理大范围缩放:
注意:简单的图形是说用不包含PathEffect和非null join(通过调用setStrokeJoin()/setStrokeMiter())的Paint调用
drawRect(),drawCircle(),drawOval(),drawRoundRect(),和drawArc()(用useCenter=false)命令绘制出来的图形。
其他的就是复杂的图形。
如果你的应用被这样的特性或者是限制所影响,可以在受影响的部分通过调用setLayerType(View.LAYER_TYPE_SOFTWARE, null)来关闭硬件加速。
这种方式使你仍然能够在其他地方利用到硬件加速。
参考“控制硬件加速”章节获取更多如何在app的不同级别启用和禁用硬件加速的信息
View Layers
在所有版本的Android中,view都可以渲染到off-screen的缓冲区里面去,要么通过使用view的绘图缓存,要么使用Canvas.saveLayer()。
Off-screen缓冲区或者叫layers有许多用处,它在复杂view做动画或者是应用复杂的效果的时候可以带来更好的性能。
比如:可以使用Canvas.saveLayer()来实现渐变效果,把view临时渲染到layer中,然后用透明度合成以后显示到屏幕上。
从Android 3.0(API level 11)开始,可以使用View.setLayerType()方法来更好的控制怎样和什么时候使用layer。
这个方法要两个参数:一个是你想要使用的layer的类型,一个是可选的Paint对象,它描述了应该如何合成这个layer。
你可以使用Paint参数来应用颜色过滤、混合效果或者是设置layer的透明度。
view可以使用下面三种layer中的一个:
LAYER_TYPE_NONE:view是按照正常的渲染方式进行渲染,不会使用off-screen缓冲区。这是默认的行为。
LAYER_TYPE_HARDWARE: 如果app启用了硬件加速,view会以硬件方式渲染成硬件texture(The view is rendered in hardware into a hardware texture if the application is hardware accelerated.)。如果app没有启用硬件加速,LAYER_TYPE_HARDWARE的作用和LAYER_TYPE_SOFTWARE是一样的。
LAYER_TYPE_SOFTWARE: view用软件的方式渲染到bitma中。(The view is rendered in software into a bitmap.)
使用哪一种layer取决于你的目标:
性能:使用LAYER_TYPE_HARDWARE把view渲染成硬件texture。view被渲染进layer以后,只有到view调用了invalidate()的时候,它的绘制代码才会被执行。有些动画,比如alpha动画可以直接应用到layer中,这对GPU是非常高效的。
视觉效果:使用LAYER_TYPE_HARDWARE或者是LAYER_TYPE_SOFTWARE和Paint给view应用特殊的视觉效果。比如:可以使用ColorMatrixColorFilter仅以黑色和白色来绘制view。
兼容性:使用LAYER_TYPE_SOFTWARE强制view以软件方式进行渲染。如果被硬件加速的view(比如你的整个app都是硬件加速的)渲染出了问题,这是一种很方便的解决硬件渲染pipeline限制的方式。
View layers和动画
当app启用了硬件加速的时候,Hardware layers可以更快更流畅的传递动画。让一个复杂的需要很多绘制操作的view以每秒60帧做动画很多时候是不可能的,但是使用hardware layers把view渲染成硬件textture可以改善这种状况。硬件textture可用来给view做动画,减少view在动画过程中重绘的需要。除非是改变了会调用view的invalidate()的属性,或者是手动调用了invalidate(),否则view是不会被重绘的。如果在你的app中运行动画的时候,没有得到你想要的流畅的效果,考虑在做动画的view上启用hardware
当view背后有hardware layer做支撑的时候,view的一些属性是以把layer合成到屏幕上的方式进行处理的。设置这样的属性是非常高效的,因为他们不需要view的失效重绘。下面的属性列表会应影响layer合成的方式,调用这些属性的set方法会导致优化的失效(optimal invalidation),因为不需要重绘目标view。
alpha: 改变layer的透明度
x, y, translationX, translationY:改变layer的位置
scaleX, scaleY: 改变layer的大小
rotation, rotationX, rotationY: 改变layer在3D空间中的旋转角度
pivotX, pivotY: 改变layer的变换起点
这些属性是当view使用ObjectAnimator做动画的时候使用的名字,如果你想要访问这些属性,调用他们的set或者是get方法就可以了。
比如,修改alpha属性,可以调用setAlpha()。下面的代码片段展示了在3D中沿着Y轴让view旋转的最有效的方式:
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();
因为hardware layers会消耗video memory(这是什么玩意??),极力推荐只有在动画执行期间才启用,动画完成以后要立马禁用。
可以用动画监听来做:
View.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
animator.start();
了解更多关于动画属性的信息,请参考“动画属性”。
提示和技巧
切换到硬件加速2D图像显示可以立马就能提升性能,但是,你仍然要遵守下面的规则来设计你的app,才能更有效地使用GPU:
减少app中view的数量
系统要绘制的view的数量越多,系统运行的就越慢。这个对软件渲染pipeline同样适用。减少view是优化UI的最简单的方式。
避免过度绘制
不要在view上面绘制过多的层。删掉那些完全被上面的不透明的view所遮挡的view。如果你需要绘制很多层的view,考虑合并他们到一个层。
现在的硬件上一个好的经验是不要在屏幕上绘制超过一帧2.5倍的像素。
不要在draw方法中创建渲染对象
一种常见的错误是每当渲染方法调用的时候都去创建一个新的Paint或者Path。
这会强制垃圾收集器更频繁的运行,并且会越过硬件pipeline里面的缓存和优化
不要频繁的修改view的形状
复杂的形状、路径、和环形的实例是使用纹理遮罩效果来渲染的,
每当你创建或者是修改path的时候,硬件pipeline都会创建一个新的遮罩,这个代价是很昂贵的
不要频繁的修改bitmap
每当你修改了bitmap的内容,下次绘制的时候会被当成是一个GPU的纹理被再次上传。
小心的使用alpha
当使用setAlpha(),AlphaAnimation,或者是ObjectAnimator来设置view的透明度的时候,view是在一个off-screen的缓冲区里面进行渲染的,这需要双倍的填充率。
当在很大的view上应用alpha的时候,要考虑设置view的layer type为:LAYER_TYPE_HARDWARE。
总结一下:
(1)硬件加速是从3.0才开始支持的,它改变了android的绘图模型,能提高绘图的性能。
(2)view layer从一开始就支持,在复杂view做动画或者是应用复杂的效果的时候可以带来更好的性能。
(3)view layer的一种类型叫做LAYER_TYPE_HARDWARE,启用了硬件加速就用硬件渲染,不启用就用软件渲染。
(4)总之,做动画或者复杂的显示效果的时候,总是设置LAYER_TYPE_HARDWARE,肯定是没有问题的。如下:
View.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
});animator.start();
(5)启用硬件加速已知的一些问题,参考:
项目中要调用支付宝的极简支付,出现了花屏view错乱的情况,禁用硬件加速后解决了。
项目中有个页面非常卡,fps很低,后来发现是给一个很大的view设置了alpha,然后加了一句: view.setLayerType(View.LAYER_TYPE_HARDWARE, null);性能发生了翻天覆地的改善,简直不可思议!
(6)view.isHardwareAccelerated()一定要在attached到window以后调用才能得到,在onCreate(),onResume()都是获取不到的。参考:
附一个android版本号和API level的对应关系表:
没有更多推荐了,

我要回帖

更多关于 渲染模块 的文章

 

随机推荐