Android 性能优化---绘制优化篇

Posted 冬天的毛毛雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 性能优化---绘制优化篇相关的知识,希望对你有一定的参考价值。

作者:FashionLazy

一、绘制原理

android中,每个View都有三个固定的核心步骤,并依次去执行。它们分别是measure、layout以及draw,通过Measure和Layout来确定当前需要绘制的View所在的大小和位置,再通过绘制(Draw)到surface。measure、layout和draw方法主要是运行在系统的应用框架层,而真正将数据渲染到屏幕上的则是系统Nativie层的SurfaceFlinger服务来完成的。

1.1 应用层

Measure

用深度优先原则递归得到所有视图(View)的宽、高;获取当前View的正确宽度childWidthMeasureSpec和高度childHeightMeasureSpec之后,可以调用它的成员函数Measure来设置它的大小。如果当前正在测量的子视图child是一个视图容器,那么它又会重复执行操作,直到它的所有子孙视图的大小都测量完成为止。

Layout

位置计算的递归原理跟测量的差不多,用深度优先原则递归得到所有视图(View)的位置;当一个子View在应用程序窗口左上角的位置确定之后,再结合它在前面测量过程中确定的宽度和高度,就可以完全确定它在应用程序窗口中的布局。

Draw

目前Android支持了两种绘制方式:软件绘制(CPU)和硬件加速(GPU),其中硬件加速在Android 3.0开始已经全面支持,很明显,硬件加速在UI的显示和绘制的效率远远高于CPU绘制,但硬件加速并非如大家所想的那么完善,它也存在明显的缺点:

  • 耗电:GPU的功耗比CPU高。
  • 兼容问题:某些接口和函数不支持硬件加速。
  • 内存大:使用OpenGL的接口至少需要8MB内存。

所以是否使用硬件加速,需要考虑一些接口是否支持硬件加速,同时结合产品的形态和平台,比如TV版本就不需要考虑功耗的问题,而且TV屏幕大,使用硬件加速容易实现更好的显示效果。而绘制也并不是直接绘制到屏幕上,而是由应用层先绘制到我们的缓冲区,然后再由系统层的SurfaceFlinger服务将数据渲染到屏幕上。

1.2 系统层

真正把需要显示的数据渲染到屏幕上,是通过系统级进程中的SurfaceFlinger服务来实现的,SurfaceFlinger的主要工作内容如下:

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

SharedClient

SharedClient是Android中的匿名共享内存,Android的显示系统使用该匿名共享内存机制实现应用与SurfaceFlinger间的跨进程的数据传输。每一个应用和SurfaceFlinger之间都会创建一个SharedClient,每个SharedClient对象中,最多可以创建31个SharedBufferStack,每个Surface对应一个SharedBufferStack,也就是一个Window。这也意味着一个应用程序最多只能创建31个窗口,同时SharedBufferStack又包含有两个缓冲区(低于4.1版本)或者三个缓存区(4.1及以上)。

Android显示框架

对于显示流程我们可以做个简单的总结:首先应用层绘制到缓存区,其次由SurfaceFlinger从缓存区中把数据渲染到屏幕,这中间的数据共享则通过SharedClient实现应用层与SurfaceFlinger实现跨进程的通信。

二、刷新机制

首先我们来看下安卓系统的渲染数据流程图

  1. 首先CPU准备数据,CPU主要负责Measure、Layout、Record、Execute的数据计算工作;
  2. CPU会把display list中的数据添加到图形驱动层所维护的一个队列中,通过Graphics Driver层把数据交给GPU渲染;
  3. GPU从Graphics Driver层的队列中取出数据进行绘制,最终把数据渲染到显示屏上

2.1 FPS(Frames Per Second)

FPS(Frames Per Second)它是指画面每秒传输帧数,通俗来讲就是指动画或视频的画面数,最简单的举例就是我们玩游戏时,如果画面在60fps则不会感觉到卡顿,如果低于60fps,比如50fps则会感觉到卡顿,这个时候我们可能需要针对性的做一些优化。要想画面保持在60fps,则需要每个绘制时长在16ms以内,如下图所示。

2.2 Project Butter

在Android 4.1开始,Project Butter被推出,其主要的目的就是解决Android UI流畅性差的问题,Project Butter对Android Display系统进行了重构,引入三个核心元素:VSYNC、Triple Buffer和Choreographer。

2.2.1 VSYNC 信号

VSYNC是Vertical Synchronization(垂直同步)的缩写,可以简单地把它认为是一种定时中断,一旦收到VSYNC中断,CPU就开始处理各帧数据。下面我们可以简单的看下未加入VSYNC信号及加入VSYNC信号后的区别

未加入VSYNC前

从上图我们可以看出在没有VSYNC前,第一个16msCPU、GPU以及Display都能正常工作,但是到了第二个16ms时,CPU与GPU在第二个16ms快结束时才开始工作,而没有在第二个16ms开始时就加入工作,这个时候就导致了第三个16ms仍然显示第一帧的内容,而不是第二帧的内容,出现了一次卡顿。也是为了避免CPU没有及时的处理UI绘制,所以在4.1版本推出了Project Butter,其中VSYNC则是解决刷新不同步的问题的。

加入VSYNC后

