Android性能优化之绘制优化

前言

成为一名优秀的Android开发,须要一份完备的知识体系,在这里,让咱们一块儿成长为本身所想的那样~。

前一段时间,笔者带你们一块儿深刻探索Android布局优化深刻探索Android卡顿优化,内容难度比较大,所以,本篇文章就是上述两篇文章的基础篇,掌握这篇文章的知识后,阅读上面两篇文章的难度会小不少。html

咱们都知道,形成绘制不流畅最大的罪魁祸首就是卡顿,而卡顿的主要场景有不少,按场景能够分红4类:UI绘制、应用启动、页面跳转、事件响应,其中又可细分为以下:python

一、UI

  • 绘制
  • 刷新

二、启动

  • 安装启动
  • 冷启动
  • 热启动

三、跳转

  • 页面间跳转
  • 先后台切换

四、响应

  • 按键
  • 系统事件
  • 滑动

而形成其产生的根本缘由能够分为两大类:android

一、界面绘制

  • 绘制层级深
  • 页面复杂
  • 刷新不合理

二、数据处理

  • 数据处理在UI线程
  • 占用CPU高,致使主线程拿不到时间片
  • 内存增长致使GC频繁,从而引发卡顿

1、Android系统显示原理

Android的显示过程能够简单归纳为:Android应用程序把通过测量、布局、绘制后的surface缓存数据、经过SurfaceFlinger把数据渲染到显示屏幕上,经过Android的刷新机制来刷新数据。也就是说应用层负责绘制,系统层负责渲染,经过进程间通讯把应用层须要绘制的数据传递到系统层服务,系统层服务经过刷新机制把数据更新到屏幕git

一、绘制原理

应用层

在Android的每一个View都会通过Measure和Layout来肯定当前须要绘制的View所在的大小和位置,而后,再经过Draw绘制到surface上。在Android系统中总体的绘制源码是在ViewRootImpl类的performTraversals()方法,经过这个方法能够看出Measure和Layout都是递归来获取View的大小和位置,而且以深度做为优先级。显然,层级越深,元素越多,耗时就越长。github

对于绘制,Android支持两种绘制方式:算法

  • 软件绘制(CPU)
  • 硬件绘制(GPU)

硬件加速从Android 3.0开始支持,它在UI显示和绘制效率方面远高于软件绘制。但它的局限以下:shell

  • 耗电:GPU功耗高于CPU。
  • 兼容性:不兼容某些接口和函数。
  • 内存大:使用OpenGL的接口须要占用内存8MB。

系统层

将数据渲染到屏幕上是经过系统级进程中的SurfaceFlinger服务来实现的,它的主要工做流程以下:json

  • 一、响应客户端事件,建立Layer与客户端的Surface创建链接
  • 二、接收客户端数据和属性,修改Layer属性,如尺寸、颜色、透明度等
  • 三、将建立的Layer内容刷新到屏幕上
  • 四、维持Layer的序列,并对Layer的最终输出作裁剪计算

其中,SurfaceFlinger系统进程和应用进程使用了匿名共享内存SharedClient,而且,每个应用和SurfaceFlinger之间都会建立一个SharedClient,在每一个SharedClient中,最多能够建立31个SharedBufferStack,每个SharedBufferStack对应一个Surface,即一个window。(其中包含了两个(小于4.1版本)或者三个(4.1及以上版本)缓冲区)canvas

所以,从上可知,一个Android应用程序最多能够包含31个窗口。最后,显示的总体流程以下:缓存

  • 一、应用层绘制到缓冲区
  • 二、SurfaceFlinger把缓冲区数据渲染到屏幕,其中使用了Android匿名共享内存SharedClient缓存须要显示的数据来达到目的

绘制的过程首先是CPU准备数据,经过Driver层把数据交给CPU渲染,其中CPU主要负责Measure、Layout、Record、Execute的数据计算工做,GPU负责Rasterization(栅格化)、渲染。由于图形API不容许CPU直接和GPU通讯,因此要经过一个图形驱动的中间层来进行链接,在图形驱动里面维护了一个队列,CPU把display list(待显示的数据列表)添加到队列中,GPU从这个队列中取出数据进行绘制,最终才在显示屏上显示出来

Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,若是每次渲染都成功,这样就可以达到流畅画面所需的60FPS。

二、刷新机制

