【Android进阶笔记】渲染优化

959 阅读6分钟

1. CPU与GPU协同工作原理

1.1. CPU和GPU架构

  • CPU:解析布局,生成View对象及位置属性,转化为多维矢量图形。

  • GPU:将矢量图形栅格化,并在屏幕上绘制图形。

  • CPU与GPU的桥梁:openGL ES。

1.1.1. 画面显示过程

Android系统会每隔16ms发出一次Vsync信号,类似于一个时间中断,系统在每次拿到Vsync信号时刷新屏幕,就不会觉得卡顿。

1.1.2. 没有Vsync

屏幕第2帧,CPU可能忙于别的任务(主线程任务太重),导致在第二个16ms结束时,也没有计算完数据,因此GPU也只能在第三个16ms才能计算完第2帧画面,此时就有两个16ms显示的相同画面,则画面出现了卡顿。

1.1.3. 有Vsync

每一次Vsync信息发出,CPU都在第一时间响应,来进行刷新,此时CPU和GPU的刷新时间,和显示屏的FPS是一致的,所以正常情况下是没有卡顿的。

1.1.4. 掉帧查看

Logcat 输入“Skipped”筛选系统日志,不过滤信息“No Filters”,不过滤层级“Verbose”


2. CPU优化

为了减少CPU在每一帧中布局解析的计算量,常用的方法有嵌套层级优化、优化、优化、优化。

2.1. 视图层次结构优化

针对复杂的布局,应该尽量让View的视图层次结构扁平化,层次结构越扁平,完成布局阶段所需的时间越少,Google 官方建议不要超过 10 层

AndroidStudio ➡ Tools ➡ Layout Inspector,选择对应进程和对应Activity,查看布局树。

常用基础布局对比

布局性能嵌套层级布局难度
FrameLayout
LinearLayout
RelativeLayout
ConstraintLayout

通常使用ConstraintLayout写布局的话,能有效减少布局层级,优化性能。

2.2. <include/>优化

通常我们可以将通用的布局提出到一个单独的Layout文件中,然后在需要的地方使用标签将其加载进来,例如导航栏等。

<include
    android:layout_width="match_parent"
    android:layout_height="40dp"
    layout="@layout/titlebar" />

2.3. <merge/>优化

使用标签并不能直接减少嵌套层级。如果include的父布局和被include的根布局相同,且不需要设置backgroundpadding等属性,则布局根标签可以使用标签代替。

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_alignParentBottom="true"
        android:text="@string/app_name" />

</merge>

然后在需要的地方使用标签将其加载进来。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    // include的父布局,会作为merge的子布局的父布局
    <include layout="@layout/merge_view" />
</RelativeLayout>

此时就能减少一层布局嵌套。

2.4. <ViewStub/>优化

同一样可以用来引入一个外部布局。如果引入的布局默认不显示也不占位置,则可以使用代替,从而在解析layout的时候可以节省CPU和内存资源。例如进度条、网络错误等提示性布局。

<ViewStub
    android:id="@+id/network_error_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout="@layout/network_error" />

在代码中,可以使用 (ViewStub)findViewById(id) 找到 ViewStub,通过 stub.inflate() 展开ViewStub,然后得到被引用的实例化对象。

ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);
if(stub != null){
    // 一旦inflate一次之后,id就失效了,则findViewById也找不到ViewStub了
    // 这里保存之后,以后就不用也不能再inflate了
    networkErrorView = stub.inflate();
}

3. GPU过度绘制

GPU过度绘制是在屏幕刷新的一帧中,同一个像素被绘制了多次。

这些组件从上到下分布,低层级的可能被高层级的遮挡,如果此时还花时间去绘制那些不可见的部分,就会出现过度绘制。

设置 ➡ 开发者选项 ➡ 调试GPU过度绘制 ➡ 显示过度绘制区域,就可以打开调试工具。

最理想的是白色和蓝色,即一个像素只绘制一次或两次,粉色以上区域面积不超过屏幕的三分之一,不能出现红色区域。

3.1. background优化

移除Activity的背景色

  • 主题设置为Theme.Translucent及其子主题;
  • 自定义主题windowIsTranslucenttruewindowBackground为透明色;

移除Layout的背景色

  • android:background="@null"或直接删除android:background属性;
  • android:background="@color/transparent",虽然透明了GPU不渲染,但实际会占用内存,只不过颜色是透明的,不建议;

裁剪Canvas

类似扑克牌游戏,位于底层的卡片并非全部露出,如果裁剪Canvas,则可以使被遮住的部分不渲染。

canvas.save();  
canvas.clipRect(left, top, right, bottom);  
// canvas.其他操作();
canvas.restore();

降低透明度

透明对象需要先绘制现有的像素,以便达到正确的混合效果。可以通过减少要渲染的透明对象的数量,来改善这些情况下的过度绘制。例如,要获得灰色文本,您可以在TextView中绘制黑色文本,再为其设置半透明的透明度值。但是,您可以简单地通过用灰色绘制文本来获得同样的效果,而且能够大幅提升性能。


4. GPU呈现模式分析

GPU 渲染模式分析工具以图表(以颜色编码的直方图)的形式显示各个阶段及其相对时间。

设置 ➡ 开发者选项 ➡ GPU/HWUI呈现模式分析 ➡ 在屏幕上显示为条形图,就可以打开调试工具。

竖条区段渲染阶段说明
Swap交换缓冲区表示 CPU 等待 GPU 完成其工作的时间。如果此竖条升高,则表示应用在 GPU 上执行太多工作。
Issue发出命令表示 Android 的 2D 渲染程序向 OpenGL 发出绘制和重新绘制显示列表的命令所花的时间。此竖条的高度与执行每个显示列表所花的时间的总和成正比。显示列表越多,红色竖条就越高。
Upload同步和上传表示将位图信息上传到 GPU 所花的时间。大区段表示应用花费大量的时间加载图形,加载大量小型资源或少量大型资源。
Draw绘制表示用于创建和更新视图显示列表的时间。如果竖条的此部分很高,表明可能有许多自定义视图绘制,或 onDraw 方法执行的工作很多。
Measure测量/布局表示在视图层次结构中 [onLayout](developer.android.com/reference/a…, int, int, int, int)) 和 [onMeasure](developer.android.com/reference/a…, int)) 回调上所花的时间。大区段表示处理视图层次结构需要很长时间,通常是由于需要布局的视图数量过多,或者出现了其他问题。
Anim动画表示评估运行该帧的所有动画程序所花的时间。如果此区段很大,则表示您的应用可能在使用性能欠佳的自定义动画程序,或因更新属性而导致一些意料之外的工作。
Input输入处理表示应用执行输入事件回调中的代码所花的时间。如果此区段很大,则表示应用花太多时间处理用户输入。不妨考虑将此类处理任务分流到其他线程。
Misc其他时间/VSync 延迟表示应用执行两个连续帧之间的操作所花的时间。它可能表示界面线程中进行的处理太多,而这些处理任务本可以分流到其他线程。