此时,系统每16ms会发送一次VSYNC信号,每收到 VSYNC 中断,CPU 会立即准备 Buffer 数据,由于大部分显示设备刷新频率都是 60Hz(一秒刷新 60 次),也就是说一帧数据的准备工作都要在 16ms 内完成。这样应用总是在 VSYNC 边界上开始绘制,而 SurfaceFlinger 总是 VSYNC 边界上进行合成。这样可以消除卡顿,并提升图形的视觉表现。

2.2.2 Triple Buffer三缓冲机制

双缓冲

在了解三缓冲前,我们先了解下双缓冲。一般来说,不同的 View 或者 Activity 它们都会共用一个 Window,也就是共用同一个 Surface。而每个 Surface 都会有一个 BufferQueue 缓存队列,但是这个队列会由 SurfaceFlinger 管理,通过匿名共享内存机制(SharedClient)与 App 应用层交互。

整个流程如下:

  • 每个 Surface 对应的 BufferQueue 内部都有两个 Graphic Buffer ,一个用于绘制一个用于显示。我们会把内容先绘制到离屏缓冲区(OffScreen Buffer),在需要显示时,才把离屏缓冲区的内容通过 Swap Buffer 复制到 Front Graphic Buffer 中
  • 这样 SurfaceFlinge 就拿到了某个 Surface 最终要显示的内容,但是同一时间我们可能会有多个 Surface。这里面可能是不同应用的 Surface,也可能是同一个应用里面类似 SurefaceView 和 TextureView,它们都会有自己单独的 Surface
  • 这个时候 SurfaceFlinger 把所有 Surface 要显示的内容统一交给 Hareware Composer,它会根据位置、Z-Order 顺序等信息合成为最终屏幕需要显示的内容,而这个内容会交给系统的帧缓冲区 Frame Buffer 来显示(Frame Buffer 是非常底层的,可以理解为屏幕显示的抽象)

从上图我们可以看到,双缓冲在理想的状态下,每16ms接收到VSync信号,在16ms内处理完CPU与GPU能处理完各自的数据,同时可能还存在富余的时间,这种情况说明CPU与GPU的FPS被拉低到与Display的FPS(一般都是60HZ)相同,此时图像显示的效果就会非常平滑。

但我们也会发现新的问题,如上图所示,即如果在16ms内,CPU与GPU还未处理完数据,但这时候下一个VSync到来了,B还未处理好数据,A仍需要在Display中显示,由于双缓冲的两个帧都被占用,从而导致了该次16ms未做任何处理,且B帧还需要再等待下一次VSync才能被刷新到Display上,为了解决这个问题,则出现了三缓冲。

三缓冲

由上图得出,当再次出现VSync信号到来,A、B被占用的问题时,可以使用C Buffer进行数据处理,虽然前面还是多显示一次A帧,但不至于遇到该情况时后续出现频繁的掉帧情况。与双缓冲相比,三缓冲在牺牲一个16ms的延迟同时保证了图像的连续性,这是可接受,而如果Buffer数量再继续增加,虽然图像连续性有所保证,但就会出现明显的滞后,所以,Buffer最好还是两个,三个足矣。

2.2.3 Choreographer

首先我们来看下Choreographer的定义与结构,如下图:

  • Choreographer是线程单例的,而且必须要和一个Looper绑定,因为其内部有一个Handler需要和Looper绑定。
  • DisplayEventReceiver是一个abstract class,其JNI的代码部分会创建一个IDisplayEventConnection的VSYNC监听者对象。这样,来自EventThread的VSYNC中断信号就可以传递给Choreographer对象了。由图8可知,当VSYNC信号到来时,DisplayEventReceiver的onVsync函数将被调用。
  • 另外,DisplayEventReceiver还有一个scheduleVsync函数。当应用需要绘制UI时,将首先申请一次VSYNC中断,然后再在中断处理的onVsync函数去进行绘制。
  • Choreographer定义了一个FrameCallback interface,每当VSYNC到来时,其doFrame函数将被调用。这个接口对Android Animation的实现起了很大的帮助作用。以前都是自己控制时间,现在终于有了固定的时间中断。
  • Choreographer的主要功能是,当收到VSYNC信号时,去调用使用者通过postCallback设置的回调函数。目前一共定义了三种类型的回调,它们分别是:
   CALLBACK_INPUT:优先级最高,和输入事件处理有关。

    CALLBACK_ANIMATION:优先级其次,和Animation的处理有关。

   CALLBACK_TRAVERSAL:优先级最低,和UI等控件绘制有关。

优先级高低和处理顺序有关。当收到VSYNC中断时,Choreographer将首先处理INPUT类型的回调,然后是ANIMATION类型,最后才是TRAVERSAL类型。更多Choreographer相关的,可以参考链接(Android Project Butter)

2.3 Android 5.0:RenderThread

虽然Project Butter的加入大大优化了Android系统的渲染效率,但是当渲染的任务比较耗时的时候,UI主线程依旧会因为任务繁重,进而出现卡顿的情况。而为了减轻UI主线程的任务,在5.0版本,引入了RenderThread技术,在进一步利用GPU对图形的绘制渲染能力的同时,将GPU绘制的任务从UI主线程中放到不同的线程去执行,从而减轻UI主线程的绘制任务,使得与用户的交互更加流畅。