在4.1版本的Project Butter中对Android Display系统进行了重构,引入了三个核心元素:VSYNC(Vertical Synchronization)、Triple Buffer(三级缓冲)、Choreographer。其中做为Project Buffer核心的VSYNC,即垂直同步可认为是一种定时中断而Choreographer起调度的做用,将绘制工做统一到VSYNC的某个时间点上,使应用的绘制工做有序

那么,为何要推出Project Butter呢?

目的是解决刷新不一样步的问题。

在Tripe Buffer出现以前,Android的显示系统采用的是双缓冲技术。

为何要使用双缓冲技术?

在Linux上一般使用 Framebuffer 来作显示输出,当用户进程更新Framebuffer中的数据后,显示驱动会把FrameBuffer中每一个像素点的值更新到屏幕,可是若是上一帧数据还没显示完,Framebuffer中的数据又更新了,就会带来残影的问题,用户会以为有闪烁感,因此采用了双缓冲技术

双缓冲的含义?

双缓冲意味着要使用两个缓冲区(在上文说起的SharedBufferStack中),其中一个称为Front Buffer,另外一个称为Back Buffer。UI老是先在Back Buffer中绘制,而后再和Front Buffer交换,渲染到显示设备中。即只有当另外一个buffer的数据准备好后,才会经过io_ctl系统调用来通知显示设备切换Buffer

当第一帧数据没有及时处理时,为何CPU不能在第二个16ms处即VSync到来就开始工做呢?

由于只有两个Buffer;因此4.1版本后,出现了第三个缓冲区:Triple Buffer。它利用CPU/GPU的空闲等待时间提早准备好数据,并不必定会使用

注意

除非必要,大部分状况下只是用到双缓冲。并且,缓冲区并非越多越好,要作到平衡到最佳效果。

Google作了这么多的优化,为何实际开发中应用还存在卡顿现象?

由于VSync 中断处理的线程优先级必定要最高,不然即便接收到VSync中断,不能及时处理,也是徒劳无功。

Choreographer的做用是什么?

当收到VSYNC信号时,调用用户设置的回调函数。回调类型的优先级从高到低为CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL

三、卡顿的根本缘由

  • 绘制任务过重、绘制一帧内容耗时太长。
  • 主线程太忙,致使VSync信号到来时尚未准备好数据从而致使丢帧。

2、性能分析工具

Android经常使用的绘制优化工具通常有以下几种:

  • Hierarchy View:查看Layout层次
  • Android Studio自带的Profile CPU工具
  • 静态代码检查工具Lint
  • Profile GPU Rendering
  • TraceView
  • Systrace

这里咱们来说解后面三种分析工具。

一、卡顿检测工具Profile GPU Rendering

它是Android手机上自带的一个辅助工具,打开Profile GPU Rendering后能够看到实时刷新的彩色图,其中每一根竖线表示一帧,由多个颜色组成。

Android M以前

在Android M版本以前,每一条柱状图都由红、黄、蓝、紫组成,分别对应每一帧在不一样阶段的实际耗时不一样颜色的解释以下:

  • 蓝色:表示测量绘制的时间,须要多长时间去建立和更新DisplayList。在蓝色的线很高时,有多是由于须要从新绘制,或者自定义视图的onDraw函数处理事情太多
  • 红色:表示Android进行2D渲染Display List的执行的时间。当红色的线很是高时,多是因为从新提交了视图致使的
  • 橙色:处理时间或CPU告诉GPU渲染一帧的地方,若是柱状图很高,就意味着GPU太繁忙了
  • 紫色:将资源转移到渲染线程的时间。(4.0版本以上提供)

Android M以后

而且,从Android M开始变成了渲染八步骤:

一、橙色-Swap Buffers

表示GPU处理任务的时间。

二、红色-Command Issue

进行2D渲染显示列表的时间,越高表示须要绘制的视图越多。

三、浅蓝-Sync&Upload

准备有待绘制的图片所耗费的时间,越高表示图片数量越多或图片越大。

四、深蓝-Draw

测量和绘制视图所需的时间,越高表示视图越多或onDraw方法有耗时操做。

五、一级绿-Measure/Layout

onMeasure与onLayout所花费的时间。

六、二级绿-Animation

执行动画所须要花费的时间。越高表示使用了非官方动画工具或执行中有读写操做。

七、三级绿-Input Handling

系统处理输入事件所耗费的时间。

八、四级绿-Misc Time/Vsync Delay

主线程执行了太多任务,致使UI渲染跟不上vSync的信号而出现掉帧。

