UI渲染优化

UI渲染的优化,我们应该知道UI怎么渲染的,在这个过程中会出现什么问题,我们才会去优化,针对这个问题我们怎么去优化。我们将从这几个部分进行探讨,并总结一些小的优化技巧。

1.UI的渲染过程

UI被渲染到屏幕上我们需要两个关键的组件:CPU和GPU,它们共同的工作,在屏幕上绘制图片,每一个组件都有自己固定的流程。

  • CPU:准备需要显示的数据。这个过程有测量,布局,记录,和执行.


    CPU处理流程
  • GPU:把CPU计算好的DisplayList控件进行栅格化处理转换成纹理和3D图片显示到屏幕上

    GPU使用一些指定的基础指令集,主要是多边形和纹理,也就是图片,CPU在屏幕上绘制图像前会向GPU输入这些指令,这一过程通常使用的API就是Android的OpenGL ES,这就是说,在屏幕上绘制UI对象时无论是按钮、路径或者复选框,都需要在CPU中首先转换为多边形或者纹理,然后再传递给GPU进行格栅化。

GPU处理流程
  • 完美的处理方法:Android系统每隔16ms发出VSYNC信号
显示

2.绘制过程中的问题

卡顿

我们应用程序的流畅性是很总要的。但是我们经常会出现应用的卡顿,那卡顿什么这么造成的。大部分是卡到了主进程。这就就要从两方面讨论。

  • 1.外部(计算优化)
1.在View显示的过程中是是否有大量的内存分配和释放也就是内存抖动。
2.是否有一个很耗时的操作。
  • 2.内部(渲染优化) view本身的卡顿。
1.CPU方面:最常见的性能问题是不必要的布局和失效布局,这些内容缺必须在视图层次结构中进行测量、清除并重新创建。这就会引起重建显示列表次数多,花费太多的时间做不必要的绘制。(布局优化)
2.GPU方面,最常见的问题是过度绘制,通常是在像素着色过程中,通过其他工具进行后期着色时浪费了GPU处理时间。(过度)

3.优化

3.1.计算优化

对于内存的抖动我们可以使用两个工具进行问题的定位

3.1.1使用android profiler来定位大致的情况


录制内存分配

图中我们可以看到在一段时间内内存出现了大量的分配和释放

查找内存分配

通过分析工具我们知道在这段时间内内存分配最多的函数。我们可以去看代码分析错误。

3.1.2可以使用TraceView来确定详细的问题所在


内存分配

参数

名称 意义
Name 方法的详细信息,包括包名和参数信息
Incl Cpu Time Cpu执行该方法该方法及其子方法所花费的时间
Incl Cpu Time % Cpu执行该方法该方法及其子方法所花费占Cpu总执行时间的百分比
Excl Cpu Time Cpu执行该方法所话费的时间
Excl Cpu Time % Cpu执行该方法所话费的时间占Cpu总时间的百分比
Incl Real Time 该方法及其子方法执行所话费的实际时间,从执行该方法到结束一共花了多少时间
Incl Real Time % 上述时间占总的运行时间的百分比
Excl Real Time % 该方法自身的实际允许时间
Excl Real Time 上述时间占总的允许时间的百分比
Calls+Recur 调用次数+递归次数,只在方法中显示,在子展开后的父类和子类方法这一栏被下面的数据代替
Calls/Total 调用次数和总次数的占比
Cpu Time/Call Cpu执行时间和调用次数的百分比,代表该函数消耗cpu的平均时间
Real Time/Call 实际时间于调用次数的百分比,该表该函数平均执行时间

==主要使用方法是:在find中查找自己应用包名中,使用CPU时间长的,和是否有递归调用的。==

可以通过优化算法和更改数据结构的方法进行代码的优化。
比如:

StringBuffer 与 String
String是对象不是常量,在字符进行拼接时候,会新产生一个String

在需要大量有规律计算数据的情况下,特别是有乘除计算的时候,我们可以通过打表的方法进行优化。也就是在PC上计算好数据放在表中,使用的时候只要进行读取。

3.2.布局优化

3.2.1Hierarchy Viewer检测

布局优化

三个圆点分别代表:测量、布局、绘制三个阶段的性能表现。

  • 1)绿色:渲染的管道阶段,这个视图的渲染速度快于至少一半的其他的视图。
  • 2)黄色:渲染速度比较慢的50%。
  • 3)红色:渲染速度非常慢。

