【0191】Android 性能优化之UI优化
1. 导致UI卡顿的原因
【问】UI卡顿是如何造成的?
【答】UI卡顿是指主线程了(UI线程)卡顿。
导致的原因有二:
【1】外部引起的
比如:Activity里面直接进行网络访问/大文件的IO操作
内存这一块有些什么要注意的。
1)内存抖动的问题。
new Object
obj = null
2)一个方法太耗时了。
【2】View本身的卡顿
自定义View要注意的,能否优化
1)可以使用Allocation Tracing来定位大致的情况
2)可以使用TraceView来确定详细的问题所在。
【gc机制说明卡顿的问题】
当这些GCs所用时间超过一般值,或者一大堆一起执行会耗费庞大的帧象时间,这是很麻烦的事情。
绘图过程中GC回收


首先程序在任意帧内执行GCs所用的时间越多,消除少于16毫秒的呈像障碍,
所必需的时间就会变少,如果有许多GCs或一大串指令一个接一个地操作,帧象时间很可能会超过16毫秒的呈像障碍,这会导致隐形的碰撞或闪躲。
其次,指令流程可能造成GCs强制执行的次数增多,或者,执行时间超过正常值。
例如,在一个长期运行的循环最内侧分配囤积对象,很多数据就会污染内存堆,马上就会有许多GCs启动,由于这一额外的内存压力,虽然内存环境管理良好,计算比其他语言复杂,内存泄露仍会产生,这些漏洞在GCs启动时,通过无法被释放的数据污染内存堆,严重降低可用空间的总量,并以常规方式强制GC的执行。就是这样,如果要减少任意帧内启动GC的次数,需要着重优化程序的内存使用量,从指令的角度看,或许很难追踪这些问题的起因,
2.UI的渲染机制
VSYNC(垂直刷新/绘制)
60HZ是屏幕刷新理想的频率。60fps---一秒内绘制的帧数。
24帧/秒 电源胶卷时代
在60fps内,系统会得到发送的VSYNC(垂直刷新)信号qu去进行渲染,就会正常地绘制。
60fps要求:每一帧只能停留16ms.
VSYNC:有两个概念
1)Refresh Rate:屏幕在一秒时间内刷新屏幕的次数----有硬件的参数决定,比如60HZ.
2)Frame Rate:GPU在一秒内绘制操作的帧数,比如:60fps。
GPU刷新:GPU帮助我们将UI组件等计算成纹理Texture和三维图形Polygons
同时会使用OpenGL---会将纹理和Polygons缓存在GPU内存里面。
View Tree。
3. 使用Android Allocation Tracking分析
【实例】执行一个gif动画,在动画执行的过程中点击按钮“走一个”,在执行过程的过程中会执行计算。

