版权声明:本文出自汪磊的博客,未经作者允许禁止转载。

本篇博客主要记录一些工作中常用的UI渲染性能优化及调试方法,理解这些方法对于我们编写高质量代码也是有一些帮助的,主要内容包括介绍CPU,GPU的职责,UI的overdraw,Hierarchy View工具的使用以及canvas.clipRect()方法防止View的重叠绘制,都是一些老生常谈的玩意,只是为了自己记录一下才写出来,如果您已经掌握,直接跳过就可以了。

一、CPU,GPU的职责介绍

对于大多数手机的屏幕刷新频率是60hz,也就是如果在1000/60=16.67ms内没有把这一帧的任务执行完毕,就会发生丢帧的现象,丢帧是造成界面卡顿的直接原因,渲染操作通常依赖于两个核心组件:CPU与GPU。CPU负责包括Measure,Layout等计算操作,GPU负责Rasterization(栅格化)操作(所谓栅格化就是将矢量图形转换为位图的过程,手机上显示是按照一个个像素来显示的,栅格化再普通一些的说法就是将一个Button,TextView等组件拆分到一个个像素上去显示)。

UI渲染优化的目的就是减轻CPU,GPU的压力,除去不必要的操作,保证每帧16ms以内处理完所有的CPU与GPU的计算,绘制,渲染等等操作,使UI顺滑,流畅的展示出来。

二、查找Overdraw

Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在重叠的UI布局中,如果不可见的UI也在做绘制的操作或者后一个控件将前一个控件遮挡,会导致某些像素区域被绘制了多次,从而增加了CPU,GPU的压力。

那么我们找出布局中Overdraw的地方呢?很简单,一般手机里面开发者选项都有调试GPU过度绘制的开关,打开即可。

以小米4手机为例,依次找到设置->更多设置->开发者选项->调试GPU过度绘制开关,打开就可以了。

打开调试GPU过度绘制开关之后,再次回到自己开发的应用发现界面怎么多了一些花花绿绿的玩意,没错,不同的颜色代表过度绘制的程度,具体如下表:

蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,1x,2x,3x,4x分别表示同一像素上同一帧的时间内被绘制了多次,1x就表示一次最理想情况,4x表示4次最差的情况,我们要做的就是尽量减少3x,4x的情况出现。

下面我们以一个简单demo来进一步说明一下,比如我们开发好一个界面,如下:


很简单的功能,功能做完了,我们看看能不能做下优化呢?打开OverDraw功能,再次查看界面,如下;

咦?怎么大部分都是浅绿色呢?也就是说同一像素上同一帧的时间内被绘制了2次,这是怎么回事?这时我们需要看下UI布局了,看哪些地方可以优化一下。

主界面布局如下:

 <?xml version="1.0" encoding="utf-8"?>
<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"> <ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#F1F1F1"
android:dividerHeight="1dp"
android:background="@android:color/white"
android:scrollbars="vertical">
</ListView> </RelativeLayout>

ListView每个条目布局如下:

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="52dp"
android:background="@drawable/ts_account_list_selector"> <TextView
android:id="@+id/ts_item_has_login_account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="12345678999"
android:textColor="@android:color/black"
android:textSize="16sp" /> <LinearLayout
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="3dp"
android:layout_marginLeft="10dp"
android:gravity="center_vertical" > <ImageView
android:id="@+id/ts_item_time_clock_image"
android:layout_width="12dp"
android:layout_height="12dp"
android:src="@mipmap/ts_login_clock" /> <TextView
android:id="@+id/ts_item_last_login_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_toRightOf="@id/ts_item_time_clock_image"
android:text="上次登录"
android:textColor="@android:color/darker_gray"
android:textSize="11sp" /> <TextView
android:id="@+id/ts_item_login_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_toRightOf="@id/ts_item_last_login_time"
android:text="59分钟前"
android:textColor="@android:color/darker_gray"
android:textSize="11sp" />
</LinearLayout> <TextView
android:id="@+id/ts_item_always_account_image_tips"
android:layout_width="wrap_content"
android:layout_height="13dp"
android:layout_alignParentRight="true"
android:layout_marginTop="2dp"
android:background="@mipmap/ts_always_account_bg"
android:gravity="center"
android:text="常用"
android:textColor="@android:color/white"
android:textSize="9sp" /> <ImageView
android:id="@+id/ts_item_delete_account_image"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_alignParentRight="true"
android:layout_marginTop="2dp"
android:layout_marginRight="13dp"
android:layout_centerVertical="true"
android:src="@mipmap/ts_close" /> </RelativeLayout>

