第一: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就不会在做第二次计算了。