Android性能优化记录之界面渲染优化

文中部分文字图片出处http://hukai.me/android-performance-patterns/
Hierarchy Viewer 部分文字出处http://blog.csdn.net/swordgirl2011/article/details/52887925

前言:网上已经有很多对于性能优化的文章,但是为啥自己还要造轮子呢?俗话说的好读万卷书不如行万里路,好记性不如烂笔头。只有自己动手去做了印象才会更深嘛!写这篇是为了记录性能优化这一系列的学习以及使用,顺便锻炼下语言组织能力。

在之前Google发布了一系列的Android性能优化的在线课程,这是在Udacity的教学视频

  • 用户感知界面卡顿的原因
  • 卡顿的原因
  • 过度绘制

用户感知界面卡顿的原因

  • 对于Android系统来说每16ms都会重新绘制一次Activity,应用必须得在16ms内完成屏幕刷新的全部逻辑,这样才能达到每秒60帧(盗图一张),当应用绘制的时间超过16ms 用户就会感觉到界面卡顿,也是俗称的掉帧。当绘制超过16ms时用户会感觉到卡顿,如果在进行交互的时候比如输入文字什么的,这是时候掉帧是一件非常糟糕的事情
    图一这里写图片描述

卡顿的原因

Android 界面的渲染 是由cpu 和Gpu来完成的。cpu最常见的性能问题就是不必要的布局和失效布局这些会在视图结构中进行测量,清除与重新创建,CPU常见的问题就是渲染的时候浪费了时间。造成这种问题的原因有两种

- 列表显示重复创建的次数太多  使cpu工作过度
- GPU进行后期着色的时候浪费了GPU的处理时间 -过度绘制

将一个Activity绘制到屏幕是由栅格化完成的,怎么说?就是由CPU将图像转化成多边形或者纹理在传给CPU进行栅格化操作把图像绘制出来,整个过程就是对象转换以及数据的上传。以减少对象的转换以及数据上传的时间来实现优化

过度绘制

过度绘制是指屏幕上的某一像素在同一时间进行了多次绘制,在进行没必要的绘制时浪费GPU和CPU的资源。过度绘制主要浪费GPU的资源和时间,使其在进行栅格化操作的时候浪费大量时间,打开手机的开发者模式—调试GPU过度绘制—显示过度绘制区域,打开之后会显示过度绘制的颜色。
图二
过度绘制的优化以布局开始, 以下代码为例

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    android:background="@android:color/white"//这里一次背景颜色
    tools:context=".MainActivity">
    <LinearLayout
        android:orientation="vertical"
        android:background="@android:color/white"//这里在一次背景颜色,
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/header_text"/>
    </LinearLayout>

    </LinearLayout>

对两个LinearLayout都给了白色的背景色,我们来看看效果
这里写图片描述
我们发现图片显示了两倍的过度绘制,现在我们将第二个LinearLayout背景色去掉

<LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/header_text"/>
    </LinearLayout>

这里写图片描述
再来看看效果, 变成了一倍 !嗯?1倍?对的1倍,因为受Android手机主题的影响窗口的背景有默认颜色,在根layout没有设置背景颜色的时候在不同的手机上会发现背景是黑色或者白色的这是候怎么办?一行代码搞定

getWindow().setBackgroundDrawable(null);

这里写图片描述
现在是不是干净多了?当然有些过度绘制是不影响性能的比如系统的ActionBar。
在布局中减少多余的背景色我们达到优化的效果,当然啦对于复杂的自定义view我们如何来优化?举个列子当类似于卡片层叠的时候被遮住的一部分是没用必要的绘制的,这是候就需要用到剪切 clipRect()方法的使用了。

这里写图片描述

代码以Google 官方视频里的 图片层叠为例,仿写的。

public class CardView extends View {
    private Paint paint;

    public CardView(Context context) {
        super(context);
        paint=new Paint();
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setAntiAlias(true);
    }

    public CardView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(),
                R.drawable.joanna).copy(Bitmap.Config.ARGB_8888, true);
        canvas.save();
        Utils.drawImage(canvas,bitmap1,000,0,200,200,0,0);
        canvas.restore();
        canvas.save();
        Utils.drawImage(canvas,bitmap1,100,0,200,200,0,0);
 存储新合成的图片
        canvas.restore();
    }

}

这里写图片描述
第二张图片盖住第一张图片的部分 显示了二倍过度绘制,被盖住的地方是没用必要绘制的,这时候我们就用剪切的方式来剪切没一张图片的大小 例:第一张图片只剪切一半大小第二张图片剪切整张图片大小以此类推。我们来看看修改之后的代码

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(),
                R.drawable.joanna).copy(Bitmap.Config.ARGB_8888, true);
        canvas.save();
        canvas.clipRect(0,0,100,200);//
        Utils.drawImage(canvas,bitmap1,000,0,200,200,0,0);
        canvas.restore();
        canvas.save();
        canvas.clipRect(100,0,400,200);//
        Utils.drawImage(canvas,bitmap1,100,0,200,200,0,0);
 存储新合成的图片
        canvas.restore();
    }

这里写图片描述
现在是不是盖住的地方没有过度绘制啦?对于过度绘制的优化总结下来3句话

  • 移除多余的背景色
  • 设置窗体背景为空 getWindow().setBackgroundDrawable(null);
  • 利用clipRect()方法剪切掉不必要的绘制

失效、多余、深度布局