发现哪里的问题了吗?这里我就直接说了,问题在于ListView多余设置了背景:android:background="@android:color/white",设置此背景对于我们这个需求根本就没有用,显示不出来并且增加GPU额外压力,去掉ListView背景之后再次观察如下:

渲染性能提升了一个档次,在实际工作中情况会复杂很多,为了实现一个效果会不得不牺牲性能,这就需要自己团队权衡了,好了OverDraw到此为止。

三、clipRect来解决自定义View的OverDraw

我们平时写自定义View的时候有时会重写onDraw方法,但是Android系统是无法检测onDraw里面具体会执行什么操作,从而系统无法为我们做一些优化。这样对编程人员要求就高了,如果我们自己写的View有大量重叠的地方就造成了CPU,GPU资源的浪费,但是我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视,下面我们通过谷歌提供的一个小demo进一步说明。实现效果如下:

主要就是卡片重叠效果,优化前代码实现如下:

DroidCard类封装要绘制的一个个卡片的信息:

 public class DroidCard {

     public int x;//左侧绘制起点
public int width;
public int height;
public Bitmap bitmap; public DroidCard(Resources res,int resId,int x){
this.bitmap = BitmapFactory.decodeResource(res,resId);
this.x = x;
this.width = this.bitmap.getWidth();
this.height = this.bitmap.getHeight();
}
}
DroidCardsView为真正的自定义View:
 public class DroidCardsView extends View {
//图片与图片之间的间距
private int mCardSpacing = 150;
//图片与左侧距离的记录
private int mCardLeft = 10; private List<DroidCard> mDroidCards = new ArrayList<DroidCard>(); private Paint paint = new Paint(); public DroidCardsView(Context context) {
super(context);
initCards();
} public DroidCardsView(Context context, AttributeSet attrs) {
super(context, attrs);
initCards();
}
/**
* 初始化卡片集合
*/
protected void initCards(){
Resources res = getResources();
mDroidCards.add(new DroidCard(res,R.drawable.alex,mCardLeft)); mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res,R.drawable.claire,mCardLeft)); mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res,R.drawable.kathryn,mCardLeft));
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (DroidCard c : mDroidCards){
drawDroidCard(canvas, c);
}
invalidate();
} /**
* 绘制DroidCard
*/
private void drawDroidCard(Canvas canvas, DroidCard c) {
canvas.drawBitmap(c.bitmap,c.x,0f,paint);
}
}

代码不是本篇重点,不过也不难,自行查看就可以了。我们打开overdraw开关,效果如下:

淡红色区域明显被绘制了三次(三张图片重合的地方),其实下面的图片完全没必要完全绘制,只需要绘制三分之一即可,接下来我们就需要对其优化,保证最下面两张图片只需要回执其三分之一最上面图片完全绘制出来就可。

DroidCardsView代码优化为:

 public class DroidCardsView extends View {

     //图片与图片之间的间距
private int mCardSpacing = 150;
//图片与左侧距离的记录
private int mCardLeft = 10; private List<DroidCard> mDroidCards = new ArrayList<DroidCard>(); private Paint paint = new Paint(); public DroidCardsView(Context context) {
super(context);
initCards();
} public DroidCardsView(Context context, AttributeSet attrs) {
super(context, attrs);
initCards();
}
/**
* 初始化卡片集合
*/
protected void initCards(){
Resources res = getResources();
mDroidCards.add(new DroidCard(res, R.drawable.alex,mCardLeft)); mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res, R.drawable.claire,mCardLeft)); mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res, R.drawable.kathryn,mCardLeft));
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mDroidCards.size() - 1; i++){
drawDroidCard(canvas, mDroidCards,i);
}
drawLastDroidCard(canvas,mDroidCards.get(mDroidCards.size()-1));
invalidate();
} /**
* 绘制最后一个DroidCard
* @param canvas
* @param c
*/
private void drawLastDroidCard(Canvas canvas,DroidCard c) {
canvas.drawBitmap(c.bitmap,c.x,0f,paint);
} /**
* 绘制DroidCard
* @param canvas
* @param mDroidCards
* @param i
*/
private void drawDroidCard(Canvas canvas,List<DroidCard> mDroidCards,int i) {
DroidCard c = mDroidCards.get(i);
canvas.save();
canvas.clipRect((float)c.x,0f,(float)(mDroidCards.get(i+1).x),(float)c.height);
canvas.drawBitmap(c.bitmap,c.x,0f,paint);
canvas.restore();
}
}

