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

本篇博客主要记录一些工作中常用的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. 5.3、Android Studio录像

    Android Monitor允许你从设备中录制一段MP4格式的视频,最长允许3分钟. 录制视频 在硬件设备中录制视频: 1. 打开一个项目 2. 在设备中运行应用 3. 显示Android Moni ...

  2. cocos2dx 3.3 C++工程添加lua支持

    准备工作: 1. 拷贝cocos2d-x-3.3rc0\external\lua整个文件夹到项目中(如myProject\cocos2d\external\lua) 2. 拷贝cocos2d-x-3. ...

  3. Andriod的国际化-android学习之旅(五十八)

    android资源国际化

  4. Unity UGUI基础之Image

    UGUI的Image等价于NGUI的Sprite组件,用于显示图片. 一.Image组件: Source Image(图像源):纹理格式为Sprite(2D and UI)的图片资源(导入图片后选择T ...

  5. Guava 教程2-深入探索 Google Guava 库

    原文出处: oschina 在这个系列的第一部分里,我简单的介绍了非常优秀的Google collections和Guava类库,并简要的解释了作为Java程序员,如果使用Guava库来减少项目中大量 ...

  6. 【一天一道LeetCode】#64. Minimum Path Sum.md

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...

  7. [Java]数组排序-选择排序 冒泡排序 插入排序

    1 选择排序  原理:a 将数组中的每个元素,与第一个元素比较          如果这个元素小于第一个元素, 就将这个         两个元素交换.       b 每轮使用a的规则, 可以选择出 ...

  8. Android事件总线分发库EventBus3.0的简单讲解与实践

    Android事件总线分发库EventBus的简单讲解与实践 导语,EventBus大家应该不陌生,EventBus是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent,Han ...

  9. Linux学习笔记 --iptables防火墙配置

    iptables防火墙配置 一.防火墙简介 1.功能: 1)通过源端口,源IP地址,源MAC地址,包中特定标记和目标端口,IP,MAC来确定数据包是否可以通过防火墙 2)分割内网和外网[附带的路由器的 ...

  10. 使用MD5加密的登陆demo

    最近接手了之前的一个项目,在看里面登陆模块的时候,遇到了一堆问题.现在记录下来. 这个登陆模块的逻辑是这样的 1 首先在登陆之前,调用后台的UserLoginAction类的getRandomKey方 ...