CPU::中央处理器,它集成了运算,缓冲,控制等单元,包括绘图功能。CPU将对象处理为多维图形、纹理(Bitmaps、Drawables等都是一起打包到统一的纹理)。
GPU:一个类似于CPU的专门用来处理Graphics的处理器,作用用来帮助加快格栅化操作,当然,也有相应的缓存数据(例如缓存已经光栅化过的bitmap等)机制。
OpenGL ES:是手持嵌入式设备的3DAPI,跨平台的、功能完善的2D和3D图形应用程序接口API,有一套固定渲染管线流程.。
DisplayList在Android把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的,DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。
格栅化:是将Button,Shape,Path,String,Bitmap等组件等矢量资源,转化为一格格像素点的像素图,显示到屏幕上(硬件是:图像处理器,GPU显卡的处理器),过程图如下:
垂直同步VSYNC:让显卡的运算和显示器刷新率一致以稳定输出的画面质量。它告知GPU在载入新帧之前,要等待屏幕绘制完成前一帧。下面的三张图分别是GPU和硬件同步所发生的情况。①Refresh Rate:屏幕一秒内刷新屏幕的次数,由硬件决定,例如60Hz。②Frame Rate:GPU一秒绘制操作的帧数,比如:60fps。正常情况过程图如下:
Android系统的渲染管线分为两个关键组件:CPU和GPU,它们共同工作,在屏幕上绘制图片,每个组件都有自身定义的特定流程。我们必须遵守这些特定的操作规则才能达到效果。
在CPU方面,最常见的性能问题是:不必要和失效的布局,这些内容必须在视图层次结构中进行测量、清除并重新创建。引发这种问题通常有两个原因:一是重建显示列表的次数太多,二是花费太多时间作废视图层次并进行不必要的重绘,这两个原因在更新显示列表或者其他缓存GPU资源时导致CPU工作过度。
在GPU方面,最常见的问题是我们所说的:过度绘制(overdraw),通常是在像素着色过程中,通过其他工具进行后期着色时浪费了GPU处理时间。
CPU在屏幕上绘制图像前会向GPU输入指定的基础指令集,GPU使用这些指令集,主要是多边形和纹理(图片),这一过程通常使用的API就是Android的OpenGL ES。这就是说,在屏幕上绘制UI对象时无论是按钮、路径或者复选框,首先需要在CPU中计算并转换为Polygons和Texture(多边形和纹理),然后使用OpenGL ES将计算好的Polygons和Texture传递到GPU,最后GPU再进行格栅化。
UI对象----->CPU处理为多维图形,纹理----->通过OpeGL ES接口调用GPU-----> GPU对图进行光栅化(Frame Rate )----->硬件时钟(Refresh Rate)----->垂直同步----->投射到屏幕。
图片来源于:深入Android渲染机制
Android系统每隔16ms发出VSYNC信号(1000ms/60=16.66ms),触发对UI进行渲染, 如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着计算渲染的大多数操作都必须在16ms内完成。
正常情况:
渲染超时,计算渲染时间超过16ms。当这一帧画面渲染时间超过16ms的时候,垂直同步机制会让显示器硬件等待GPU完成栅格化渲染操作,这样会让这一帧画面,多停留了16ms、甚至更多。这样就这造成了用户看起来画面停顿。
当GPU渲染速度过慢,就会导致如下情况,某些帧显示的画面内容就会与上一帧的画面相同:
优化方向:CPU的优化,从减轻加工View对象成Polygons(多边形)和Texture(纹理)来下手,测量、清除并重新创建不必要和失效的布局。
任何时候View中的绘制内容发生变化时,都会重新执行创建DisplayList,渲染DisplayList,更新到屏幕上等一 系列操作。这个流程的表现性能取决于你的View的复杂程度,View的状态变化以及渲染管道的执行性能。举个例子:当View的大小发生改变,DisplayList就会重新创建,然后再渲染;而当View发生位移,则DisplayList不会重新创建,而是执行重新渲染的操作。当你的View过于复杂,操作又过于复杂,就会计算渲染时间超过16ms,产生卡顿问题。
渲染耗时呈现工具—GPU呈现模式分析。每一条柱状线都包含三部分:蓝色代表测量绘制Display List的时间;红色代表OpenGL渲染Display List所需要的时间;黄色代表CPU等待GPU处理的时间。中间有一根绿色的横线,代表16ms,需要确保每一帧花费的总时间都低于这条横线,这样才能够避免出现卡顿的问题。
优化方向:OpenGL ES API允许数据上传到GPU后可以对数据进行保存,做了缓存。
优化方向:尽量避免过度绘制(overdraw)
GPU的绘制过程,就跟刷墙一样,一层层的进行,16ms刷一次。这样就会造成图层覆盖的现象,即无用的图层还被绘制在底层,造成不必要的浪费。
过度绘制查看工具使用步骤:设置 -> 开发者选项 -> 调试GPU过度绘制 -> 显示GPU过度绘制。图例展示过度渲染:
随着过度绘制的增多,标记颜色也会逐渐加深,例如1倍过度绘制会被标记为蓝色,2倍、3倍、4倍过度绘制遵循同样的模式。
(1)Hierarchy Viewer在哪?
(2)如何使用?一图胜千言(建议在模拟器中使用)
(1)报错,提示如下log:
[2016-11-20 15:59:49 - ViewServerDevice]Unable to debug device: nem_ul10-52F4C16425002867
Android的官方文档中,有这么一句话:出于安全考虑,Hierarchy Viewer只能连接Android开发版手机或是模拟器。推荐方法:谷歌大神romainguy在github的项目ViewServer,可以实现用HierarchyViewer工具对查看和调试应用的UI。。
可以下载下来导入到IDE中,里面有个ViewServer的类,类注释上也标注了用法,引入方法有两种:
// 1 添加依赖
dependencies {
compile project(':viewserver')
}
// 2 将ViewServer.java添加到项目中
在BaseActivity中或者希望调试的Activity以下该三个方法中,添加几行代码:
public class BaseActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewServer.get(this).addWindow(this);
}
public void onResume() {
super.onResume();
ViewServer.get(this).setFocusedWindow(this);
}
public void onDestroy() {
super.onDestroy();
ViewServer.get(this).removeWindow(this);
}
}
从左到右依次,代表View的:Measure, Layout和Draw的性能,不同颜色代表不同的性能等级:
(1)绿色:渲染的管道阶段,这个视图的渲染速度快于至少一半的其他的视图。
(2)黄色:渲染速度比较慢的50%。
(3)红色:渲染速度非常慢。
红色节点是代表应用性能慢的一个潜在问题,下面是几个例子,如何来分析和解释红点的出现原因?
(1)如果视图层级结构中的根视图,Messure阶段为红色,Layout阶段为红色,Draw阶段为黄色,这个是比较常见的,因为这个节点是所有其它视图的父类;
(2)如果一个视图组里面有许多的子节点,并且测量阶段呈现为红色,则需要观察下子节点的绘制情况;
(3)如果在叶节点或者ViewGroup中,只有极少的子节点,这可能在设备上运行并不慢,但是需要指出为什么这个节点是红色的,可以借助Systrace或者Traceview工具,获取更多额外的信息;
(4)如果视图结构中的一个叶子节点,有20个视图是红色的Draw阶段,这是有问题的,需要检查代码里面的onDraw方法。
查看耗时情况和布局树的深度的工具,提升布局性能的关键点是:尽量保持布局层级的扁平化,避免出现重复的嵌套布局。使用线性布局LinearLayout导致布局层次变深,如果有这类问题,使用相对布局RelativeLayout代替LinearLayout,减少布局的层次;因为RelativeLayout的版本更优于LinearLayout的写法。
例如下面的例子,有2行显示相同内容的视图,分别用两种不同的写法来实现,他们有着不同的层级。
没有用的根布局是指没有背景绘制或者没有大小限制的根布局,这样的布局不会对UI效果产生任何影响。我们可以**把没有用的根布局,通过merge标签合并来减少层次;把没有用的父布局,删除。**以下是例子:当它的布局代码如下:
把没有用的根布局,通过merge标签合并来减少层次;把没有用的父布局,删除后:
merge标签即是合并。merge必须放在布局文件的根节点上;merge并不是ViewGroup,也不是View,对merge标签设置的所有属性都是无效的。如果是merge标签,那么直接将其中的子元素添加到merge标签parent中,这样就保证了不会引入额外的层级。所以:
(1)merge只能作为XML布局的根标签使用;
//LayoutInflate类的源码说明:
} else if (TAG_MERGE.equals(name)) {
// 如果merge不是根节点,报错
throw new InflateException(" must be the root element");
}
(2)因为merge不是View,所以对merge标签设置的所有属性都是无效的。
(3)因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点。如:
//layoutInflater.inflate(parser, root, true)
layoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
(4)如果Activity的布局文件根节点是FrameLayout,可以替换为merge标签,这样,执行setContentView之后,会减少一层FrameLayout节点;
①根节点是FrameLayout的效果
(5)自定义View如果继承LinearLayout,建议让自定义View的布局文件根节点设置成merge,这样能少一层节点。
①根节点是FrameLayout的效果
//自定义的View,竖直方向的LinearLayout
public class MergeLayout extends LinearLayout {
public MergeLayout(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.merge_activity, this, true);
}
}
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
//自定义的View,竖直方向的LinearLayout
public class MergeLayout extends LinearLayout {
public MergeLayout(Context context) {
super(context);
// 设置为数值方向的布局
setOrientation(VERTICAL);
LayoutInflater.from(context).inflate(R.layout.merge_activity, this, true);
}
}
(6)学习链接:Android 布局优化之include与merge
在Android的应用程序开发中,标题栏是必不可少的一个元素,大部分页面都要用到,而且布局都是一样的,这时候使用include标签就显得极其的方便。使用时通常需要注意以下几点:
include标签的layout_*属性会替换掉被include视图的根节点的对应属性。
include标签的id属性会替换掉被include视图的根节点id,如果获取根节点id视图会抛出空指针
Android 布局优化之include与merge
ViewStub标签最大的优点是当你需要时才会加载,使用他并不会影响UI初始化时的性能。各种不常用的布局像:进度条、显示错误消息等,可以使用ViewStub标签,以减少内存使用量,加快渲染速度。
//setVisibility可以被调用多次,但不建议这么做
//如果引用的视图已经被垃圾回收器回收,则抛出异常这也就是为什么setVisibility可以调用多次,但是并不推荐这样做的原因
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
//只能被调用一次,第二次调用会抛出异常"ViewStub must have a non-null ViewGroup viewParent"
//因为ViewStub成功执行inflate方法后会调用parent.removeViewInLayout(this);
//将自己从父节点移除 所以ViewStub的inflate只能调用一次
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
// 方式1,获取ViewStub,
ViewStub listStub = (ViewStub) findViewById(R.id.stub);
listStub.setVisibility(View.VISIBLE);
ListView commLv = findViewById(R.id.stub_comm_lv);
if (listStub.getVisibility() == View.VISIBLE) {
// 已经加载, 否则还没有加载
}
// 方式二
View commLv2;//全局变量
ViewStub listStub2 = (ViewStub) findViewById(R.id.stub) ;
if (commLv2 == null) {
commLv2 = (ListView)listStub2.inflate();
} else {
// ViewStub已经加载
}
Android UI布局优化之ViewStub:使用+源码分析
//避免ListView的3个圆圈报红,尽可能不使用包裹内容属性。
// 等待
开始清除前的渲染图:
清除的内容:不必要的Background:
// 我们主布局的文件已经是background为white了,那么可以移除ListView的白色背景
// Item布局中的LinearLayout的
android:background="@android:color/darker_gray"
// Item布局中的RelativeLayout的
android:background="@android:color/white"
// Item布局中id为id_msg的TextView的
android:background="@android:color/white"
我们的这个Activity要求背景色是灰色,我们的确在layout中去设置了背景色灰色,那么这里注意下,我们的Activity的布局最终会添加在DecorView中,这个View会中的背景是不是就没有必要了,所以我们希望调用mDecor.setWindowBackground(drawable),那么可以在Activity调用getWindow().setBackgroundDrawable(null),可以避免过度绘制一层。
// 在Activity的onCreate方法后
setContentView(R.layout.activity_overdraw_01);
getWindow().setBackgroundDrawable(null);
// 在layout中去设置了背景色colorPrimary
(1)设置方法
这个背景比较难发现,主要需要看Adapter的getView的代码,上述代码你会发现,首先为每个icon设置了背景图片(主要是当没有icon图的时候去显示),然后又设置了一个头像。那么就造成了overdraw,有头像的完全没必要去绘制背景,所有修改代码:
Droid droid = getItem(position);
if(droid.imageId ==-1) {
holder.icon.setBackgroundColor(0x4400ff00);
holder.icon.setImageResource(Color.TRANSPARENT);
} else {
holder.icon.setImageResource(droid.imageId);
holder.icon.setBackgroundResource(Color.TRANSPARENT);
}
(2)看最后的Show GPU Overdraw 与最初的比较
(1)经常会由于疏忽造成很多不必要的绘制,比如大家看下面这样的图。多张卡片叠加,那么如果你是一张一张卡片从左到右的绘制,效果肯定没问题,但是叠加的区域肯定是过度绘制了。并且material design对于界面设计的新的风格更容易造成上述的问题。那么有什么好的方法去改善呢?
(2)答案是有的:Android的Canvas对象给我们提供了很便利的方法clipRect裁剪掉View的覆盖部分,增加cpu的计算量来优化GPU的渲染。
(1)Activity代码
public class OverDrawActivity02 extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new CardView(this));
}
}
(2.1)未修改前CardView 代码
/**
* CardView
*/
public class CardView extends View {
private Bitmap[] mCards = new Bitmap[3];
private int[] mImgId = new int[]{R.drawable.alex, R.drawable.chris, R.drawable.claire};
public CardView(Context context) {
super(context);;
}
public CardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
if (!isInEditMode()) {
initView(context, attrs);
}
}
public void initView(Context context, AttributeSet attrs) {
Bitmap bm = null;
for (int i = 0; i < mCards.length; i++) {
bm = BitmapFactory.decodeResource(getResources(), mImgId[i]);
mCards[i] = Bitmap.createScaledBitmap(bm, 200, 300, false);
}
setBackgroundColor(ContextCompat.getColor(context, R.color.colorAccent));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(20, 120);
for (Bitmap bitmap : mCards) {
canvas.translate(120, 0);
canvas.drawBitmap(bitmap, 0, 0, null);
}
canvas.restore();
}
}
(2.2)修改后的CardView 代码
/**
* 分析得出,除了最后一张需要完整的绘制,其他的都只需要绘制部分;所以我们在循环的时候,给i到n-1都添加了clipRect的代码。
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(20, 120);
for (int i = 0; i < mCards.length; i++) {
canvas.translate(120, 0);
canvas.save();
if (i < mCards.length - 1) {
canvas.clipRect(0, 0, 120, mCards[i].getHeight());
}
canvas.drawBitmap(mCards[i], 0, 0, null);
canvas.restore();
}
canvas.restore();
}
性能优化其实不仅仅是一种技术,而是一种思想,你只听过它的高大上,却不知道它其实就是各个细节处的深入研究和处理。当然,有的时候也需要自己进行权衡效果和性能,根据需求进行选择。还有,Android Device Monitor是个好东西,简直就是性能优化大本营,性能优化的工具基本都在其中。
所以在平时的开发过程中,养成良好的思考习惯是第一步:
//写代码的时候要想:
(1)我的代码是不是多余?
(2)我的对象有没有必要在循环中创建?
(3)我的计算方法是不是最优?
//画界面的时候要想:
(1)布局是否有背景?
(2)是否可以删掉多余的布局?
(3)布局是否扁平化,移除非必需的UI组?
(4)自定义View是否进行了裁剪处理?
(1)RelativeLayout会让子View调用2次onMeasure,LinearLayout在有weight时,也会调用子View2次onMeasure。
(2)RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
(3)在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
(4)能用两层LinearLayout,尽量用一个RelativeLayout,在时间上此时RelativeLayout耗时更小。另外LinearLayout慎用layout_weight,也将会增加一倍耗时操作。由于使用LinearLayout的layout_weight,大多数时间是不一样的,这会降低测量的速度。这只是一个如何合理使用Layout的案例,必要的时候,你要小心考虑是否用layout weight。
(5)总之减少层级结构,才是王道,让onMeasure做延迟加载,用viewStub,include等一些技巧。
那些 Android 程序员必会的视图优化策略
深入Android渲染机制
Google《Android性能优化》学习笔记
Hongyang-Android UI性能优化实战 识别绘制中的性能问题
Hierarchy Viewer使用详解
https://blog.csdn.net/bboyfeiyu/article/details/45869393
ListView滑动删除实现之一——merge标签与LayoutInflater.inflate()