android 的渲染主要分为两个组件:1.CPU 2.GPU,由这两者共同完成在屏幕上绘制 。
CPU:中央处理器,它集成了运算,缓冲,控制等单元,包括绘图功能.CPU将对象处理为多维图形,纹理(Bitmaps、Drawables等都是一起打包到统一的纹理)。
GPU:一个类似于CPU的专门用来处理Graphics的处理器,用来帮助加快格栅化操作,当然,也有相应的缓存数据(例如缓存已经光栅化过的bitmap等)机制。
OpenGL ES:手持嵌入式设备的3DAPI,跨平台的、功能完善的2D和3D图形应用程序接口API,有一套固定渲染管线流程。
DisplayList:把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的。DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。
格栅化:将图片等矢量资源,转化为一格格像素点的像素图,显示到屏幕上。
垂直同步VSYNC:让显卡的运算和显示器刷新率一致以稳定输出的画面质量。它告知GPU在载入新帧之前,要等待屏幕绘制完成前一帧。下面的三张图分别是GPU和硬件同步所发生的情况,Refresh
Rate:屏幕一秒内刷新屏幕的次数,由硬件决定,例如60Hz.而Frame Rate:GPU一秒绘制操作的帧数,单位fps。如下图:
UI对象—->CPU处理为多维图形,纹理 —–通过OpeGL ES接口调用GPU—-> GPU对图进行光栅化(Frame Rate ) —->硬件时钟(Refresh Rate)—-垂直同步—->投射到屏幕
Android系统每隔16ms发出VSYNC信号(1000ms/60=16.66ms),触发对UI进行渲染, 如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着计算渲染的大多数操作都必须在16ms内完成。正常情况如下:
注意:使顺利在60帧,每帧必须小于16毫秒完成。
当这一帧画面渲染时间超过16ms的时候,垂直同步机制会让显示器硬件 等待GPU完成栅格化渲染操作, 这样会让这一帧画面,多停留了16ms,甚至更多.这样就这造成了 用户看起来 画面停顿.。
当GPU渲染速度过慢,就会导致,某些帧显示的画面内容就会与上一帧的画面相同。
通过上面的介绍,我们知道渲染中可能出现的问题,那么如何避免或者说尽量减少这种问题的发生呢?接下来就是本文要说的过度绘制,减少不必要的绘制,尽量会减少渲染的时间。
过度绘制(Overdraw)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面, 如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。
按照以下步骤打开Show GPU Overrdraw的选项:设置 -> 开发者选项 -> 调试GPU过度绘制 -> 显示GPU过度绘制。
打开“开发者选项”->“GPU呈现模式分析”->“在屏幕上显示为条形图”
这个会分别显示关于StatusBar,NavBar,激活的程序Activity区域的GPU Rendering信息。Activity区域有一根绿色的横线,代表16ms,我们需要确保每一帧花费的总时间都低于这条横线,这样才能够避免出现卡顿的问题。
界面上会滚动显示垂直的柱状图来表示每帧画面所需要渲染的时间,柱状图越高表示花费的渲染时间越长。每一条柱状线都包含三部分:
1.减少布局层级
关闭相关手机上的开发者检测工具开关,打开Android Device Monitor, 找到 Hierarychy view 查看自己的布局找到,深的层级,是否可以做优化. 最外层父容器 是否需要。
使用线性布局LinearLayout排版导致UI层次变深,如果有这类问题,我们就使用相对布局RelativeLayout代替LinearLayout,减少UI的层次;如果不能减少布局层次推荐使用LinearLayout。
没有用的父布局时指没有背景绘制或者没有大小限制的父布局,这样的布局不会对UI效果产生任何影响。我们可以把没有用的父布局,通过merge标签合并来减少UI的层次;
2.去除不必要的背景
有时候为了方便会先给Layout设置一个整体的背景,再给子View设置背景,这里也会造成重叠,如果子View宽度mach_parent,可以看到完全覆盖了Layout的一部分,这里就可以通过分别设置背景来减少重绘。再比如如果采用的是selector的背景,将normal状态的color设置为“@android:color/transparent”,也同样可以解决问题。适时使用Color.TRANSPARENT,因为透明色Color.TRANSPARENT是不会被渲染的,他是透明的。
所以开发过程中我们为某个View或者ViewGroup设置背景的时候,先思考下是否真的有必要,或者思考下这个背景能不能分段设置在子View上,而不是图方便直接设置在根View上。
注意:在不同安卓版本的测试机中,去掉相同层次的背景色后,有的显示正常,有的不正常。
3.优化自定义View的计算
学会裁剪掉View的覆盖部分,增加cpu的计算量,来优化GPU的渲染,这个API可以很好的帮助那些有多组重叠 组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制。并且在onDraw方法中减少View的重复绘制。
下面一个运用clipRect的例子
public class CardView extends View {
private Bitmap[] mCards = new Bitmap[3];
private int[] mImgId = new int[]{R.mipmap.a, R.mipmap.b, R.mipmap.c};
public CardView(Context context) {
super(context);
for (int i = 0; i < mCards.length; i++) {
Bitmap bm = BitmapFactory.decodeResource(getResources(), mImgId[i]);
mCards[i] = Bitmap.createScaledBitmap(bm, 510, 510, false);
}
setBackgroundColor(Color.WHITE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(50, 200);
for (Bitmap bitmap : mCards) {
canvas.translate(150, 0);
canvas.drawBitmap(bitmap, 0, 0, null);
}
canvas.restore();
}
}
对于这段代码,没有使用clipRect方法前,过度绘制检测效果图:
修改onDraw方法:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(50, 200);
for (int i = 0; i < mCards.length; i++) {
canvas.translate(150, 0);
canvas.save();
if (i < mCards.length - 1) {
canvas.clipRect(0, 0, 150, mCards[i].getHeight());
}
canvas.drawBitmap(mCards[i], 0, 0, null);
canvas.restore();
}
canvas.restore();
}
去除windowbackground :
getWindow().setBackgroundDrawable(null);
//getWindow().setBackgroundDrawableResource(android.R.color.transparent);
平时的开发过程中,需要权衡效果和性能,根据需求进行选择. ,养成良好的思考习惯。