Android 性能优化之绘制优化

绘制性能的好坏 主要影响 :Android应用中的页面显示速度

优化思路

绘制影响 Android 性能的实质就是影响页面的绘制时间。

一个页面通过递归完成测量和绘制过程,即 measurelayout 过程。

因此可以从以下几个方面进行优化,从而提高 Android 应用中的页面显示速度:

  • 降低 View.onDraw() 的复杂度
  • 避免过度绘制(Overdraw)

优化方案

降低 View.onDraw() 的复杂度

不要创建新的局部对象

onDraw() 方法可能会被频繁调用。如果 onDraw() 方法内需创建局部对象,则会在瞬间产生大量的历史对象,这使得占用过多内存并导致系统频繁 GC,降低了程序的执行效率。因此,onDraw() 方法中不要创建新的局部对象。

避免执行大量耗时操作

Google 官方性能优化标准要求,View 的最佳绘制频率为 60 fps(即:要求每帧绘制时间≤16ms)。如果 onDraw() 方法内执行大量耗时操作,会抢占 CPU 的时间片,从而导致 View 的绘制过程不流畅。因此,onDraw() 方法中避免执行大量耗时操作。

避免过度绘制(Overdraw)

过度绘制的简介

过度绘制是指屏幕上的某一像素点在同一帧中被重复绘制多次。

过度绘制发生的主要原因是因为多层次或重叠的 UI 结构。

在多层次或重叠的 UI 结构里,若不可见的 UI 也在做绘制操作,则会导致某些像素区域被绘制多次。过度绘制则会导致屏幕显示的色块不同。

过度绘制的主要影响如下:

  • 界面显示时,浪费资源去渲染不必要、看不见的背景;
  • 多次绘制就会导致页面加载或滑动时的不流畅、掉帧(对用户体验来说就是 APP 卡顿)。

过度绘制的检测

  • 方法一:通过开发者选项开启 GPU 过度绘制调试

    Android 手机的开发者选项中有调试 GPU 过度绘制的选项,打开步骤为设置 -> 开发者选项 -> 调试GPU过度绘制 -> 显示GPU过度绘制

  • 方法二:通过 adb 命令开启 GPU 过度绘制调试
    开启调试 GPU 过度绘制的命令如下:

    $ adb shell setprop debug.hwui.overdraw show
    

    关闭调试 GPU 过度绘制的命令如下:

    $ adb shell setprop debug.hwui.overdraw false
    

    执行命令之后可能需要重新启动要调试的应用。

过度绘制的表现形式

过度绘制会导致屏幕显示的色块不同,具体如下图所示:
过度绘制的表现形式

依据过度绘制的层度可以分成:

  • 无过度绘制:一个像素只被绘制了一次
  • 过度绘制x1:一个像素被绘制了两次
  • 过度绘制x2:一个像素被绘制了三次
  • 过度绘制x3:一个像素被绘制了四次
  • 过度绘制x4+:一个像素被绘制了五次及以上

过度绘制的优化原则

很多过度绘制是难以避免的(如背景+文字导致的过度绘制),只能尽可能避免过度绘制:

  • 尽可能地控制过度绘制的次数为 2 次(绿色)以下,蓝色最理想;
  • 尽可能避免过度绘制的粉色和红色情况;
  • 不允许 3 次以上的过度绘制(淡红色)面积超过屏幕大小的 1/4。

过度绘制的优化方案

  • 优化方案1: 移除 Window 的默认背景
    查看 Android 源码里的 Theme 主题,如下:

    <style name="Theme">
     ...
     <!-- Window attributes -->
     <item name="windowBackground">@drawable/screen_background_selector_dark</item>
     ...
    </style>
    

    也就是说继承 Theme 这个 style 的风格,默认情况下,新建一个 Activity 都是有背景的。一般情况下,该默认的 Window 背景基本用不上:因背景都自定义设置;若不移除,则导致所有界面都多 1 次绘制。
    使用时只要在自己的 AppTheme 里面去除该背景色即可:

    <style name="AppTheme" parent="android:Theme.Light.NoTitleBar">
     <item name="android:windowBackground">@null</item>
    </style>
    

    或者在 Activity 的 onCreate() 方法中将背景置为 null 即可:

    getWindow().setBackgroundDrawable(null);
    
  • 优化方案2:移除控件中不必要的背景

    • 场景1:ListView 与 Item:列表页(ListView)与其内子控件(Item)的背景相同,则可移除子控件(Item)布局中的背景;
    • 场景2:ViewPager 与 Fragment:对于一个 ViewPager 和多个 Fragment 组成的首页界面,若每个 Fragment 都设有背景色,则可移除 ViewPager 的背景。
  • 优化方案3:减少布局文件的层级

    原理:减少不必要的嵌套(UI层级少),则过度绘制的可能性低。
    优化方式:使用标签并合适选择布局类型。

  • 优化方案4:使用 Canvas 的 clipRect 和 clipPath 方法限制 View 的绘制区域

    作用:使用 Canvas 的 clipRect 和 clipPath 方法给 Canvas 设置一个裁剪区域,只有在该区域内才会被绘制,区域之外的都不绘制(如:android.support.v4.widget.DrawerLayout)。

  • 优化方案5:ImageView 的 background 和 imageDrawable 重叠

    Android 中,所有的 View 均可以设置 background。ImageView 除了能够设置 background 之外,还能设置 ImageDrawable。开发中,很多时候需要显示图片,在图片加载出来之前通常是需要显示一张默认图片的,很多时候会使用 ImageView 的 background 属性来设置默认背景图,而 imageDrawable 来设置需要加载的图片。这样会导致一个问题,当图片加载到页面后,默认背景图被挡住了,但是却仍然然需要绘制,导致过度绘制情况的发生。解决方案是把背景图和真正加载的图片都通过 imageDrawable 方法进行设置。


评论
 上一篇
Java ThreadLocal 详解 Java ThreadLocal 详解
简介java.lang.ThreadLocal 表示线程本地存储区(Thread Local Storage,简称为 TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的 TLS 区域。ThreadLocal 是
2019-01-31
下一篇 
Android 性能优化之布局优化 Android 性能优化之布局优化
在 Android 中系统对 View 进行测量、布局和绘制时,都是通过对 View 树的遍历来进行操作的。如果一个 View 树的高度太高就会严重影响测量、布局和绘制的速度。Google 也在其 API 文档中建议 View 高度不宜超过
2019-01-23
  目录