主要就是使用Canvas的clipRect方法,绘制之前裁剪出一个区域,这样绘制的时候只在这区域内绘制,超出部分不会绘制出来。

重新执行程序,效果如下:

处理后性能就提升了一丝丝,此外我们还可以使用canvas.quickReject方法来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。

四、Hierarchy Viewer的使用

Hierarchy Viewer可以很直观的呈现布局的层次关系。我们可以通过红,黄,绿三种不同的颜色来区分布局的Measure,Layout,Executive的相对性能表现如何,

提升布局性能的关键点是尽量保持布局层级的扁平化,避免出现重复的嵌套布局。如果我们写的布局层级比较深会严重增加CPU的负担,造成性能的严重卡顿,关于Hierarchy Viewer的使用举例这里就不列举了,我觉得大部分安卓开发人员都会使用了,不知道为什么我这电脑Hierarchy Viewer工具突然出问题了,紧急抢救中。。。

五、内存抖动现象

在我们优化过view的树形结构和overdraw之后,可能还是感觉自己的app有卡顿和丢帧,或者滑动慢:卡顿还是存在。这时我们就要查看一下是否存在内存抖动情况了

Android有自动管理内存的机制,但是对内存的不恰当使用仍然容易引起严重的性能问题。在同一帧里面创建过多的对象是件需要特别引起注意的事情,在同一帧

里创建大量对象可能引起GC的不停操作,执行GC操作的时候,所有线程的任何操作都会需要暂停,直到GC操作完成。大量不停的GC操作则会显著占用帧间隔时间。

如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了,严重时可能引起卡顿:

导致GC频繁操作有两个主要原因:

一是内存抖动,所谓内存抖动就是短时间产生大量对象又在短时间内马上释放。

二是短时间产生大量对象超出阈值,内存不够,同样会触发GC操作。

观察内存抖动我们可以借助android studio中的工具,3.0以前可以使用android monitor,3.0以后被替换为android Profiler。

如果工具里面查看到短时间发生了多次内存的涨跌,这意味着很有可能发生了内存抖动,如图:

为了避免发生内存抖动,我们需要避免在for循环里面分配对象占用内存,需要尝试把对象的创建移到循环体之外,自定义View中的onDraw方法也需要引起注意,每次屏幕发生绘制以及动画执行过程中,onDraw方法都会被调用到,避免在onDraw方法里面执行复杂的操作,避免创建对象。对于那些无法避免需要创建对象的情况,我们可以考虑对象池模型,通过对象池来解决频繁创建与销毁的问题,但是这里需要注意结束使用之后,需要手动释放对象池中的对象。

好了,关于UI渲染性能优化介绍到此为止,本篇大量参考谷歌官方发布的性能优化资料,都是一些老玩意,主要用于个人记录。