由上图可以看出,RenderThread的加入将原本在UI Thread中执行的GPU绘制任务分担了,此时即使是比较复杂的渲染数据,也能保证一定的流畅性,从而提高了UI主线程的响应速度。

三、优化分析

3.1 卡顿原因

我们知道,UI问题的最大体现则是屏幕卡顿现象,由前面的绘制原理与刷新机制我们可以简单得出,造成卡顿的主要原因有下面两点:

  • 绘制任务太重,绘制一帧内容耗时太长。
  • 主线程太忙了,导致VSync信号来时还没有准备好数据导致丢帧。

3.1.1 绘制耗时过长

UI布局造成的耗时过长主要有以下几点:

  • 界面布局层级过深:一个页面的Measure、Layout和draw过程都是通过递归来完成的,多叉树遍历的时间与树的高度h相关,其时间复杂度为O(h),如果层级太深,每增加一层则会增加更多的页面显示时间。
  • 复杂的布局:对于一些存在View状态变化等情况的界面,当View发生变化时,都需要进行重新计算、创建DisplayList、渲染DisplayList,更新到屏幕上等一系列操作,如果布局很复杂,就很容易导致严重的性能问题。
  • 过渡绘制:可能的原因有重叠的UI设置了多个background,亦或者自定义View中draw方法在同一区域进行了重复的绘制。

3.1.2 主线程繁忙

主线程主要负责的工作内容有以下几点:

  • UI生命周期控制
  • 系统事件处理
  • 消息处理
  • 界面布局
  • 界面绘制
  • 界面刷新

而如果我们在主线程中加入了一些复杂的数据处理,网络请求等,那么可能会导致主线程被阻塞而不能及时响应VSync信号,出现卡顿丢帧的情况。

3.2 性能分析工具

工欲善其事必先利其器,了解了绘制的原理,知道了造成UI问题的原因,那么我们还需要进一步的去把有问题的点找出来并加以优化。Android常用的绘制优化的定位工具一般有如下几种:

  • Hierarchy View:查看Layout层次
  • Layout Inspector
  • Android Studio自带的Profile CPU工具
  • 静态代码检查工具Lint
  • GPU 渲染速度和过度绘制
  • TraceView
  • Systrace
  • btrace

3.2.1 Hierarchy View

笔者的开发环境是Mac版的,那么则主要以Mac下的版本为例去讲述,windows的基本相似。 Hierarchy View是Android SDK自带的一个布局可视化工具,但因为在Android studio3.2版本Android device monitor就被弃用了,所以我们可以通过在SDK的目录下去启动,路径是tools–>monitor,启动过程中可能由于一些配置问题,导致无法启动或者无法打开调试界面,链接是笔者遇到的一些问题,并得到了解决,如遇到其他问题可自行度娘等,或留言一起谈论。

Hierarchy View基本使用

上面是笔者之前学习Kotlin时写的一个Demo,图中分析的是主界面的布局层级,从图中我们可以看到主要有5个窗口:

  • Windows:当前连接的设备以及设备中能被分析的界面;
  • View Properties:可跟Windows窗口切换,主要是查看当前选中的View的一些状态属性;
  • Tree View:当前选中的Activity的布局中所有控件树,从左向右层级逐渐加深;
  • Tree Overview:选中的Activity布局的全局预览
  • Layout View:查看界面控件在手机上的实际布局位置

Hierarchy View查看层级与耗时

查看层级与耗时的功能可以说是Hierarchy View最实用的一个功能,能够帮助我们大致的分析出当前界面的每个View的一个大致耗时情况,下面我们看下下图的分析情况:

点击选中需要分析的View,点击1处,“Obtain layout times for tree rooted at selected node”; 可以查看到当前的View下共有22个子View,同时我们可以看到实际的Measure、Layout以及Draw的实际耗时 可以看到当前View层级下每个子View的耗时性能情况,红色表示跟当前View下的其他View相比处理速度较慢、黄色次之,绿色的表示比当前View层级下50%以上的控件速度快。

3.2.2 Layout Inspector

Layout Inspector是google提供给我们进行布局分析的一个工具,也是目前google在弃用Hierarchy View后推荐使用的一款布局分析工具,可以在Android studio的Tools-Layout Inspector中启用,具体的使用教程以及分析过程小伙伴们可以参考google提供的文档:Layout Inspector

3.2.3 Profile CPU工具

Android Studio 在3.0 及更高版本中的 Android Profiler 取代了 Android Monitor 工具。Android Profiler 工具可提供实时数据,帮助我们了解应用的 CPU、内存、网络和电池资源使用情况。Profile CPU分析工具是Android Profile性能分析器中的其中一个,我们可以使用 CPU 性能分析器分析 CPU 活动和跟踪记录,打开 CPU 性能剖析器,可按以下步骤操作:

  1. 依次选择 View > Tool Windows > Profiler 或点击工具栏中的 Profile 图标

如果 Select Deployment Target 对话框显示提示,请选择需将应用部署到哪个设备上以进行性能剖析。如果已通过 USB 连接设备但系统未列出该设备,请确保已启用 USB 调试
2. 点击 CPU 时间轴上的任意位置以打开 CPU 性能剖析器。