此外,可经过以下adb命令将具体的耗时输出到日志中来分析:

adb shell dumpsys gfxinfo com.**.** 
复制代码

二、TraceView

它主要用来分析函数的调用过程,能够对Android的应用程序以及Framework层代码进行性能分析。

使用TraceView查看耗时,主要关注Calls + Recur Calls / Total和(该方法调用次数+递归次数)和Cpu Time / Call(该方法耗时)这两个值,而后优化这些方法的逻辑和调用次数,减小耗时

注意

RealTime(实际时长)的实际执行时间要比CPU Time要长,由于它包括了CPU的上下文切换、阻塞、GC等时间消耗。

三、Systrace UI性能分析

Systrace是Android 4.1及以上版本提供的性能数据采样和分析工具,它的主要做用能够归结为以下两点:

  • 一、收集Android关键子系统(如surfaceflinger、WindowManagerService等Framework部分关键模块、服务、View系统等)的运行信息,这样能够更直观地分析系统瓶颈,改进性能
  • 二、跟踪系统的I/0操做、内核工做队列、CPU负载等,在UI显示性能分析上提供很好的数据,特别是在动画播放不流畅、渲染卡顿等问题上

一、Systrace使用方法

使用事项以下:

  • 支持4.1版本及以上。
  • 4.3之前的系统版本须要打开Setting>Developer options>Monitoring>Enable traces。

通常咱们使用命令行来获得输出的html表单,在4.3版本及以上能够省略设置跟踪类别标签来获取默认值。命令以下:

cd android-sdk/platform-tools/systrace
python systrace.py --time=10 -o mynewtrace.html sched gfx view wm
复制代码

其中,经常使用的几个参数命令以下:

  • -o :保存的文件名。
  • -t N, --time=N:多少秒内的数据,默认为5s,以当前时间点日后倒N秒时间。

其他标签用法请参见此处

此外,咱们可使用代码插桩的方式,在Android 4.3及以上版本可使用Trace类的Trace.beginSection()与Trace.endSection()方法来进行追踪。其中须要注意:

  • 一、保证beginSection和endSection的调用次数要匹配。
  • 二、Trace的begin与end必须在同一线程中执行。

二、分析Systrace报告

使用Chrome打开文件后,其中和UI绘制关系最密切的是Alerts和Frame两个数据:

  • Alerts:标记了性能有问题的点,单击该点能够查看详细信息,右侧的Alerts框还能够看到每一个类型的Alerts的数量。
  • Frame:每一个应用都有一行专门显示frame,绘制正常时每一帧就显示为一个绿色的圆圈。当显示为黄色或者红色时,则代表它的渲染时间超过了16.6ms

最后,这里再列出在Systrace便于操做的快捷键:

  • W:放大
  • S:缩小
  • A:左移
  • D:右移

3、布局优化方式

一、减小层级

  • 合理使用RelativeLayout和LinearLayout。
  • 合理使用Merge。

合理使用RelativeLayout和LinearLayout

RelativeLayout也存在性能低的问题,缘由是RelativeLayout会对子View作两次测量。但若是在LinearLayout中有weight属性,也须要进行两次测量,可是由于没有更多的依赖关系,因此仍然会比RelativeLayout的效率高。

注意

因为Android的碎片化程度很高,因此使用RelativeLayout能使构建的布局适应性更强。

合理使用Merge

merge的原理:在Android布局的源码中,若是是Merge标签,那么直接将其中的子元素添加到Merge标签Parent中。

注意

  • 一、Merge只能用在布局XML文件的根元素。
  • 二、使用merge来加载一个布局时,必须指定一个ViewGroup做为其父元素,而且要设置加载的attachToRoot参数为true。
  • 三、不能在ViewStub中使用Merge标签。缘由就是ViewStub的inflate方法中根本没有attachToRoot的设置。

二、提升显示速度

ViewStub是一个轻量级的View,它是一个看不见的,而且不占布局位置,占用资源很是小的视图对象。能够为ViewStub指定一个布局,加载布局时,只有ViewStub会被初始化,而后当ViewStub被设置为可见时,或是调用了ViewStub.inflate()时,ViewStub所指向的布局才会被加载和实例化,而后ViewStub的布局属性都会传给它指向的布局

