Android性能优化

UI渲染优化

2017-10-12  本文已影响0人  田间小鹿

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

1.UI的渲染过程

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

GPU处理流程 显示

2.绘制过程中的问题

卡顿

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

1.在View显示的过程中是是否有大量的内存分配和释放也就是内存抖动。
2.是否有一个很耗时的操作。
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检测

布局优化

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

==优化思想:查看自己的布局,层次是否很深以及渲染比较耗时,然后想办法能否减少层级以及优化每一个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);  

上一篇 下一篇

猜你喜欢

热点阅读