如上图所示,CPU 性能剖析器的默认视图包括以下时间轴:

  1. 事件时间轴:显示应用中的 Activity 在其生命周期内不断转换经历各种不同状态的过程,并指示用户与设备的交互,包括屏幕旋转事件。如需了解如何在搭载 Android 7.1(API 级别 25)及更低版本的设备上启用事件时间轴,请参阅启用高级性能剖析

  2. CPU 时间轴:显示应用的实时 CPU 使用率(以占总可用 CPU 时间的百分比表示)以及应用当前使用的线程总数。此时间轴还会显示其他进程(如系统进程或其他应用)的 CPU 使用率,以便可以将其与应用的 CPU 使用率进行对比。可以通过沿时间轴的横轴方向移动鼠标来检查历史 CPU 使用率数据。

  3. 线程活动时间轴:列出属于应用进程的每个线程,并使用下面列出的颜色在时间轴上指示它们的活动。记录跟踪数据后,可以从此时间轴上选择一个线程,以在跟踪数据窗格中检查其数据。

    • 绿色:表示线程处于活动状态或准备使用 CPU。也就是说,线程处于正在运行或可运行状态。
    • 黄色:表示线程处于活动状态,但它正在等待一项 I/O 操作(如磁盘或网络 I/O),然后才能完成它的工作。
    • 灰色:表示线程正在休眠且没有消耗任何 CPU 时间。 当线程需要访问尚不可用的资源时,就会出现这种情况。在这种情况下,要么线程主动进入休眠状态,要么内核将线程置于休眠状态,直到所需的资源可用。

    CPU 性能剖析器还会报告 Android Studio 和 Android 平台添加到应用进程的线程的 CPU 使用率,这些线程包括 JDWPProfile SaverStudio:VMStatsStudio:PerfaStudio:Heartbeat 等(不过,它们在线程活动时间轴上显示的确切名称可能有所不同)。Android Studio 报告此数据是为了方便确定线程活动和 CPU 使用率什么时候是由应用的代码实际引发的。

    CPU性能剖析器还给我们提供了很多定位分析CPU的功能方法,更多高级的用法,小伙伴们可以去Google官网查阅-CPU性能剖析器性能分析

3.2.4 静态代码检查工具Lint

Lint是一个静态扫描工具,可以在未编译时对工程进行扫描,帮我们识别出代码的一些潜在问题,而在我们工程项目较大,xml等布局文件多的时候,Lint可以快速的帮助我们把层级较深、单页面控件较多的布局文件定位出来,让我们能够快速的优化我们的布局。

扫描规则与缺陷级别

在开始扫描前,我们可以通过设置扫描规则与缺陷级别来帮助我们去做优先扫描以及剔除一些不需要的规则,针对性的提高我们的扫描速度。Mac版Lint扫描规则设置窗口打开步骤 Preferences–>Inspections–>Lint,如下图所示:

  • Accessibility–可及性
  • Compliance–合规性
  • Correctness–正确性
  • Internationalization–国际化
  • Interoperability–互通性
  • Performance–性能相关
  • Security–安全相关
  • Testing–测试相关
  • Usability–可用性

问题的严重程度(severity)从高到低依次是:

  • Fatal
  • Error
  • Warning
  • Information
  • Ignore

与布局相关的规则

与布局相关的扫描规则在Performance中

如果我们只是做布局的扫描,那么可以只勾选这两个选项,然后屏蔽我们不需要扫描规则,从而有针对性加快扫描以及优化处理。

  • Layout has too many views:表示控件太多,默认超过80个控件会提示该问题;
  • Layout hierarchy is too deep:表示布局太深,默认层级超过10层会提示该问题,可以自定义环境变量ANDROID_LINT_MAX_DEPTH来修改。布局深度增加会导致内存消耗也随之增加,因此布局尽可能浅而宽;

开启扫描

设置好Lint的扫描规则后,可以在Android studio的菜单栏中启动Lint,Analyze–>Inspect code,在扫描前我们可以选择对整个工程进行扫描,也可以去扫描指定module或者文件:

此时点击OK之后,等待lint的扫描结果:

然后我们可以根据Lint给出的结果,针对性的做出一些合理的优化,比如上图中的一个警告提示,Lint给出我们使用merge替代FrameLayout的方案。

3.2.5 GPU 渲染速度和过度绘制

分析 GPU 渲染速度

GPU 渲染模式分析工具以滚动直方图的形式直观地显示渲染界面窗口帧所花费的时间(以每帧 16 毫秒的速度作为对比基准)。

在性能较低的 GPU 上,可用的填充率(GPU 填充帧缓冲区的速度)可能很低。随着绘制一帧所需的像素数的增加,GPU 可能需要更长的时间来处理新命令,并要求系统的其余任务等待,直到它跟上进度。此分析工具可帮助确定 GPU 何时因尝试绘制像素而不堪重负,或何时因大量的过度绘制而被拖累。

启用分析器

开始前,请确保使用的是搭载 Android 4.1(API 级别 16)或更高版本的设备,并启用开发者选项。如需在使用应用时开始分析设备 GPU 渲染,请执行以下操作(以下步骤可能因不同产商设备而有所不同):

  1. 在设备上,转到 设置 并点按 开发人员选项
  2. 监控 部分,选择 GPU呈现模式分析
  3. 在“GPU 呈现模式分析”对话框中,选择在屏幕上显示为条形图,以在设备的屏幕上叠加图形。
  4. 打开要分析的应用。
