王学岗性能优化————UI绘制优化(CPU与GPU原理,过度绘制优化,布局优化)

第一:GPU与CPU的工作流程与过度绘制
我们看到一个界面,界面上所有画出来的东西都是由GPU完成,但是怎么去画是由cpu来完成。说白了,CPU是计算画图的方法,GPU是怎么画到屏幕,CPU会把结果交给GPU。
我们的优化也分为两点:(1)cpu:减少xml转换成对象的时间;(2)GPU:减少重复绘制的时间;
什么是过度绘制?
GPU的绘制过程,就跟刷墙一样,一层一层的进行,16ms刷一次。这样就会造成图像覆盖的现象,既无用的图层还是会被绘制在底层,造成不必要的浪费。
如果不懂UI渲染原理,猛戳这里android布局渲染流程
查看过度绘制的方法:
开发者选项——>硬件加速渲染———>调试GPU过度绘制。
会有三个选项,第三个是色盲的选择,我们选择第二个。选择第二个之后你会发现我们的手机变得五颜六色。
在这里插入图片描述
如果Activity只有一个页面的时候,那么屏幕是白色的,加一层东西就会显示浅蓝色,这叫做1层过度绘制(1xOverDraw),依次类推。5层以上(4xOverdraw)显示的都是红色。
二:优化过度绘制的方法(主要减少GPU的工作量)
优化1::xml布局背景色与windowBackgroud冲突
解决方法 getWindow().setBackgroundDrawable(null);设置为空。或者仿照上一篇文章中解决黑白屏的方法设置。
优化2:布局不要随意设置背景,尤其是根布局。
优化3:ImageView不要设置背景色。 iv_imageView.setBackgroundColor(Color.TRANSPARENT);
注意android.R.color.transparent不会消耗任何性能。
代码如下`

 if (chat.getAuthor().getAvatarId() != 0) {
            Picasso.with(getContext()).load(chat.getAuthor().getAvatarId()).into(
                    chat_author_avatar);
            chat_author_avatar.setBackgroundColor(Color.TRANSPARENT);
        } else {
            Picasso.with(getContext()).load(android.R.color.transparent).into(
                    chat_author_avatar);
            chat_author_avatar.setBackgroundColor(chat.getAuthor().getColor());
        }

控件无论设置为透明或是不透明,CPU都会去计算,但是设置透明的时候,不会交给GPU去绘制。设置不透明的时候,会交给GPU去计算。这样节省了GPU时间。
优化4:有一种情形,比如扑克牌发牌,肯定会出现重叠的问题, 如下图所示。
在这里插入图片描述
我们看下优化前的代码
DroidCard类

package com.example.android.mobileperf.render;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
//扑克牌实体类
public class DroidCard {
    //x坐标,y坐标我省略了。
    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

package com.example.android.mobileperf.render;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

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);
		//因为三张图片坐标有偏移,所以绘制list的时候三张图片会一张一张向右摆放。
        for (DroidCard c : mDroidCards) {
            drawDroidCard(canvas,c);
        }

        invalidate();
    }

    private void drawDroidCard(Canvas canvas, DroidCard c) {
        canvas.drawBitmap(c.bitmap,c.x,0f,paint);
    }

}

我们在布局中使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="vertical">
//自定义控件
    <com.example.android.mobileperf.render.DroidCardsView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

虽然这样也可以达到显示的效果,但是UI绘制严重重叠了。该怎么解决呢?
我们可以把显示的那一部分画布裁剪下来。在裁剪的地方绘制。因为画布就这么大,那么多于的地方就不会处理了。GPU也不会理会那个多余的地方。
我们修改DroidCardsView 类

package com.example.android.mobileperf.render;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

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);
        /**
		   //因为三张图片坐标有偏移,所以绘制list的时候三张图片会一张一张向右摆放。
          for (DroidCard c : mDroidCards) {
            drawDroidCard(canvas,c);
           }
         *
         */
		//注意size-1,如果有三张图,我们只画两张,最后一张单独处理就行了。
        for (int i = 0; i < mDroidCards.size() - 1; i++){
            drawDroidCard(canvas, mDroidCards,i);
        }
        drawLastDroidCard(canvas,mDroidCards.get(mDroidCards.size()-1));

        invalidate();
    }

//    private void drawDroidCard(Canvas canvas, DroidCard c) {
//        canvas.drawBitmap(c.bitmap,c.x,0f,paint);
//    }

    /**
     * 绘制最后一个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();
    }
}

第三:布局的优化(主要减少CPU的工作量)
在sdk中有一个工具可以帮助我们做性能优化,D:\sdk\tools\bin目录下的uiautomatorviewer.bat工具。
介绍下android studio中的性能优化工具-Layout Inspector性能优化工具的使用
首先在android studio中找到它。
在这里插入图片描述
选择你要查看的进程,一般会有很多进程,我们这里因为我只有一个项目,所以只有一个进程。
在这里插入图片描述
查看所在进程的布局文件
在这里插入图片描述
navigationBarBackground是我们的导航栏,statusBarBackgroud是最上面的状态栏。整个xml布局结构可以在左侧清楚的看到。
另外在D:\sdk\tools下的monitor.bat中,点击Hierarchy Viewer中可以看到Hierarchy Viewer。其效果与Layout Inspector是类似的。Hierarchy Viewer中有三个点:绿色表示该View的此项性能比该View Tree中超过50%的都要快;黄色表示该View的此项性能比该View Tree中超过50%的都要慢;红色表示该View的此项性能是View Tree中最慢的。
优化1::能在一个平面显示的内容,尽量只用一个容器。不要过多的布局嵌套
优化2:尽可能把相同的容器合并merge
当是父亲独生子的时候,可以使用merge合并,这样在树中会少一层,少一层CPU计算的东西就少了一份。
在这里插入图片描述
LinearLayout所在的布局代码修改跟标签为merge





注意合并的时候LinearLayout不能有ID属性
优化3:能复用的代码用include处理,可以减少cpu的重复工作。能复用的代码尽可能的复用,能复用的东西不要反复去写,反复写就会导致CPU同样的东西重复去计算。比如每个页面都是用的ToorBar。
我们在第一次加载include布局的时候,从CPU到GPU,这一过程是完整的。CPU计算好后放到GPU里运行。GPU会把这些用到的东西做缓存,下次在需要用的时候是从GPU缓存里面取出来执行,这样CPU就不会在做第二次计算了。

猜你喜欢

转载自blog.csdn.net/qczg_wxg/article/details/89741141