【源码】关键是在imPrettySureSortingIsFree()中执行大量的计算任务
1 public class MemoryChurnActivity extends Activity { 2 public static final String LOG_TAG = "Ricky"; 3 4 @Override 5 protected void onCreate(Bundle savedInstanceState) { 6 super.onCreate(savedInstanceState); 7 setContentView(R.layout.activity_caching_exercise); 8 9 Button theButtonThatDoesFibonacciStuff = (Button) findViewById(R.id.caching_do_fib_stuff); 10 theButtonThatDoesFibonacciStuff.setText("走一个"); 11 12 theButtonThatDoesFibonacciStuff.setOnClickListener(new View.OnClickListener() { 13 @Override 14 public void onClick(View v) { 15 imPrettySureSortingIsFree(); //执行大量的计算 16 } 17 }); 18 WebView webView = (WebView) findViewById(R.id.webview); 19 webView.getSettings().setUseWideViewPort(true); 20 webView.getSettings().setLoadWithOverviewMode(true); 21 webView.loadUrl("file:///android_asset/shiver_me_timbers.gif"); 22 } 23 24 /** 25 * 排序后打印二维数组,一行行打印 26 */ 27 public void imPrettySureSortingIsFree() { 28 int dimension = 300; 29 int[][] lotsOfInts = new int[dimension][dimension]; 30 Random randomGenerator = new Random(); 31 for(int i = 0; i < lotsOfInts.length; i++) { 32 for (int j = 0; j < lotsOfInts[i].length; j++) { 33 lotsOfInts[i][j] = randomGenerator.nextInt(); 34 } 35 } 36 37 for(int i = 0; i < lotsOfInts.length; i++) { 38 String rowAsStr = ""; 39 //排序 40 int[] sorted = getSorted(lotsOfInts[i]); 41 //拼接打印 42 for (int j = 0; j < lotsOfInts[i].length; j++) { 43 rowAsStr += sorted[j]; 44 if(j < (lotsOfInts[i].length - 1)){ 45 rowAsStr += ", "; 46 } 47 } 48 Log.i("ricky", "Row " + i + ": " + rowAsStr); 49 } 50 } 51 52 public int[] getSorted(int[] input){ 53 int[] clone = input.clone(); 54 Arrays.sort(clone); 55 return clone; 56 } 57 58 }
【内存抖动效果】再点击按钮“走一个”之后,出现内存抖动。


【分析】




经过分析是由MemoryChurnAcivity造成的。
【优化之后】使用StringBuilder
1 // 优化以后 2 StringBuilder sb = new StringBuilder(); 3 String rowAsStr = ""; 4 for(int i = 0; i < lotsOfInts.length; i++) { 5 //清除上一行 6 sb.delete(0,rowAsStr.length()); 7 //排序 8 int[] sorted = getSorted(lotsOfInts[i]); 9 //拼接打印 10 for (int j = 0; j < lotsOfInts[i].length; j++) { 11 // rowAsStr += sorted[j]; 12 sb.append(sorted[j]); 13 if(j < (lotsOfInts[i].length - 1)){ 14 // rowAsStr += ", "; 15 sb.append(", "); 16 } 17 } 18 rowAsStr = sb.toString(); 19 Log.i("ricky", "Row " + i + ": " + rowAsStr); 20 }
【优化之后】很明显没有再出现内存抖动的情况

4.使用TrackingView分析
TraceView工具能做什么?
从代码层面分析性能问题,针对每个方法来分析,比如当我们发现我们的应用出现卡顿的时候,我们可以来分析出现卡顿时在方法的调用上有没有很耗时的操作,关注以下两个问题:
- 调用次数不多,但是每一次执行都很耗时
- 方法耗时不大,但是调用次数太多
简单一点来说就是我们能找到频繁被调用的方法,也能找到执行非常耗时的方法,前者可能会造成Cpu频繁调用,手机发烫的问题,后者就是卡顿的问题
TraceView工具启动
打开Monitor,点击图中的标注的按钮,启动追踪:

TraceView工具面板
打开App操作你的应用后,再次点击的话就停止追踪并且自动打开traceview分析面板:

traceview的面板分上下两个部分:
• 时间线面板以每个线程为一行,右边是该线程在整个过程中方法执行的情况
• 分析面板是以表格的形式展示所有线程的方法的各项指标
时间线面板

左边是线程信息,main线程就是Android应用的主线程,这个线程是都会有的,其他的线程可能因操作不同而发生改变.每个线程的右边对应的是该线程中每个方法的执行信息,左边为第一个方法执行开始,最右边为最后一个方法执行结束,其中的每一个小立柱就代表一次方法的调用,你可以把鼠标放到立柱上,就会显示该方法调用的详细信息:

你可以随意滑动你的鼠标,滑倒哪里,左上角就会显示该方法调用的信息。
1.如果你想在分析面板中详细查看该方法,可以双击该立柱,分析面板自动跳转到该方法:

2.放大某个区域
刚打开的面板中,是我们采集信息的总览,但是一些局部的细节我们看不太清,没关系,该工具支持我们放大某个特殊的时间段:

如果想回到最初的状态,双击时间线就可以。
3.每一个方法的表示:

可以看出来,每一个方法都是用一个凹型结构来表示,坐标的凸起部分表示方法的开始,右边的凸起部分表示方法的结束,中间的直线表示方法的持续.
分析面板
面板列名含义如下:
|
名称 |
意义 |
|
Name |
方法的详细信息,包括包名和参数信息 |
|
Incl Cpu Time |
Cpu执行该方法该方法及其子方法所花费的时间 |
|
Incl Cpu Time % |
Cpu执行该方法该方法及其子方法所花费占Cpu总执行时间的百分比 |
|
Excl Cpu Time |
Cpu执行该方法所话费的时间 |
|
Excl Cpu Time % |
Cpu执行该方法所话费的时间占Cpu总时间的百分比 |
|
Incl Real Time |
该方法及其子方法执行所话费的实际时间,从执行该方法到结束一共花了多少时间 |
|
Incl Real Time % |
上述时间占总的运行时间的百分比 |
|
Excl Real Time % |
该方法自身的实际允许时间 |
|
Excl Real Time |
上述时间占总的允许时间的百分比 |
|
Calls+Recur |
调用次数+递归次数,只在方法中显示,在子展开后的父类和子类方法这一栏被下面的数据代替 |
|
Calls/Total |
调用次数和总次数的占比 |
|
Cpu Time/Call |
Cpu执行时间和调用次数的百分比,代表该函数消耗cpu的平均时间 |
|
Real Time/Call |
实际时间于调用次数的百分比,该表该函数平均执行时间 |
你可以点击某个函数展开更详细的信息:

展开后,大多数有以下两个类别:
• Parents:调用该方法的父类方法
• Children:该方法调用的子类方法
如果该方法含有递归调用,可能还会多出两个类别:
• Parents while recursive:递归调用时所涉及的父类方法
• Children while recursive:递归调用时所涉及的子类方法
首先我们来看当前方法的信息:
|
列 |
值 |
|
Name |
24 android/widget/FrameLayout.draw(L android/graphics/Canvas;)V |
|
Incl Cpu% |
20.9% |
|
Incl Cpu Time |
375.201 |
|
Excl Cpu Time % |
0.0% |
|
Excl Cpu Time |
0.000 |
|
Incl Real Time % |
1.1% |
|
Incl Real Time |
580.668 |
|
Excl Real Time % |
0.0% |
|
Excl Real Time |
0.000 |
|
Calls+Recur |
177+354 |
|
Cpu Time/Call |
0.707 |
|
Real Time/Call |
1.094 |
根据下图中的toplevel可以看出总的cpu执行时间为1797.167ms,当前方法占用cpu的时间为375.201,375.201/1797.167=0.2087,和我们的Incl Cpu Time%是吻合的。当前方法消耗的时间为580.668,
而toplevel的时间为53844.141ms,580.668/53844.141=1.07%,和Incl Real Time %也是吻合的。在来看调用次数为177,递归次数为354,和为177+354=531,375.201/531 = 0.7065和Cpu Time/Call也是吻合的,580.668/531=1.0935,和Real Time/Call一栏也是吻合的。
![]()
Parents
现在我们来看该方法的Parents一栏: ![]()
|
列 |
值 |
|
Name |
22 com/android/internal/policy/impl/PhoneWindow$DecorView.draw(Landroid/graphics/Canvas;)V |
|
Incl Cpu% |
100% |
|
Incl Cpu Time |
375.201 |
|
Excl Cpu Time % |
无 |
|
Excl Cpu Time |
无 |
|
Incl Real Time % |
100% |
|
Incl Real Time |
580.668 |
|
Excl Real Time % |
无 |
|
Excl Real Time |
无 |
|
Call/Total |
177/531 |
|
Cpu Time/Call |
无 |
|
Real Time/Call |
无 |
其中的Incl Cpu Time%变成了100%,因为在这个地方,总时间为当前方法的执行时间,这个时候的Incl Cpu Time%只是计算该方法调用的总时间中被各父类方法调用的时间占比,比如Parents有2个父类方法,那就能看出每个父类方法调用该方法的时间分布。因为我们父类只有一个,所以肯定是100%,Incl Real Time一栏也是一样的,重点是Call/Total,之前我们看当前方式时,这一栏的列名为Call+Recur,而现在变成了Call/Total,这个里面的数值变成了177/531,因为总次数为531次,父类调用了177次,其他531次是递归调用。这一数据能得到的信息是,当前方法被调用了多少次,其中有多少次是父类方法调用的。
Children

可以看出来,我们的子类有2个,一个是自身,一个是23android/view/View.draw(L android/graphics/Canvas;)V,self代表自身方法中的语句执行情况,由上面可以看出来,该方法没有多余语句,直接调用了其子类方法。另外一个子类方法,可以看出被当前方法调用了177次,但是该方法被其他方法调用过,因为他的总调用次数为892次,你可以点击进入子类方法的详细信息中。
Parents while recursive

列举了递归调用当前方法的父类方法,以及其递归次数的占比,犹豫我们当前的方法递归了354次,以上三个父类方法递归的次数分别为348+4+2 = 354次。
Children while recursive

列举了当递归调用时调用的子类方法。
4.1 使用TrackingView分析
【简单使用】在DDMS中,记得先选中要Tracking的进程。





【完整展现的所有的内容】代表点击start allocation tracking按钮开始到再次点击stop allocation tracking按钮结束执行的内存的情况,需要计算一段时间。

4.2 TrackingView实例分析-内存抖动
【实例分析-1】在main主线程中出现了大量的内存抖动的情况,短时间内内存分配形成黑色部分和彩色的冒尖的部分。HeapTaskDaemon是堆内存分配,非常频繁。


当鼠标停留在时间轴面板时在时间参数面板中会显示调用的此时对应的方法,parents是指该方法被哪个方法调用。children是指该方法被哪个方法调用。parents和children都可以在此被点击查看上一层或者下一层的调用关系。逐渐递归查看。

4.4 TrackingView实例分析-cpu耗时
【实例分析-2】gif执行的同时计算斐波那契数列。同一个方法被反复的调用。




【关键参数-1】在点击展开该方法之后,查看parents项,查看到Incl Cpu Time %参数。
此方法在逐层追溯判断具体是哪个方法导致cpu时间耗时长的重要依据。
如下面的例子:10的方法被9方法调用时,9方法在所有10方法被调用的方法中所占的时间。
在实际的项目中可能10的方法被调用的类不止一个,可能有A类B类C类,此时该参数所占的百分比出现3个百分比,假如是90%、9%,1%。
非常明显的可以看出来是A类中的方法导致该方法占用cpu时间过长,因此需要追溯的是A类的方法,而不是B类/C类。

【其他参数】
|
名称 |
意义 |
|
Name |
方法的详细信息,包括包名和参数信息 |
|
Incl Cpu Time |
Cpu执行该方法该方法及其子方法所花费的时间--非常重要的参数 |
|
Incl Cpu Time % |
Cpu执行该方法该方法及其子方法所花费占Cpu总执行时间的百分比 |
|
Excl Cpu Time |
Cpu执行该方法所花费的时间 |
|
Excl Cpu Time % |
Cpu执行该方法所花费的时间占Cpu总时间的百分比 |
|
Incl Real Time |
该方法及其子方法执行所话费的实际时间,从执行该方法到结束一共花了多少时间 |
|
Incl Real Time % |
上述时间占总的运行时间的百分比 |
|
Excl Real Time % |
该方法自身的实际允许时间 |
|
Excl Real Time |
上述时间占总的允许时间的百分比 |
|
Calls+Recur |
调用次数+递归次数,只在方法中显示,在子展开后的父类和子类方法这一栏被下面的数据代替--非常重要的参数 |
|
Calls/Total |
调用次数和总次数的占比 |
|
Cpu Time/Call |
Cpu执行时间和调用次数的百分比,代表该函数消耗cpu的平均时间---非常重要的参数 |
|
Real Time/Call |
实际时间于调用次数的百分比,该表该函数平均执行时间 |

【优化】利用空间换时间的方法,将前一个数据暂存起来。
1 // 优化后的斐波那契数列的非递归算法 caching缓存+批处理思想 2 public int computeFibonacci(int positionInFibSequence) { 3 int prev = 0; 4 int current = 1; 5 int newValue; 6 for (int i=1; i<positionInFibSequence; i++) { 7 newValue = current + prev; 8 prev = current; 9 current = newValue; 10 } 11 return current; 12 }



5.UI渲染
5.1 UI卡顿
【sm】下面的文字摘自网络博客,具体哪篇是原创不知道。
Android系统每隔16ms就重新绘制一次Activity,也就是说,我们的应用必须在16ms内完成屏幕刷新的全部逻辑操作,这样才能达到每秒60帧,然而这个每秒帧数的参数由手机硬件所决定,现在大多数手机屏幕刷新率是60赫兹(赫兹是国际单位制中频率的单位,它是每秒中的周期性变动重复次数的计量),也就是说我们有16ms(1000ms/60次=16.66ms)的时间去完成每帧的绘制逻辑操作,如果错过了,比如说我们花费34ms才完成计算,那34ms中的第16ms的那帧是不会绘制的,因此就会出现我们称之为丢帧的情况。
【注】此处的16ms的时间内需要做的工作有:代码完成的功能+cpu的计算+gpu的渲染。activity的绘制是瞬间的事情,并非是指绘制需要16ms;
类比的实例:人类坐地铁。16ms发一次车,如果在16ms内没有完成赶到地铁车门口,地铁是不会等待,出现丢帧。只能等待下次的绘制。

5.2 绘制的过程
绘制过程中比较耗时的操作:
1.CPU计算时间 2.CPU将计算好的Polygons和Texture传递到GPU的时候也需要时间 OpenGL ES API允许数据上传到GPU后可以对数据进行保存,做了缓存。 3.GPU进行格栅化
Android系统的渲染管线分为两个关键组件:CPU和GPU,它们共同工作,在屏幕上绘制图片,每个组件都有自身定义的特定流程。必须遵守这些特定的操作规则才能达到效果。

在CPU方面,最常见的性能问题是不必要的布局和失效,这些内容必须在视图层次结构中进行测量、清除并重新创建,
引发这种问题通常有两个原因:一是重建显示列表的次数太多,二是花费太多时间作废视图层次并进行不必要的重绘,这两个原因在更新显示列表或者其他缓存GPU资源时导致CPU工作过度。
在GPU方面,最常见的问题是我们所说的过度绘制(overdraw),通常是在像素着色过程中,通过其他工具进行后期着色时浪费了GPU处理时间。

CPU和 GPU
想要开发一款性能优越的应用,我们必须了解底层是如何运行的。
有一个主要问题就是,Activity是如何绘制到屏幕上的?那些复杂的XML布局文件和标记语言,是如何转化成用户能看懂的图像的?
实际上,这是由格栅化操作来完成的,格栅化就是将例如字符串、按钮、路径或者形状的一些高级对象,拆分到不同的像素上在屏幕上进行显示,格栅化是一个非常费时的操作。
所有手机都有硬件图像处理器(GPU显卡的处理器),目的就是加快格栅化的操作,GPU在上个世纪90年代被引入用来帮助加快格栅化操作。

GPU使用一些指定的基础指令集,主要是多边形和纹理,也就是图片,CPU在屏幕上绘制图像前会向GPU输入这些指令,这一过程通常使用的API就是Android的OpenGL ES,
这就是说,在屏幕上绘制UI对象时无论是按钮、路径或者复选框,都需要在CPU中首先转换为多边形或者纹理,然后再传递给GPU进行格栅化。
cpu会将输入的资源(bitmap、view、三维图形、path)转化成为三维图形polygons和纹理textures,是属于非常耗时和耗电的操作。

要知道,一个UI对象转换为一系列多边形和纹理的过程肯定相当耗时,从CPU上传处理数据到GPU同样也很耗时。
所以很明显,需要尽量减少对象转换的次数,以及上传数据的次数,幸亏,OpenGL ES API允许数据上传到GPU后可以对数据进行保存,
当我们下次绘制一个按钮时,只需要在GPU存储器里引用它,然后告诉OpenGL如何绘制就可以了,
一条经验之谈:渲染性能的优化就是尽可能地上传数据到GPU,然后尽可能长地在不修改的情况下保存数据,因为每次上传资源到GPU时,都会浪费宝贵的处理时间,
Android系统的Honeycomb版本发布之后,整个UI渲染系统就在GPU中运行,之后各个版本都在渲染系统性能方面有更多改进。
Android系统在降低、重新利用GPU资源方面做了很多工作,这方面完全不用担心,举例说,任何我们的主题所提供的资源,例如Bitmaps、Drawables等都是一起打包到统一的纹理当中,然后使用网格工具上传到GPU,例如Nine Patches等
这样每次需要绘制这些资源时,就不用做任何转换,他们已经存储在GPU中了,大大加快了这些视图类型的显示。然而随着UI对象的不断升级,渲染流程也变得越来越复杂,例如说绘制图像,就是把图片上传到CPU存储器,然后传递到GPU中进行渲染。
路径使用时完全另外一码事,我们需要在CPU中创建一系列的多边形,甚至在GPU中创建掩蔽纹理来定义路径。
绘制字符就更加复杂一些,首先我们需要在CPU中把字符绘制制成图像,然后把图像上传到GPU进行渲染再返回到CPU,在屏幕上为字符串的每个字符绘制一个正方形。
现在Android系统已经解决了大多数性能问题,除非我们还有更高要求,我们基本不会发现与GPU相关的问题,然后还有一个GPU性能问题瓶颈,这个问题困扰着每个程序员,这就是过度绘制。
6. GPU过度绘制优化
【GPU过度绘制】是指屏幕上的某个像素点在同一帧的时间内被绘制了多次
粉刷房子,给墙壁粉刷工作量非常大,如果需要重新粉刷,第一次的粉刷就白干了。
同样的道理,应用程序会因为过度绘制,从而导致性能问题,如果我们想兼顾高性能和完美的设计,往往会碰到一种性能问题,即过度绘制。

有一堆重叠的UI卡片,最接近用户的卡片在最上面,其余卡片都藏在下面,也就是说花大力气绘制的那些下面的卡片基本都是不可见的。
问题就在于此,因为每次像素经过渲染后,并不是用户最后看到的部分,这就是在浪费GPU的时间。
目前流行的一些布局是一把双刃剑,带给我们漂亮视觉感受的同时,也造成过度绘制的问题,为了最大限度地提高应用程序的性能,必须尽量减少过度绘制。
Android手机提供了查看过度绘制情况的工具,在开发者选项中打开“Show GPU overdraw”选项,手机屏幕显示会出现一些异常不用过于惊慌,Android在屏幕上使用不同颜色,标记过度绘制的区域,
如果某个像素点只渲染了一次,看到的是它原来的颜色,随着过度绘制的增多,标记颜色也会逐渐加深,例如1倍过度绘制会被标记为蓝色,2倍、3倍、4倍过度绘制遵循同样的模式。
所以当调试应用程序的用户界面时,目标就是尽可能的减少过度绘制,将红色区块转变成蓝色区块,为了完成目标有两种清楚过度绘制的方法,首先要从视图中清楚那些,不必要的背景和图片,
他们不会在最终渲染图像中显示,记住,这些都会影响性能。
其次,对视图中重叠的屏幕区域进行定义,从而降低CPU和GPU的消耗。

【设置手机选项中的overDraw调试选项】

6.1 背景色设置优化
【主题往往会设置一个背景色】
优化:尽量避免过度绘制(overdraw) GPU如何优化: 1.背景经常容易造成过度绘制。 手机开发者选项里面找到工具:Debug GPU overdraw 由于我们布局设置了背景,同时用到的MaterialDesign的主题会默认给一个背景。 /解决的办法:将主题添加的背景去掉

左侧是没有去掉主题背景色的绘制,右侧是去掉主题背景色的绘制。由原来的2层绘制变为了现在的1次绘制。


【布局当中的一系列的背景色会造成过度绘制】


优化前后的对比


【ListView中的优化】图片加载的优化



6.2 自定义控件优化处理过度绘制
【方法】通过裁剪

【实例】类似于扑克牌显示左侧字母的效果
中间的部分会被逐层遮盖,出现过度绘制


【优化前的代码】源码下载地址:
1 public class DroidCardsView extends View { 2 //图片与图片之间的间距 3 private int mCardSpacing = 50; 4 //图片与左侧距离的记录 5 private int mCardLeft = 10; 6 7 private List<DroidCard> mDroidCards = new ArrayList<DroidCard>(); 8 9 private Paint paint = new Paint(); 10 11 public DroidCardsView(Context context) { 12 super(context); 13 initCards(); 14 } 15 16 public DroidCardsView(Context context, AttributeSet attrs) { 17 super(context, attrs); 18 initCards(); 19 } 23 /** 24 * 初始化卡片集合 25 */ 26 protected void initCards(){ 27 Resources res = getResources(); 28 mDroidCards.add(new DroidCard(res,R.drawable.alex,mCardLeft)); 29 30 mCardLeft+=mCardSpacing; 31 mDroidCards.add(new DroidCard(res,R.drawable.claire,mCardLeft)); 32 33 mCardLeft+=mCardSpacing; 34 mDroidCards.add(new DroidCard(res,R.drawable.kathryn,mCardLeft)); 35 } 36 37 @Override 38 protected void onDraw(Canvas canvas) { 39 super.onDraw(canvas); 40 for (DroidCard c : mDroidCards){ 41 drawDroidCard(canvas, c); 42 } 43 44 invalidate(); 45 } 46 47 /** 48 * 绘制DroidCard 49 * @param canvas 50 * @param c 51 */ 52 private void drawDroidCard(Canvas canvas, DroidCard c) { 53 canvas.drawBitmap(c.bitmap,c.x,0f,paint); //此处单纯的将所有的bitMap直接绘制,没有考虑过度绘制 54 } 55 }
【优化】增加cpu的压力减小gpu的压力
1 public class DroidCardsView extends View { 2 3 //图片与图片之间的间距 4 private int mCardSpacing = 150; 5 //图片与左侧距离的记录 6 private int mCardLeft = 10; 7 8 private List<DroidCard> mDroidCards = new ArrayList<DroidCard>(); 9 10 private Paint paint = new Paint(); 11 12 public DroidCardsView(Context context) { 13 super(context); 14 initCards(); 15 } 16 17 public DroidCardsView(Context context, AttributeSet attrs) { 18 super(context, attrs); 19 initCards(); 20 } 24 /** 25 * 初始化卡片集合 26 */ 27 protected void initCards(){ 28 Resources res = getResources(); 29 mDroidCards.add(new DroidCard(res, R.drawable.alex,mCardLeft)); 30 31 mCardLeft+=mCardSpacing; 32 mDroidCards.add(new DroidCard(res, R.drawable.claire,mCardLeft)); 33 34 mCardLeft+=mCardSpacing; 35 mDroidCards.add(new DroidCard(res, R.drawable.kathryn,mCardLeft)); 36 } 37 38 @Override 39 protected void onDraw(Canvas canvas) { //修改 40 super.onDraw(canvas); 41 for (int i = 0; i < mDroidCards.size() - 1; i++){ 42 drawDroidCard(canvas, mDroidCards,i); 43 } 44 45 drawLastDroidCard(canvas,mDroidCards.get(mDroidCards.size()-1)); 46 invalidate(); 47 } 48 49 /** 50 * 绘制最后一个DroidCard 51 * @param canvas 52 * @param c 53 */ 54 private void drawLastDroidCard(Canvas canvas,DroidCard c) { //++++++++ 55 canvas.drawBitmap(c.bitmap,c.x,0f,paint); 56 } 57 58 /** 59 * 绘制DroidCard 60 * @param canvas 61 * @param mDroidCards 62 * @param i 63 */ 64 private void drawDroidCard(Canvas canvas,List<DroidCard> mDroidCards,int i) { 65 DroidCard c = mDroidCards.get(i); 66 canvas.save(); //画布保存 67 canvas.clipRect((float)c.x,0f,(float)(mDroidCards.get(i+1).x),(float)c.height); //关键计算画布的大小 68 canvas.drawBitmap(c.bitmap,c.x,0f,paint); 69 canvas.restore(); //画布剪裁 70 }
优化前后的对比;

7.CPU优化
CPU的优化,从减轻加工View对象成Polygons和Texture来下手
View Hierarchy中包涵了太多的没有用的view,这些view根本就不会显示在屏幕上面,
一旦触发测量和布局操作,就会拖累应用的性能表现。
1.如何找出里面没用的view呢?或者减少不必要的view嵌套。
工具:Hierarchy Viewer检测
【说明】真机无法使用Hierarchy Viewer的解决办法:https://blog.csdn.net/yuminfeng728/article/details/52640110
使用ViewServer让Android真机连接Hierarchy Viewer的步骤 https://blog.csdn.net/chenjian723122704/article/details/79010625
Android 中性能优化之布局优化 https://blog.csdn.net/yuminfeng728/article/details/52168133



三个圆点分别代表:测量、布局、绘制三个阶段的性能表现。
1)绿色:渲染的管道阶段,这个视图的渲染速度快于至少一半的其他的视图
2)黄色:渲染速度比较慢的50%
3)红色:渲染速度非常慢
【优化的方法】减少view中的布局的层次,减少Layout
【优化思想】优化思想:查看自己的布局,层次是否很深以及渲染比较耗时,然后想办法能否减少层级以及优化每一个View的渲染时间。


【优化前的布局】
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" 6 android:orientation="horizontal" 7 android:paddingBottom="@dimen/chat_padding_bottom"> 8 9 <ImageView 10 android:id="@+id/chat_author_avatar" 11 android:layout_width="@dimen/avatar_dimen" 12 android:layout_height="@dimen/avatar_dimen" 13 android:layout_margin="@dimen/avatar_layout_margin" /> 14 15 <LinearLayout 16 android:layout_width="match_parent" 17 android:layout_height="wrap_content" 18 android:background="@android:color/darker_gray" 19 android:orientation="vertical"> 20 //去掉此层-Layout,保留内部的view 21 <RelativeLayout 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:textColor="#78A" 25 android:background="@android:color/white" 26 android:orientation="horizontal"> 27 28 <TextView xmlns:android="http://schemas.android.com/apk/res/android" 29 android:layout_width="wrap_content" 30 android:layout_height="wrap_content" 31 android:layout_alignParentLeft="true" 32 android:padding="@dimen/narrow_space" 33 android:gravity="bottom" 34 android:id="@+id/chat_author_name" /> 35 36 <TextView xmlns:android="http://schemas.android.com/apk/res/android" 37 android:layout_width="wrap_content" 38 android:layout_height="wrap_content" 39 android:layout_alignParentRight="true" 40 android:textStyle="italic" 41 android:padding="@dimen/narrow_space" 42 android:id="@+id/chat_datetime" /> 43 </RelativeLayout> 44 45 <TextView xmlns:android="http://schemas.android.com/apk/res/android" 46 android:layout_width="match_parent" 47 android:layout_height="match_parent" 48 android:padding="@dimen/narrow_space" 49 android:background="@android:color/white" 50 android:id="@+id/chat_text" /> 51 </LinearLayout> 52 </LinearLayout>
【优化后的布局】
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent"> 6 7 <ImageView 8 android:id="@+id/chat_author_avatar" 9 android:layout_width="@dimen/avatar_dimen" 10 android:layout_height="@dimen/avatar_dimen" 11 android:src="@drawable/alex"/> 12 13 <TextView 14 android:id="@+id/chat_author_name" 15 android:layout_width="wrap_content" 16 android:layout_height="wrap_content" 17 android:layout_toRightOf="@id/chat_author_avatar" 18 android:paddingLeft="@dimen/narrow_space" 19 android:text="XXX"/> 20 21 <TextView 22 android:id="@+id/chat_datetime" 23 android:layout_alignParentRight="true" 24 android:layout_width="wrap_content" 25 android:layout_height="wrap_content" 26 android:paddingRight="@dimen/narrow_space" 27 android:textStyle="italic" 28 android:text="AAA"/> 29 30 <TextView 31 android:id="@+id/chat_text" 32 android:layout_toRightOf="@id/chat_author_name" 33 android:layout_below="@id/chat_datetime" 34 android:layout_width="match_parent" 35 android:layout_height="match_parent" 36 android:paddingLeft="@dimen/narrow_space" 37 android:paddingBottom="@dimen/chat_padding_bottom" 38 android:text="BBB"/> 39 40 </RelativeLayout>
【ListView的width-height不要使用wrap_content】

7.2 LayoutInspector工具的使用
【遗留问题】
【1】Layout Inspector —— Android Studio 替代 Hierarchy Viewer 的新方案
https://blog.csdn.net/ziwang_/article/details/66970591
浙公网安备 33010602011771号