检查输出

下面是有关输出的几点注意事项:

  • 对于每个可见应用,该工具将显示一个图形。
  • 沿水平轴的每个竖条代表一个帧,每个竖条的高度表示渲染该帧所花的时间(以毫秒为单位)。
  • 水平绿线表示 16 毫秒。要实现每秒 60 帧,代表每个帧的竖条需要保持在此线以下。当竖条超出此线时,可能会使动画出现暂停。
  • 该工具通过加宽对应的竖条并降低透明度来突出显示超出 16 毫秒阈值的帧。
  • 每个竖条都有与渲染管道中某个阶段对应的彩色区段。区段数因设备的 API 级别不同而异。

下表介绍了使用运行 Android 6.0 及更高版本的设备时分析器输出中某个竖条的每个区段。

更多的GPU分析内容可以查看使用 GPU 渲染模式分析工具进行分析

分析过渡绘制

这是开发者选项中的另一个功能,通过对界面进行彩色编码来帮助我们识别过度绘制。当应用在同一帧中多次绘制相同像素时,便会发生过度绘制。因此,此图可显示应用可能在何处执行不必要的渲染工作,这可能是 GPU 多此一举地渲染用户不可见的像素所导致的性能问题。因此,应尽可能修复过度绘制事件

启用过渡绘制检测功能

应先启用开发者选项(如果尚未执行此操作)。然后,如需在备上直观呈现过度绘制问题,请按以下步骤操作:

  1. 在设备上,转到 设置 并找到开发人员选项;
  2. 向下滚动到硬件加速渲染部分,并选择调试 GPU 过度绘制;
  3. 调试 GPU 过度绘制对话框中,选择显示过度绘制区域;
检查输出

Android 将按如下方式为界面元素着色,以确定过度绘制的次数:

  • 真彩色:没有过度绘制
  • 蓝色:过度绘制 1 次
  • 绿色:过度绘制 2 次
  • 粉色:过度绘制 3 次
  • 红色:过度绘制 4 次或更多次

3.2.6 TraceView

traceview是Android sdk中的一个工具,用于分析计算性能,跟踪方法耗时导致的卡顿问题。它将traceview文件转为图形,直观的反应出代码的执行时间、执行次数,便于我们分析。

生成方式

代码生成

在需要调试的代码段前后分别加入下面两行代码:

Debug.startMethodTracing("FirstKotlin-MainActivity")
...
Debug.stopMethodTracing()

然后启动程序执行对应代码的功能后,对应的trace文件将会保存到应用程序的沙盒目录下:sdcard/Android/data/PackageName/files/FirstKotlin-MainActivity.trace,此时可以通过adb命令拉取到电脑上进行分析

Profile CPU分析工具生成

首先选择要调试的应用,然后进入上图Profile CPU页面,此时点击鼠标右键,选中Record CPU trace,开始记录CPU执行情况

其次在应用中对于需要监控的功能进行响应的点击操作后,再次点击鼠标右键,选中Stop recording即可结束当前片段CPU的记录

最后把刚才记录的CPU片段保存到本地即可;

检测轨迹

如果使用代码插桩生成的trace文件,在复制到电脑上之后,可以使用Android studio的Profile进行导入,在性能剖析器的 Sessions 窗格中点击 Start new profiler session 图标

然后选择 Load from file。 Android studioCPU 性能分析器中的轨迹视图提供了多种方法查看来自所记录的轨迹的信息。

对于方法轨迹和函数轨迹,如上图所示,可以直接在 Threads 时间轴中查看 Call Chart,并从 Analysis 窗格中查看 Flame ChartTop DownBottom UpEvents 标签页。对于系统轨迹,可以直接在 Threads 时间轴中查看 Trace Events,并从 Analysis 窗格中查看 Flame ChartTop DownBottom UpEvents 标签页。 更详尽的Profile使用手册,可以阅读CPU活动性能分析-检查轨迹

3.2.7 Systrace

Systrace是Android4.1中新增的性能数据采样和分析工具。它可帮助开发者收集Android关键子系统(如surfaceflinger、WindowManagerService等Framework部分关键模块、服务,View系统等)的运行信息,从而帮助开发者更直观的分析系统瓶颈,改进性能。

Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。在Android平台中,它主要由3部分组成:

  • 内核部分:Systrace利用了Linux Kernel中的ftrace功能。所以,如果要使用Systrace的话,必须开启kernel中和ftrace相关的模块。
  • 数据采集部分:Android定义了一个Trace类。应用程序可利用该类把统计信息输出给ftrace。同时,Android还有一个atrace程序,它可以从ftrace中读取统计信息然后交给数据分析工具来处理。
  • 数据分析工具:Android提供一个systrace.py(python脚本文件,位于Android SDK目录/platform-tools/systrace中,其内部将调用atrace程序)用来配置数据采集的方式(如采集数据的标签、输出文件名等)和收集ftrace统计数据并生成一个结果网页文件供用户查看。 从本质上说,Systrace是对Linux Kernel中ftrace的封装。应用进程需要利用Android提供的Trace类来使用Systrace.

简单使用

一般我们可以使用命令行来输出html表单,在4.3版本及以上可以省略设置跟踪类别标签来获取默认值。命令如下:

python systrace.py --time=10 -o trace.html