这里说下渲染管道中的CPU部分,在屏幕中绘制某一个图案的时候,Android将高级的xml文件转换成GPU能够识别的对象然后显示到屏幕上,这个操作是在DisplayList的帮助下完成的,DisplayList持有所有要交给GPU绘制到屏幕上的数据信息包含GPU要绘制的全部对象的信息列表以及执行绘制的OpenGL命令列表,在某个view第一次需要被渲染的时候DisplayList会因此被创建,当这个view需要显示在屏幕上时将绘制指令提交给GPU还是执行DisplayList,当时这个view的位置发生改变时仅仅执行DisplayList就行了。
这里写图片描述

当修改了view的某些可见组件的内容时候之前的DisplayList就不能再用了,这个时候就要创建一个新的DisplayList重新更新到屏幕上,当然啦任何view的内容发生变化 就会重新更新,这个过程的表现性能取决于view的复杂程度。
这里写图片描述
当view的大小位置发生改变的时候会经过ViewHierarchy询问各个View的新尺寸,view以及view下的子控件会重新测量大小。当然现在Android系统已经非常处理记录并执行渲染管道,当然啦在处理View太多或者布局更复杂或者自定义View系统执行功能的时间就会越长,当ViewHierarchy失控的时候执行这些功能的时间是和ViewHierarchy中所需要处理的节点数成正比的,View更多或者View更复杂处理的时间就越长,造成的这种浪费的首要原因就是ViewHierarchy包含了太多的无用View,这些view不会显示在屏幕上,一旦发生测量操作就会拖累性能。

这里写图片描述

针对以上问题有一款Hierarchy Viewer工具来查找这些无用View

Hierarchy Viewer

关于Hierarchy Viewer的使用这里就不叙述了,还不知道Hierarchy Viewer如何使用的移步这里

Hierarchy Viewer让布局显示更加结构化一目了然,让我们更容易理解这个结构内的独特视图的相对渲染性能,Hierarchy Viewer的使用在Udacity的教学视频中第11个视频开始讲解。我们就来看看一个例子
这里写图片描述

由图可知这是一个listview 下面的是item的布局代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    android:orientation="horizontal"
    android:paddingBottom="@dimen/chat_padding_bottom">

    <ImageView
        android:id="@+id/chat_author_avatar"
        android:layout_width="@dimen/avatar_dimen"
        android:layout_height="@dimen/avatar_dimen"
        android:layout_margin="@dimen/avatar_layout_margin" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:textColor="#78A">

            <TextView xmlns:android="http://schemas.android.com/apk/res/android"
                android:id="@+id/chat_author_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:gravity="bottom"
                android:padding="@dimen/narrow_space" />

            <TextView xmlns:android="http://schemas.android.com/apk/res/android"
                android:id="@+id/chat_datetime"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:padding="@dimen/narrow_space"
                android:textStyle="italic" />
        </RelativeLayout>

        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/chat_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="@dimen/narrow_space" />
    </LinearLayout>

</LinearLayout>

这是一个横线布局的线性布局这个嵌套相对较深,这时候我们用Hierarchy Viewer来看看这里的ui视图结构
这里写图片描述这里写图片描述
由上图可以看到item的布局节点为三个,在第一个LinearLayout和下一个RelativeLayout他们的测量均为红色,对于颜色的分析如下

  • 如果在叶节点或者ViewGroup中,只有极少的子节点,这可能反映出一个问题,应用可能在设备上运行并不慢,但是你需要指导为什么这个节点是红色的,可以借助Systrace或者Traceview工具,获取更多额外的信息;

  • 如果一个视图组里面有许多的子节点,并且测量阶段呈现为红色,则需要观察下子节点的绘制情况;

  • 如果视图层级结构中的根视图,Messure阶段为红色,Layout阶段为红色,Draw阶段为黄色,这个是比较常见的,因为这个节点是所有其它视图的父类;

  • 如果视图结构中的一个叶子节点,有20个视图是红色的Draw阶段,这是有问题的,需要检查代码里面的onDraw方法,不应该在那里调用。

现在我们采用相对布局来进行优化,优化代码如下

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:paddingBottom="@dimen/chat_padding_bottom">

    <ImageView
        android:id="@+id/chat_author_avatar"

        android:layout_width="@dimen/avatar_dimen"
        android:layout_height="@dimen/avatar_dimen"
        android:layout_alignParentLeft="true"
        android:layout_margin="@dimen/avatar_layout_margin"
        android:src="@drawable/ic_launcher" />

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/chat_author_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        android:layout_toRightOf="@+id/chat_author_avatar"
        android:gravity="bottom"
        android:padding="@dimen/narrow_space"
        android:text="2222" />

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"

        android:id="@+id/chat_datetime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:padding="@dimen/narrow_space"
        android:text="2222"
        android:textStyle="italic" />


    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/chat_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/chat_author_name"
        android:layout_toRightOf="@+id/chat_author_avatar"
        android:padding="@dimen/narrow_space"
        android:text="2222" />


</RelativeLayout>

从XML布局代码中相比没有优化之前的代码是不是清爽了很多,这是我们在来看看Hierarchy Viewer的情况
这里写图片描述
现在看起来是不是更加清爽,好了总结一下:在我们开发时布局的时候尽可能的采用相对布局进行布局,这样能够最大的减少布局嵌套,在开始的时候就应该注意到多重嵌套布局带来的性能问题,这些在后期的性能优化当中也是能够减少工作量的嘛。

后记:这边博文仅仅作为学习的一个笔记,文中有错误或者其他问题欢迎大佬吐槽

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值