Android性能优化之UI渲染性能优化的更多相关文章

  1. android app性能优化大汇总(UI渲染性能优化)

    UI性能测试 性能优化都需要有一个目标,UI的性能优化也是一样.你可能会觉得“我的app加载很快”很重要,但我们还需要了解终端用户的期望,是否可以去量化这些期望呢?我们可以从人机交互心理学的角度来考虑 ...

  2. 图形性能(widgets的渲染性能太低,所以推出了QML,走硬件加速)和网络性能(对UPD性能有实测数据支持)

    作者:JasonWong链接:http://www.zhihu.com/question/37444226/answer/72007923来源:知乎著作权归作者所有,转载请联系作者获得授权. ---- ...

  3. Android 性能优化——之控件的优化

    Android 性能优化——之控件的优化 前面讲了图像的优化,接下来分享一下控件的性能优化,这里主要是面向自定义View的优化. 1.首先先说一下我们在自定义View中可能会犯的3个错误: 1)Use ...

  4. react+redux渲染性能优化原理

    大家都知道,react的一个痛点就是非父子关系的组件之间的通信,其官方文档对此也并不避讳: For communication between two components that don't ha ...

  5. 通过分析 WPF 的渲染脏区优化渲染性能

    原文:通过分析 WPF 的渲染脏区优化渲染性能 本文介绍通过发现渲染脏区来提高渲染性能. 本文内容 脏区 Dirty Region WPF 性能套件 脏区监视 优化脏区重绘 脏区 Dirty Regi ...

  6. 使用 content-visibility 优化渲染性能

    最近在业务中实际使用 content-visibility 进了一些渲染性能的优化. 这是一个比较新且有强大功能的属性.本文将带领大家深入理解一番. 何为 content-visibility? co ...

  7. 直播推流端弱网优化策略 | 直播 SDK 性能优化实践

    弱网优化的场景 网络直播行业经过一年多的快速发展,衍生出了各种各样的玩法.最早的网络直播是主播坐在 PC 前,安装好专业的直播设备(如摄像头和麦克风),然后才能开始直播.后来随着手机性能的提升和直播技 ...

  8. 优化Angular应用的性能

    MVVM框架的性能,其实就取决于几个因素: 监控的个数 数据变更检测与绑定的方式 索引的性能 数据的大小 数据的结构 我们要优化Angular项目的性能,也需要从这几个方面入手. 1. 减少监控值的个 ...

  9. 如果要做优化,CSS提高性能的方法有哪些?

    一.前言 每一个网页都离不开css,但是很多人又认为,css主要是用来完成页面布局的,像一些细节或者优化,就不需要怎么考虑,实际上这种想法是不正确的 作为页面渲染和内容展现的重要环节,css影响着用户 ...

随机推荐

  1. UNIX网络编程——进程间通信概述

    一.顺序程序与并发程序特征 顺序程序特征 顺序性封闭性:(运行环境的封闭性)确定性可再现性 并发程序特征 共享性并发性随机性 二.进程互斥 1.由于各进程要求共享资源,而且有些资源需要互斥使用,因此各 ...

  2. 从二进制数据流中构造GDAL可以读取的图像数据(C#)

    在上一篇博客中,讲了一下使用GDAL从文件流中构造一个GDAL可以识别的数据来进行处理.原以为这个接口在C#中没有,仔细看了下GDAL库中源码,发现C#版本也有类似的函数,下面是GDAL库中的一个C# ...

  3. I/O操作之文件压缩与解压

    与文件压缩与解压相关的类在java.util.zip包下 实例 //文件压缩 import java.io.File; import java.io.FileInputStream; import j ...

  4. UNIX网络编程——使用select函数编写客户端和服务器

    首先看原先<UNIX网络编程--并发服务器(TCP)>的代码,服务器代码serv.c: #include<stdio.h> #include<sys/types.h> ...

  5. React Native项目组织结构介绍

    代码组织: 目录结构: . ├── components //组成应用的各个组件 │   ├── Routers.android.js //每个组件若实现不一样,分为android的实现和ios的实现 ...

  6. ffmpeg.c函数结构简单分析(画图)

    前一阵子研究转码的时候看了FFmpeg的源代码.由于ffmpeg.c的代码相对比较长,而且其中有相当一部分是AVFilter有关的代码(这一部分一直不太熟),因此之前学习FFmpeg的时候一直也没有好 ...

  7. 通用数据水平层级选择控件v0.70升级版使其支持jQuery v1.9.1

    升级原因:作者原来脚本支持的jquery版本太低了,查找了下资料,使得它能支持最新版本的jquery 备注说明:脚本代码源作者跟源文出处很难找,只能在此特感谢他的分享. 更新部分: 1.新版本不再支持 ...

  8. 内存分配的原理__进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)

    如何查看进程发生缺页中断的次数? 用ps -o majflt,minflt -C program命令查看. majflt代表major fault,中文名叫大错误,minflt代表minor faul ...

  9. VS2010中NET4项目中使用LOG4NET办法

    两年以前项目中是用的log4net.dll是net2.0环境下面的,最近在官网http://logging.apache.org/log4net/download_log4net.cgi找到了net4 ...

  10. How To Transact Move Order Using INV_PICK_WAVE_PICK_CONFIRM_PUB.Pick_Confirm API

    In this Document Goal   Solution   Sample Code:   Steps:   FAQ   References APPLIES TO: Oracle Inven ...