通过上面的命令行代码,我们监测了10S内系统的运行状况,并生成了demotrace.html分析文件,路径默认存储在sdk/platform-tools/systrace下

上面的命令行代码是最为基本的使用,在实际开发检测中,我们可以使用-l指令去获取当前链接设备支持检测的系统进程:

python systrace.py -l

比如我们给前面的命令行加上gfx

python systrace.py --time=10 -o trace2.html gfx

此时生成的分析文件则是针对GPU、RenderThread等与渲染图形的系统进程相关的

下图还提供了更多systrace相关的类型或选项的命令,令我们可以更有针对性的去生成分析文件:

浏览 Systrace 报告

关于如何分析Systrace的报告,这里就不再叙述了,小伙伴们可以去google官网阅读浏览 Systrace 报告

3.2.8 btrace

btrace(又名 RheaTrace)是字节跳动开源的 一个基于 Systrace 实现的高性能 Android trace 工具,它支持在 App 编译期间自动注入自定义事件,并使用 bhook 额外提供 IO 等 native 事件。

关键特征:

  • 支持自动注入自定义事件,在编译 Apk 期间为 App 方法自动注入Trace#beginSection(String)Trace#endSection()
  • 提供额外 IO 等 native 事件,方便定位耗时原因。
  • 支持仅采集主线程 trace 事件。
  • 使用便捷,稳定性高,性能优于 Systrace。

关于如何导入btrace以及使用btrace,小伙伴们可以在github上进行查阅btrace。更多与Systrace的异同点,小伙伴们也可以阅读字节跳动技术团队发表的文章btrace 开源!基于 Systrace 高性能 Trace 工具

四、优化方法

4.1 布局优化

4.1.1 减少UI布局层级

  • 合理使用RelativeLayout和LinearLayout。
  • 合理使用Merge。
  • 使用 ConstraintLayout 替代 RelativeLayout 或者 weighted LinearLayout。
合理使用RelativeLayout和LinearLayout

RelativeLayout也存在性能低的问题,原因是RelativeLayout会对子View做两次测量。但如果在LinearLayout中有weight属性,也需要进行两次测量,但是因为没有更多的依赖关系,所以仍然会比RelativeLayout的效率高。

合理使用Merge

merge标签是用来帮助在视图树中减少重复布局的,当一个layout包含另外一个layout时。merge标签使用时有一些要点需要注意:

  • merge必须放在布局文件的根节点上;
  • merge并不是一个ViewGroup,也不是一个View,它相当于声明了一些视图,等待被添加;
  • merge标签被添加到A容器下,那么merge下的所有视图将被添加到A容器下;
  • 因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点;
  • 因为merge不是View,所以对merge标签设置的所有属性都是无效的;
使用 ConstraintLayout

使用 ConstraintLayout 替代 RelativeLayout 或者 weighted LinearLayout。ConstraintLayout的效果与RelativeLayout相似,都是以各组件间的相互约束的条件来实现布局,但在较为复杂的界面中,ConstraintLayout不仅能有效的降低布局层级,也能减少布局嵌套,在性能上优于RelativeLayout与LinearLayout。而且ConstraintLayout可以在Android studio中以拖拽的方式快速搭建我们的布局界面,也能有效的提高开发效率

4.1.2 使用ViewStub提高显示速度

ViewStub是一个轻量级的View,它是一个看不见的,并且不占布局位置,占用资源非常小的视图对象。可以为ViewStub指定一个布局,加载布局时,只有ViewStub会被初始化,然后当ViewStub被设置为可见时,或是调用了ViewStub.inflate()时,ViewStub所指向的布局会被加载和实例化,然后ViewStub的布局属性都会传给它指向的布局。这样,就可以使用ViewStub来设置是否显示某个布局。

使用ViewStub时需要注意以下几点:

  • ViewStub只能加载一次,之后ViewStub对象会被置为空。换句话说,某个被ViewStub指定的布局被加载后,就不能再通过ViewStub来控制它了。所以它不适用于需要按需显示隐藏的情况。
  • ViewStub只能用来加载一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。如果想操作一个具体的View,还是使用visibility属性。
  • VIewStub中不能嵌套Merge标签。

4.1.3 布局复用

可以使用include标签去导入重复的布局

4.1.4 背景优化

尽量不要重复去设置背景,这里需要注意的是主题背景(theme), theme 默认会是一个纯色背景,如果我们自定义了界面的背景,那么主题的背景我们来说是无用的。但是由于主题背景是设置在 DecorView 中,所以这里会带来重复绘制,也会带来绘制性能损耗。

4.1.5 小结

提高布局效率的方法总体来说就是减少层级,提高绘制速度和布局复用。影响布局效率主要有以下几点:

  • 布局的层级越少,加载速度越快。
  • 减少同一层级控件的数量,加载速度会变快。
  • 一个控件的属性越少,解析越快。

根据前面的分析,对优化的总结如下:

  • 合理使用RelativeLayout或LinearLayout
  • 复杂布局可使用 ConstraintLayout替

代RealtiveLayout与LinearLayout

  • 使用标签减少布局的嵌套层次。
  • 使用标签加载一些不需要立即显示的布局。
  • 将可复用的组件抽取出来并通过标签使用。
  • 尽可能少用wrap_content, wrap_content会增加布局measure时的计算成本,已知宽高为固定值时,不用wrap_content。
  • 删除控件中的无用属性。

