android性能优化实战理论篇
本文地址:http://blog.csdn.net/iamws/article/details/51636175
第二篇:理论
通过之前前篇介绍的工具,我们知道了应该怎么样去获取要分析的数据,但是也仅仅局限在于怎么样获取数据,而没有深入数据分析,这一篇主要讲解的是UI刷新这块部分android理论知识,有了这些知识后,对于上面的数据该怎么分析,你就胸有成竹了。
ps:本文只是个人理解后的总结,并不会深入源码层次分析,如有错误,还请麻烦各位帮忙指正~
这篇文章要解决的理论问题如下:
1、什么是内存泄漏,内存溢出,这块对性能有什么影响以及android GC的种类
2、网上说的android UI刷新是60fps/16ms一次是什么样的一回事
3、android的整体绘制原理
4、view动画和属性动画区别
5、过度绘制会导致什么问题
6、硬件加速制的时候你是否对画布使用了lockcanvas()方法
7、哪些操作是耗费CPU的
8、其他性能优化注意点(想到后再加上)
如果对以上知识已经了解的话,那就麻烦跳过这篇,期待下一篇实战举例吧(┬_┬)
好了,开始
第一:什么是内存泄漏,内存溢出,这块对性能有什么影响以及android GC的种类?
(这块需要对GC有一些了解,了解了java GC的这块知识后下面就很好理解了)
由于android是个移动设备上的操作系统,没有了PC上的硬件支持,所以对于系统cpu,内存资源都需要额外关注,这才是为什么要很注意性能问题;
内存泄漏:简单讲就是之前创建了对象,也分配了内存给它,但是后来这个对象没用了,本来应该要交给系统去回收,但是对象却由于一直被GC ROOT上存在引用链导致无法回收,然后这部分内存就一直被占用了,如果每次操作都会生成这个对象的话,将会逐渐吞噬app的内存剩余空间,最终结果就是导致内存被吃完了,然后抛出溢出异常
内存溢出:常称OOM,out of memory,一般玩图片加载不注意的时候很容易碰到这个异常,这个其实是一个app在任何android手机运行的时候,都会是 linux给fork出虚拟机(例如:dalvik)进程跑的,而每个虚拟机创建的时候就会给他当前系统规定的可使用内存大小,如果你的app某个操作导致要使用的内存超过了这个分配值,而且GC后剩余的内存都没办法给你足够的空间去分配,那么这个异常就出来了
android GC种类:我们在android的logcat中输入GC关键字,可以看到GC基本上有以下这几类:
GC_FOR_MALLOC: 表示准备在堆上分配内存时内存不够触发的GC
GC_CONCURRENT: 表示是在当前内存达到一定设置量后导致触发的GC
GC_BEFORE_OOM: 表示是在准备抛OOM异常之前进行GC
GC_EXPLICIT: 表示是程序中主动调用System.gc、VMRuntime.gc接口或者收到信号时触发的主动GC
ps:android GC相关的知识推荐可以看看老罗写的博客,友情链接:http://blog.csdn.net/luoshengyang/article/details/41822747
可以看到上面除了主动代码或者信号调用以外,其它的GC都是在内存分配过程中调用的,而GC过程是需要涉及到CPU计算的,同时GC过程分为挂起或者与其他线程并行两种模式,这时候大家就明白为什么GC的时候会突然出现性能下降或者应用界面容易无响应了吧,这也证明了内存泄漏的危害,因为内存泄漏后就会导致可使用内存的降低,随着对象对内存的新申请,很容易就会触发频繁GC了,所以解决内存泄漏是解决性能优化的一个重要方法。
第二:网上说的android UI刷新是60fps/16ms一次是什么样的一回事?
我们可以看到基本上网上都有说android的UI是60fps的,也就换算成1000/60=16ms刷新一次,这种说法是怎么来的,而且这个是谁定义的呢?
答案当然是google,为什么google这样定义,这是因为做过android 2.x系列开发的人都知道,安卓当时的流畅度是完败给IOS的,流畅性被吐槽的特别多,google当时也发现这个问题很严重,于是在2012年的google i/o大会上,正式推出了4.1版本,同时也推出了黄油计划,这个黄油计划目标就是提升整体的流畅度,这里面就涉及到所谓的单缓存、双重缓存和三重缓存通道的机制区别,而且为了确保每次刷新的帧数相同,制定了vsync信号,这个信号就是通知系统的SurfaceFlinger要去执行渲染整个页面了
那么现在简单介绍一下这块缓存流程和部分技术:
什么是SurfaceFlinger:官方解释是:Every window that is created on the Android platform is backed by a surface. All of the visible surfaces rendered are composited onto the display by SurfaceFlinger.
就是说每个window背后都是surface,而所有的可见surface最后组成并要显示出来的时候,靠的就是SurfaceFlinger去渲染的,而SurfaceFlinger是一个system级的服务,依赖的是binder通信机制,由client去请求建立surface并最终完成渲染通信过程
所以android的渲染逻辑是:vsync信号是由系统每隔16ms发出一次,发出后就是通知SurfaceFlinger需要渲染了,渲染的数据哪里来呢?就是上面说的(单缓存、双重缓存和三重缓存技术通道里面存储的)存储的缓存队列,而缓存队列里面的数据怎么来的,就是CPU和GPU计算得出的页面绘制数据;
简单说下这几个缓存机制吧,单缓存:CPU/GPU绘制后的数据与屏幕显示读取的数据在同一个缓存队列中;但是由于计算频率和显示频率一般是不同的,这种缓存模式最容易导致的就是画面撕裂,也就是说看到的界面容易出现上一个帧和当前帧在同一个画面组成一张图,而这并不是我们想要的流畅度;
双重缓存:给了2个buffer队列,一个给CPU/GPU绘制后存放数据,一个给屏幕渲染的时候去读取数据,同时制定2个buffer之间的数据交换的规则,最终解决上面画面撕裂的问题;不过CPU和GPU共用同一个buffer,而我们知道,CPU和GPU如果在休眠,只有收到vsync信号才会开始处理下一个的数据,如果某次当vsync信号发出时,而且刚好又是一个耗时操作,CPU或者GPU还在处理上一个帧的计算,就会出现CPU或者GPU正占有着buffer,那么那个没有占有buffer的CPU/GPU也没办法处理了,所以就只好继续再休眠一个帧了,所以三重缓存就出来了
三重缓存:比上面的双重缓存还要多一个缓存,目地就是说硬件你们就各在个的缓存里面做事情,这样最大化利用各个硬件的计算频率优势,同时也规范制定好了之间数据的交换规则,这样,android的UI流畅度一下就上来了
顺便借用一下google io 2012的宣传资料里面的图,直观展示上面三者的区别和影响吧:
图2-1:单缓存下界面展示撕裂的原因,因为CPU/GPU频率高,Display频率低,当界面开始读取下一个数据时候,就会把途中0.015的图读出来,就撕裂啦
图2-1 单缓存实现图
图2-3双缓存实现图
图2-4 三缓存实现图
所以由上面的分析我们得知,如果想要你的应用顺畅展示界面,一需要确保你的每次渲染提交的计算过程能够在16ms内完成,二要减少没必要的高帧率计算,让你的渲染帧率保持跟系统刷新的频率,这样就能很大程度上释放cpu,同时保证流畅度,也就是性能优化的一个方法了。
此处可以参考文章:
官方渲染:http://source.android.com/devices/graphics/index.html
黄油计划:http://blog.csdn.net/innost/article/details/8272867
视频搜索:google发表的Android 21个性能优化典范教学
第三个:android的整体绘制原理
这块就纯粹是看android的view绘制源码了,网上应该也挺多的解释的,具体就是系统层的windowmanager怎么跟ViewRoot关联起来,然后再怎么从顶级DecorView到内部的view做measure,layout,draw等等,这里主要提的一点就是子布局的测量过程是需要依赖父布局的测量结果和子布局的属性一起才能决定最终子view测量结果的
为什么要单独列出来说这个呢,主要是考虑如果你的子布局一直有变化,将会带来整体所有上层父布局全都重走measure过程,而measure是CPU消耗的,下面是我demo的例子,testlinearlayout为父布局容器,嵌套两层,里面再动态插入子布局容器testtextview,布局树和日志如下:
看了图后你就明白,为什么一直说UI嵌套不能过深了,这块主要解决的就是UI布局嵌套过深,如果子布局变化过快带来的CPU计算损耗,是一个重要的性能优化点。
可以参考书籍:《深入理解android 卷II》《android开发艺术探索》里面有从framework层和java层两块详细分开介绍整个流程,这里就不多说了(主要太懒了(*^__^*) ,相关资料太多了)
第四个:view动画和属性动画的区别
这块想必大家一般都知道这些动画在应用层上最大的区别,view动画是假移动,view实际上还在原来位置,点击什么的事件也是在原位置监听,而属性动画是真移动,通过改变view的translation属性值,这样在新位置也能触发事件
不过这里要说的是他们的性能区别,先说总结的吧,优先使用属性动画去做,属性动画对性能优化提升最大
why?
//TODO 插入动画对比图1、2、3
过几天补上,做好的图不见了- - !!!
第五个:过度绘制会导致什么问题
什么叫做过度绘制?我们知道屏幕渲染最终依赖的都是手机硬件上的每一个小像素点的颜色的改变去实现的,而且由上面所知,目前android采用的是16ms的刷新机制,也就是说你看到的每一帧图像之间有16ms的间隔期,但是在这个间隔期内,你的计算可能对某些像素点指定了要做多次绘制,当系统最终渲染的时候等于也会对这个像素点按你的要求单帧内也绘制多次,导致有些时候实际上是做了很多无用功,例如指定了某个viewgroup背景颜色是白色,然后里面放入一个子view,但是你让这个子view背景色还是白色后,当系统开始渲染这一帧就会按你的意思,某个像素绘制一个白色,然后又绘制一次白色,这样是不是感觉后面的绘制实际上是多余而且没有必要的,所以说过度绘制的削减对于UI流畅度的提升非常重要。
所以过度绘制导致的问题是CPU/GPU做了很多无用功,界面渲染过程也浪费了比较多的绘制资源 ;
解决也很简单,1、减少背景设置重叠,2、减少不必要的view重叠,3、减少复杂布局实现(要和产品撕逼的一点,嘿嘿)等
下面随机找了2款线上APP的渲染展示图,都是登陆功能的,大家可以稍微看看:(只做技术知识研究分享展示用,对应APP的开发GG别打我)
第六个:想用硬件加速的时候你是否对画布使用了lockcanvas()方法
目前主流手机基本都有硬件加速功能了,而且硬件加速在渲染上对我们的CPU计算帮助很大,所以大家都想去用好它,我们很多时候在用surfaceview, surfacetextture去做一些复杂自定义view绘制的时候也特别希望用上它去帮忙加速,但是事与愿违,原来官方文档中写了一句话,导致我们以为是用了硬件加速帮忙,结果反而不是的,这句话就是官方文档中着重注意的:
翻译过来大概就是说如果获取画布采用lockcanvas()方法的时候,那么这个画布的绘制就不会是硬件加速了
这个优化点首先是要感谢工作中的同事,是他在优化过程中发现并提出的,同时证明我看文档的时候还是不太仔细,好像这句话我当时都忽略了,惭愧~
所以这块大家希望能让硬件加速帮上忙的话特别注意一下下,也算作一个优化注意点
第七个:哪些操作是耗费CPU的
这块只能说以往经常碰到的点提一下吧
1、布局计算
2、动画播放
3、图片加载
4、本地IO
5、反射机制
6、多重内部循环
7、线程
8、锁
9、某些api里面有的绘制计算
10、view的各种变形操作
11、等等等(想到再补)
是不是觉得跟java的很像啊,没错,基本就是一样的,java耗费的地方,android也一样,这块就不着重说了,大家注意实现某些逻辑的时候多考虑下是否有更好的实现方案或者更佳的api调用就ok了 ;
第八个:其它分析点
想到再补充,不过都是集中在前篇说的这几个问题里面:内存泄漏,布局层次过深,measure计算过多,动画绘制问题,io,自定义控件的draw里面对象创建,线程滥用,handler里面丢了个外部对象不清除,内部类与匿名类问题等;
下一篇将会开始结合实例数据代码分析,不过这里面要写很多对比demo,毕竟公司项目的代码不能拿出来分析的,但是demo都是根据项目中性能问题真实改编而来,这块预计需要花比较多的业余时间了,可能会分多个不同侧重点推出,敬请期待哈~
ps:这篇文章花了一个晚上去整理思绪后写的,本来留了很多补充的地方,不过要开始下一阶段工作了,而且这一系列的文章就是需要结合数据去实际分析优化性能的,重点在于实例上,所以就先推出这个版本吧,以后再考虑加上补充拉~
author:[email protected]
转载必须注明出处:本文地址:http://blog.csdn.net/iamws/article/details/51636175