注意:

  • 一、ViewStub只能加载一次,以后ViewStub对象会被置为空。因此它不适用于须要按需显示隐藏的状况。
  • 二、ViewStub只能用来加载一个布局文件,而不是某个具体的View。
  • 三、ViewStub中不能嵌套Merge标签。

三、布局复用

Android的布局复用能够经过 include 标签来实现。

四、小结

最后,下面列出了我日常作布局优化时的一些小技巧:

  • 使用标签加载一些不经常使用的布局。
  • 尽量少用wrap_content,wrap_content会增长布局measure时的计算成本,已知宽高为固定值时,不用wrap_content。
  • 使用TextView替换RL、LL。
  • 使用低端机进行优化,以发现性能瓶颈。
  • 使用TextView的行间距替换多行文本:lineSpacingExtra/lineSpacingMultiplier。
  • 使用Spannable/Html.fromHtml替换多种不一样规格文字。
  • 尽量使用LinearLayout自带的分割线。
  • 使用Space添加间距。
  • 多利用lint + alibaba规约修复问题点。
  • 嵌套层级过多能够考虑使用约束布局。

4、避免过分绘制

致使过分绘制的主要缘由通常有以下两点:

  • XML布局:控件有重叠且都有设置背景。
  • View自绘:View.OnDraw里面同一个区域被绘制屡次。

一、过分绘制检测工具

打开手机开发者选项中的Show GPU Overdraw选项,会有不一样的颜色来表示过分绘制次数,依次是无、蓝、绿、淡红、深红,分别对应0-4次过分绘制。

二、如何避免过分绘制

一、布局上的优化

  • 移除XML中非必需的背景,或根据条件设置。
  • 有选择性地移除窗口背景:getWindow().setBackgroundDrawable(null)。
  • 按需显示占位背景图片。

好比:在获取Avatar的图像以后,把ImageView的Background设置为Transparent,只有当图像没有获取到时,才设置对应的Background占位图片。

二、自定义View优化

经过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法能够指定一块矩形区域,只有在这个区域内才会被绘制。而且,它还能够节约CPU和GPU资源,在clipRect区域以外的绘制指令都不会被执行。

在绘制一个单元以前,首先判断该单元的区域是否在Canvas的剪切域内。若不在,直接返回,避免CPU和GPU的计算和渲染工做。

5、合理的刷新机制

一、减小刷新次数

  • 控制刷新频率
  • 避免没有必要的刷新

二、避免后台线程的影响

如经过监听ListView的onScrollStateChanged事件,在滚动时暂停图片下载线程工做,结束后再开始,能够提升ListView的滚动平滑度,RecyclerView同理。

三、缩小刷新区域

如自定义View通常采用invalidate方法刷新,可使用如下重载方法指定要刷新的区域:

  • invalidate(Rect dirty);
  • invalidate(int left, int top, int right, int bottom);

6、提高动画性能

提高动画性能主要从如下三个纬度着手:

  • 一、流畅度:控制每一帧动画在16m内完成。
  • 二、内存:避免内存泄漏,减少内存开销。
  • 三、耗电:减少运算量,优化算法,减少CPU占用。

一、帧动画

消耗资源最多,效果最差,能不用就不用。

二、补间动画

使用补间动画实现致使View重绘很是频繁,更新DisplayList的次数过多,且有如下缺点:

  • 一、只能用于View对象。
  • 二、只有4种动画操做。
  • 三、只是改变View的显示效果,可是不会真正改变View的属性。

三、属性动画

相比于补间动画,属性动画重绘明显会少不少,应优先使用。

四、使用硬件加速

一、硬件加速原理

核心类:DisplayList,每个View对应一个。

在打开硬件渲染后绘制View时,其中执行绘制的draw()方法会把全部绘制命令记录到一个新的显示列表(DisplayList),这个显示列表包含了输出的View层级的绘制代码,但并非加入到显示列表就马上执行,当这个ViewTree的DisplayList全都记录完毕后,由OpenGLRender负责将Root View中的DisplayList渲染到屏幕上。而invalidate()方法只是在显示列表中记录和更新显示层级,去标记不须要绘制的View

二、硬件加速控制级别

若是应用程序中只使用了标准View或者Drawable,就能够为整个系统打开硬件加速的全局设置。

三、在动画上使用硬件加速

此时,会使用硬件纹理操做对一个View进行动画绘制,若是不调用invalidate()方法,就能够减小对View自身频繁的重绘。同时Android 3.0的属性动画也减少了重绘,当View经过硬件层返回时,最终全部的层叠画面显示到屏幕,View的属性同时被处理好,所以只要设置这些属性,就能够明显提升绘制的效率,它们不须要View重绘,设置属性后,View会自动刷新。所以,属性动画中绘制的递归次数比补间动画少不少。