4.2 避免过渡绘制

造成过渡绘制的原因一般有以下两点:

  • XML布局:控件有重叠且都有设置背景。
  • 自定义View:onDraw方法里面同一个区域被绘制多次。

4.2.1 如何检测过渡绘制

可参考3.2.5 分析过渡绘制

4.2.2 如何避免过渡绘制

  • 移除XML中非必需的背景,或根据条件设置。
  • 有选择性地移除窗口背景:getWindow().setBackgroundDrawable(null)。
  • 按需显示占位背景图片。
  • 自定义View可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。canvas.clipRect()可以很好地帮助那些有多组重叠组件的自定义View来控制显示的区域。clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制,并且可以使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。

4.3 硬件加速绘制

从 Androd 3.0 开始,Android 开始支持硬件加速,到 Android 4.0 时,默认开启硬件加速。

硬件加速绘制与软件绘制整个流程差异非常大,最核心就是我们通过 GPU 完成 Graphic Buffer 的内容绘制。此外硬件绘制还引入了DisplayList 的概念,每个 View 内部都有一个 DisplayList,当某个 View 需要重绘时,将它标记为 Dirty。当需要重绘时,仅仅只需要重绘一个 View 的 DisplayList,而不是像软件绘制那样需要向上递归。这样可以大大减少绘图的操作数量,因而提高了渲染效率。

4.4 优化刷新频率

合理加载数据

如以ListView或者RecyclerView等滑动控件为例,在控件滑动时,可以暂停加载数据,在控件停止后再加载数据。

控制界面刷新区域

如自定义View一般采用invalidate方法刷新,可以使用以下重载方法指定要刷新的区域,而不是直接invalidate()全屏刷新:

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

4.5 提高动画性能

Android平台提供了三个动画框架:帧动画(Frame Animation)、补间动画(Tween Animation)和属性动画(Property Animation)。 通过动画可以实现很多酷炫的动画,但带来的性能开销也有不同程度的影响。在实现动画的过程中,主要从以下三个纬度来对比性能:

  • 流畅度:流畅度是动画的核心,控制每一帧动画在16ms以内完成。
  • 内存:避免内存泄漏,减小内存开销。
  • 耗电:减小运算量,优化算法,减小CPU占用。

4.5.1 帧动画

帧动画需要依赖图片资源,当图片过多或者较大时,内存占用就非常大,效果也比较差。所以一般情况下,因为其消耗资源过多,性能最差,因此最好不使用帧动画去实现动画效果。

4.5.2 补间动画

补间动画是通过对某个View进行一系列的操作来改变显示效果,对比逐帧动画,它的使用更简单方便。

优点:

  • 不需要定义时间频率内的每一帧,只需要定义开始和结束关键帧的内容,两个关键帧之间的效果自动生成,使用时不需要另写代码控制
  • 补间动画支持淡入淡出(AlphaAnimation)、缩放(ScaleAnimation)、平移(TranslationAnimation)和旋转(RotateAnimation)4种动画模式,并支持以上四种模式进行动画组合

缺点:

  • 使用补间动画实现动画会导致View重绘非常频繁;
  • 补间动画只能用于View对象,也就是只有继承于View或者View的控件,才能用补间动画实现动画效果;
  • 补间动画是改变View的显示效果,但是不会真正改变View的属性;

4.5.3 属性动画

属性动画由Android 3.0(API 11)及更高版本支持,通过修改动画的实际属性来实现动画效果。

优点:

  • 可以定义多个特性:动画持续时间、时间插值、重复次数和行为、动画集合以及帧刷新延迟
  • 相比于补间动画,属性动画重绘明显少很多,性能也明显更优秀

4.5.4 在动画上使用硬件加速

通过硬件加速来渲染提高动画的性能,可以实现更为快速、平滑的效果。硬件纹理操作对一个View进行动画绘制,如果不调用invalidate()方法,就可以减少对View自身频繁的重绘。

离屏缓冲或者Layer能够更高效地处理复杂view的动画效果或者UI合成效果。Android 3.0开始对何时以及如何使用层有了更多的控制,也就是新的离屏缓冲方式View.setLayerType(type,paint)方法,这个API所带的两个参数一个是使用的层类型,另外一个是可选参数Paint。

一个View可以使用如下三种Layer类型之一:

  • LAYER_TYPE_NONE:普通渲染方式,不会返回一个离屏的缓冲,默认值。
  • LAYER_TYPE_HARDWARE:如果这个应用使用了硬件加速,这个View将会在硬件中渲染为硬件纹理,如果应用程序并没有被硬件加速,则其效果和LAYER_TYPE_SOFTWARE相同。
  • LAYER_TYPE_SOFTWARE:此View通过软件渲染为一个Bitmap。

虽然硬件加速可以带来更好的绘制效果,但也有一些问题,以下几点需要注意:

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

五 卡顿的监控与实现

5.1 识别卡顿

常用的识别卡顿方法有以下几种:

  • 目视检查方法
  • Systrace 方法
  • 自定义性能检测方法

目视检查方法