==优化思想:查看自己的布局,层次是否很深以及渲染比较耗时,然后想办法能否减少层级以及优化每一个View的渲染时间。==

3.2.2解决方法

  1. 减少嵌套
(1)在不响应层级深度的情况下,如果使用LinearLayout和RelativeLayout都可以实现相同的效果,那么建议使用Linearlayout。如果使用LinearLayout不能完成某些效果,那么使用RelativeLayout,而不是两层或更多层的LinearLayout,这样就可以尽量少的View层级,提高布局性能。
(2)尽量不使用LinearLayout的权重属性,因为它会让LinearLayout多进行一次measure。
(3)RelativeLayout的子View高度尽量和RelativeLayout相同,因为如果不同,会导致RelativeLayout在onMeasure()方法中做横向测量时,纵向的测量结果尚未完成,只好暂时使用自己的高度传入子View系统。而父View给子View传入的值没有变化的时候,是不会做无谓的测量的,所以RelativeLayout的子View如果高度和RelativeLayout相同可以进行一些布局上的优化,如果实在不行可以使用padding代替margin。

参考:LinearLayout和RelativeLayout性能区别

2.使用<include/>

<include>标签可以把一个布局中加载到另外一个布局,使代码结构清晰,又可统一修改使用。
注意:include标签仅支持layout_开头的属性(和id),且android:layout_width和android:layout_height属性必须存在,才能使用其它属性(如:android:layout_grivity、android:layout_align...)

3.<merge/>

<merge/>标签通常和<include/>标签一起使用,以减少View树的层级,从而达到优化Android布局的目的。
(1)比如说我们的RelativeLayout中只有一个TextView,这个TextView不需要指定任何针对父视图的布局属性,只添加到父视图上并显示,这种情况如果把<RelativeLayout/>标签改为<merge/>标签,那么层级结构里就少了RelativeLayout这层布局。
(2)还有就是比如LinearLayout里面使用include嵌入一个布局,而这个嵌入的布局的根节点也是LinearLayout,这样就多了一层没有用的嵌套,这个时候如果我们使用<merge/>作为嵌入布局的根标签就可以避免重复嵌套的问题。

当我们的布局是用的FrameLayout的时候,我们可以把它改成merge 可以避免自己的帧布局和系统的ContentFrameLayout帧布局重叠造成重复计算(measure和layout)

4.ViewStub