在Android 3.0前,使用View的绘制缓冲或Canvas.saveLayer()函数对离屏缓冲进行渲染。Android 3.0后则使用View.setLayerType(type, paint)方法代替,type能够为如下三种Layer类型之一:

  • LAYER_TYPE_NONE:普通渲染方式,不会返回一个离屏的缓冲,默认值。
  • LAYER_TYPE_HARDWARE:若是这个应用使用了硬件加速,这个View将会在硬件中渲染为硬件纹理。
  • LAYER_TYPE_SOFTWARE:此View经过软件渲染为一个Bitmap。

设计一个动画的流程以下:

一、将要执行动画的View的LayerType设置为LAYER_TYPE_HARDWARE。

二、计算动画View的属性等信息,更新View的属性。

三、若动画结束,将LayerType设置为NONE。

硬件加速须要注意的问题:

  • 在软件渲染时,可使用重用Bitmap的方法来节省内存,可是若是开起来硬件加速,这个方案就不起做用。
  • 开启硬件加速的View在前台运行时,须要耗费额外的内存,加速的UI切换到后台时,产生的额外内存有可能不释放。
  • 当UI中存在过渡绘制时,硬件加速会比较容易发生问题。

7、卡顿监控方案与实现

目前比较流行的方案都是利用了Looper中的Printer来实现监控。

一、监控原理

利用主线程的消息队列处理机制,经过自定义Printer,而后在Printer中获取到两次被调用的时间差,这个时间差就是执行时间。若是该时间超过设定的卡顿阈值(如1000ms)时,主线程卡顿发生,并抛出各类有用信息,供开发者分析。(此外,也能够在UI线程之外开启一个异步线程,定时向UI线程发送一个任务,并记下发送时间。任务的内容是将执行时间同步到发送线程,若是UI线程被阻塞,那么发送过去的任务不能被准时执行。但此方法会增长系统开销,不可取)

卡顿信息捕获

发生卡顿时须要捕获以下四类信息,以提升定位卡顿问题的效率与精度。

  • 一、基础信息:系统版本、机型、进程名、应用版本号、磁盘空间、UID等。
  • 二、耗时信息:卡顿开始和结束时间。
  • 三、CPU信息:CPU的信息、总体CPU使用率和本进程CPU使用率(可粗略判断是当前应用消耗CPU资源太多致使的卡顿,仍是其余缘由)等。
  • 四、堆栈信息。

注意

这里的信息建议抽样上报或者能够先将其保存到本地,在合适的时机以及达到必定的量时,再压缩上报到服务器,供开发者分析。具体监控代码实现能够参考BlockCanary开源项目的代码。

8、总结

至此,这里咱们分析一下绘制优化应经历的几个过程:

  • 一、发现问题:除使用时感知的卡顿外,还应经过卡顿监控工具来发现总体的耗时状况,或打开开发者选项的一些辅助工具来发现问题。
  • 二、分析问题:可使用Systrace和TraceView来分析耗时,使用Hierarhy Viewer来分析页面层级。
  • 三、寻求缘由:深刻探索致使问题的根本缘由。
  • 四、解决问题。

应用之因此会出现卡顿,除了绘制方面的问题,还有一个影响因素就是内存,不合理地使用内存不只会致使卡顿,还会对耗电和应用的稳定性形成很大影响。下一篇性能优化文章,笔者将对Android中的内存优化进行全面的讲解,若读者以为哪里有写的很差的地方或有误的地方但愿多多进行批评指正,愿咱们共同进步和成长!

参考连接:

一、Android应用性能优化最佳实践

二、必知必会 | Android 性能优化的方面方面都在这儿

Contanct Me

● 微信:

欢迎关注个人微信:bcce5360

● 微信群:

微信群若是不能扫码加入,麻烦你们想进微信群的朋友们,加我微信拉你进群。

● QQ群:

2千人QQ群,Awesome-Android学习交流群,QQ群号:959936182, 欢迎你们加入~

About me

很感谢您阅读这篇文章,但愿您能将它分享给您的朋友或技术群,这对我意义重大。

但愿咱们能成为朋友,在 Github掘金上一块儿分享知识。。

相关文章
相关标签/搜索