目视检查有助于找出导致卡顿的用例,以下是关于进行目视检查的一些提示:

  • 运行应用的发布版本(或至少是不可调试的版本)。为了支持调试功能,ART 运行时会停用几项重要的优化功能,因此请务必确保看到的内容与用户将会看到的内容类似。
  • 启用 GPU 渲染模式分析功能:GPU 呈现模式分析功能会在屏幕上显示一些条形,以相对于每帧 16ms 的基准,快速直观地显示呈现界面窗口帧所花的时间。每个条形都有带颜色的区段,对应于呈现管道中的一个阶段,这样就可以看到哪个部分用时最长。例如,如果帧花费大量时间处理输入,应查看负责处理用户输入的应用代码。
  • 某些组件(如 RecyclerView)是卡顿的常见来源。如果应用使用了这些组件,最好查看一下应用的这些部分。
  • 有时,只有当应用通过冷启动进行启动时,才能重现卡顿。
  • 可以尝试在速度较慢的设备上运行应用,以突显此问题。

Systrace方法

Systrace 工具用于显示整个设备在做些什么,也可用于识别应用中的卡顿。Systrace 的系统开销非常小,因此可以在插桩测试期间体验实际卡顿情况。 在设备上执行卡顿的用例时,可以使用 Systrace 记录跟踪信息。

自定义性能监控

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

监控原理

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

卡顿信息捕获

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

  • 1、基础信息:系统版本、机型、进程名、应用版本号、磁盘空间、UID等。
  • 2、耗时信息:卡顿开始和结束时间。
  • 3、CPU信息:CPU的信息、整体CPU使用率和本进程CPU使用率(可粗略判断是当前应用消耗CPU资源太多导致的卡顿,还是其他原因)等。
  • 4、堆栈信息。

5.2 常见卡顿来源

可滚动列表

ListViewRecyclerView (尤其是后者)常用于最易出现卡顿的复杂滚动列表。它们都包含 Systrace 标记,因此可以使用 Systrace 来判断它们是不是导致应用出现卡顿的因素。

布局性能

如果 Systrace 表明 Choreographer#doFrame布局部分执行的工作过多或者执行工作的频率太高,则意味着遇到了布局性能问题。应用的布局性能取决于视图层次结构的哪个部分包含会发生改变的布局参数或输入。

呈现性能

Android 界面工作分为两个阶段:界面线程上的 Record View#draw 和 RenderThread 上的 DrawFrame。第一阶段对每个失效的 View 运行draw(Canvas),并可调用自定义视图或代码。第二阶段在原生 RenderThread 上运行,但将根据 Record View#draw 阶段生成的工作运行。

线程调度延迟

线程调度程序在 Android 操作系统中负责确定系统中的哪些线程应该运行、何时运行以及运行多长时间。有时,出现卡顿是因为应用的界面线程处于阻塞或未运行状态。Systrace 使用不同的颜色(见下图)来指明线程何时处于休眠状态(灰色)、可运行(蓝色:可以运行,但调度程序尚未选择让它运行)、正在运行(绿色)或处于不可中断休眠状态(红色或橙色)。这对于调试由线程调度延迟引起的卡顿问题非常有用。

对象分配和垃圾收集

自从 ART 在 Android 5.0 中作为默认运行时引入后,对象分配和垃圾回收 (GC) 问题已显著缓解,但这项额外的工作仍有可能加重线程的负担。我们可以针对每秒不会发生多次的罕见事件(例如用户点按一个按钮)进行分配,但请记住,每次分配都会产生开销。如果它处于被频繁调用的紧密循环中,应考虑避免分配以减轻 GC 上的负载。

Systrace 会显示 GC 是否频繁运行,而 Android Memory Profiler 可显示分配来源。如果尽可能避免分配(尤其是在紧密循环中),则应该不会遇到问题。 在较新版本的 Android 中,GC 通常在名为 HeapTaskDaemon 的后台线程上运行。请注意,大量的分配可能意味着在 GC 上耗费更多的 CPU 资源,如下图所示:

六、总结

文章到这,对绘制优化的理解可以总结成以下几点:

  • 了解绘制原理:了解绘制的原理,有利于我们更快的定位问题,帮助我们找到问题优化的切入口,寻找解决方案;
  • 发现问题:除了使用时感官的感受卡顿,线上问题还应通过一些卡顿监控技术去发现问题与记录问题;
  • 分析问题:利用好性能分析工具,包括Hierarchy View、Layout inspector布局检查工具,TraceView与Systrace性能耗时跟踪记录等;
  • 解决问题:结合实际的应用场景,去优化解决现有的问题;

UI绘制带来主要问题是卡顿,但造成卡顿问题的不仅只有UI绘制掉帧一个原因,还有主线程繁忙、被阻塞以及内存使用不合理等问题,严重的卡顿甚至会导致应用出现ANR现象,这种用户体验会更为糟糕。对于一个庞大的应用来说,性能优化是提高用户体验不可或缺的,也是一项任重道远的任务,让我们一起持续学习与成长,文章如有不足之处,请多谅解并指正,谢谢!!!

以上是关于Android 性能优化---绘制优化篇的主要内容,如果未能解决你的问题,请参考以下文章

Android绘制优化绘制性能分析

Android绘制优化绘制性能分析

Android 性能优化

Android绘制优化布局优化

Android UI性能优化实战 识别绘制中的性能问题

Android性能优化总结