ViewStub:当加载的时候才会占用。不加载的时候就是隐藏的,仅仅占用位置。
当ViewStub被设置为可见或调用了ViewStub.inflate()的时候,ViewStub所指向的布局才会被Inflate和实例化
(ViewStub的布局属性会传给它所指向的布局,ViewStub对象会被置空,此时查看布局结构ViewStub是不存在的,取而代之的是被inflate的Layout。

<merge xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
    ……  
    <ViewStub  
        android:layout_gravity="center"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:id="@+id/hint_fail_view"  
        android:inflatedId="@+id/hint_fail_view"  
        android:layout="@layout/fail_view"/>  
</merge>  
 
private View hintFailView;  
if (网络异常) {  
    if (hintFailView == null) {  
        ViewStub viewStub = (ViewStub)this.findViewById(R.id.hint_fail_view);  
        hintFailView = viewStub.inflate(); //注意这里  
        TextView textView = (TextView) hintFailView.findViewById(R.id.tv);  
        textView.setText("网络异常");  
    }  
    hintFailView.setVisibility(View.VISIBLE);  
}else{  
    //网络正常  
    if (hintFailView!= null) {  
        hintFailView.setVisibility(View.GONE);  
    }  
    //业务逻辑  
}  

3.3.绘制过度优化

原因:屏幕上的某个像素点在同一帧的时间内被绘制了多次

过度绘制

1.去掉不必要的背景图片

手机开发者选项里面找到工具:Debug GPU overdraw
由于我们布局设置了背景,同时用到的MaterialDesign的主题会默认给一个背景。
解决的办法:将主题添加的背景去掉getWindow().setBackgroundDrawable(null);  

2.对应listview中的item的背景图片

if (判断是否有图片) {
            Picasso.with(getContext()).load(android.R.color.transparent).into(chat_author_avatar);
            chat_author_avatar.setBackgroundColor(chat.getAuthor().getColor());
        } else {
            Picasso.with(getContext()).load(chat.getAuthor().getAvatarId()).into(
                    chat_author_avatar);
            chat_author_avatar.setBackgroundColor(Color.TRANSPARENT);
        }

3.自定义控件绘制图片的时候如果有重叠可以对画板进行裁剪

canvas.save();  
mPaint.setColor(Color.CYAN);  
//先在屏幕的0,0处绘制一个与我们Bitmap宽高相等的蓝色矩形  
canvas.drawRect(0, 0, width, height, mPaint);  
canvas.restore();  
   
canvas.save();  
//裁剪画布,左上角为0,0  右下角为指定宽高的2倍和1.5倍  
canvas.clipRect(0, 0, width*2, height*3/2);  
//以width,height为左上角绘制我们的Bitmap,由于图片的下半部分在裁剪画布之外所以不显示  
canvas.drawBitmap(mBmp, width, height, mPaint);  
canvas.restore(); 

4.总结优化

  1. 用TextView同时显示图片和文字
android:drawableLeft    android:drawableRight 
  1. 使用CompoundDrables
//Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the text.   
//Use null if you do not want a Drawable there. The Drawables must already have had setBounds(Rect) called.  
//可以在上、下、左、右设置图标,如果不想在某个地方显示,则设置为null  
//但是Drawable必须已经setBounds(Rect)  
//意思是你要添加的资源必须已经设置过初始位置、宽和高等信息  
Drawable drawable= getResources().getDrawable(R.drawable.res);  
drawable.setBounds( 0, 0, drawable.getMinimumWidth(),dra.getMinimumHeight());  
tv.setCompoundDrawables(null, null, drawable, null);  
  1. 使用LinearLayout自带的分割线
android:divider="@drawable/divider"  
android:showDividers="middle"
 
<?xml version="1.0" encoding="utf-8"?>  
<shape xmlns:android="http://schemas.android.com/apk/res/android"  
       android:shape="rectangle">  
    <size android:width="1dp"  
          android:height="1dp"/>  
    <solid android:color="#e1e1e1"/>  
</shape>  
 
showDividers 是分隔线的显示位置,beginning、middle、end分别代表显示在开始、中间、末尾。
还有dividerPadding属性
  1. 使用Space控件
 预留空白
 
<Space  
    android:layout_width="match_parent"  
    android:layout_height="15dp"/>  

  1. 使用TextView的行间距
换行
<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout  
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_height="100dp"  
    android:background="@color/white"  
    android:layout_width="match_parent">  
    <ImageView  
        android:padding="25dp"  
        android:src="@drawable/kd_1"  
        android:layout_width="100dp"  
        android:layout_height="match_parent"/>  
    <TextView  
        android:textSize="14dp"  
        android:lineSpacingExtra="8dp"  
        android:gravity="center_vertical"  
        android:text="揽件方式:上门取件\n快递公司:顺丰快递\n预约时间:9月6日 立即取件\n快递费用:等待称重确定价格"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" />  
</LinearLayout>  
 
可以看到我们仅仅利用android:lineSpacingExtra="8dp"这一行代码就省去了3个TextView。
lineSpacingExtra属性代表的是行间距,默认是0,是一个绝对高度值,同时还有lineSpacingMultiplier属性,它代表行间距倍数,默认为1.0f,是一个相对高度值。如果两者同时设置高度计算规则为mTextPaint.getFontMetricsInt(null) * 行间距倍数 + 行间距。
  1. 使用Spannable
字体
String text = String.format("¥%s   门市价:¥%s", 18.6, 22);  
int z = text.lastIndexOf("门");  
SpannableStringBuilder ssb = new SpannableStringBuilder(text);  
//颜色  
ssb.setSpan(new ForegroundColorSpan(Color.parseColor("#afafaf")), z, text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);  
ssb.setSpan(new ForegroundColorSpan(Color.parseColor("#32BBA9")), 0, z,Spannable.SPAN_INCLUSIVE_EXCLUSIVE);  
//字号  
ssb.setSpan(new AbsoluteSizeSpan(DensityUtil.dip2px(this,10)), 0, text.length(),Spannable.SPAN_INCLUSIVE_EXCLUSIVE);  
ssb.setSpan(new AbsoluteSizeSpan(DensityUtil.dip2px(this,16)), 1, z,Spannable.SPAN_INCLUSIVE_EXCLUSIVE);  
textView.setText(ssb);  

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容