Android应用性能优化——学习心得
Android应用性能优化这门课分为内存优化、视图优化、电量优化、Bitmap优化、其他优化等五大部分,下面这对这五大部分的学习能容做一下总结:
一、 内存优化
内存优化这部分内容,从以下五个方面进行了学习:
1. Android中的垃圾回收机制
学习了什么是垃圾回收;有什么缺点;Android的垃圾回收机制;Young Generation,Old Generation,Permanent Generation三个区存放的内容及管理机制,并以一个垃圾回收实例来学习了Android进行垃圾回收的过程。
2. 内存泄露的危害
应用程序分配了大量不能被回收的对象
系统可分配内存越来越少
新对象的创建需要的内存不够
GC之后再分配
60fps
3. 检测内存泄露的工具
Memory Monitor
使用Memory Monitor可以选定要监控的设备和监控的应用程序,图中蓝色部分表示我们使用的内存,灰色部分表示空闲的内存,图中的每个峰值代表进行了一次垃圾回收。每次垃圾回收之后,使用的内存都有一个峰值跌倒了一个较低的值。(先运行程序,然后点击“View”-“Tool Windows”-“Android Monitor”)
Allocation Tracker
内存抖动
内存抖动是因为短时间内大量的对象被创建又马上被释放。瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,会触发GC从而导致产生的对象又很快被回收,即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。
总结:
1) 定位代码中分配的对象的类型,大小,时间,线程,堆栈等信息
2) 定位内存抖动问题
3) 配合Heap Viewer一起定位内存泄露问题
4)使用复杂
Heap Viewer
可以实时的展示应用程序在运行时所有已分配的对象的数量大小以及类型信息。可以检测内存泄露的问题。
总结:
内存快照信息
每次GC之后收集一次信息
查找内存泄露利器
使用复杂
LeakCanary
1) 检测内存泄露的库(A memory leak detection library for Android and Java.)
2) https://github.com/square/leakcanary
3) 查找内存泄露的神器
常见的内存泄露问题
1)单例造成的泄露
2)非静态内部类的静态实例造成的泄露
3)Handler造成的内存泄露
4. 如何避免内存泄露
尽量不要静态变量引用Activity
使用WeakReference
使用静态内部类来代替内部类(静态内部类不会持有外部类的一个引用的)
静态内部类使用弱引用来饮用外部类
在声明周期结束的时候释放资源
5. 如何减少内存的使用
减少内存使用主要是为了避免前面讲到的内存抖动问题,因为应用程序内存使用降低了,垃圾回收收的频率也会低一些,这样的话产生内存抖动的概率也就会降低。从而使我们的运行程序运行更平缓一些。
使用更轻量的数据结构(比如SpareArray代替HashMap)
SpareArray比HashMap占用的内存小一些,
避免在onDraw方法中创建对象
onDraw方法调用的频率可能会非常高,如果我们在onDraw方法中,创建对象的话,会导致我们创建大量的临时对象,这样的话会很容易产生内存抖动现象。如果一定要在onDraw方法创建对象的话,建议使用对象池的方法。
对象池(Message.obtain())
对象池相当于一个对象缓冲,每一次并不是创建一个新的对象,而是从这段缓冲内存里面查找是否有可用的对象,如果没有的话才会创建一个新的对象出来,一个比较常见的例子就是:Handler中我们经常会使用Message.obtain()这个方法来获取一个Message对象,其实这里就是相当于在一个Message对象池中获取。
LRUCache
可以帮助我们大大的减少内存的使用,
Bitmap内存复用,压缩(inSampleSize,inBitmap)
StringBuilder来代替String ,尤其是我们在大量使用Sring拼接的时候,使用StringBuilder可以节省不少的内存,
二、 试图优化
1. 60fps
如果要让大脑感受到一个非常平滑的动画效果的话,至少需要24fps,24fps这个数字很神气,这也是我们在日常生活中所看到的电影所采用的帧率,电影领域大量使用24fps技术,一方面效果已经很不错了,另一方面可以很好的控制他们的成本。
当帧率达到60fps的时候,对我们的大脑来说是一个最流畅的效果,一旦超过这个数字的话大部分人的大脑是感受不到什么差别的,这就是Android中为什么使用60fps,因为只有达到60fps才能让用户感受到足够的流畅程度,我们的大脑对掉帧的现象非常敏感,什么是掉帧呢?本来正常的情况下是60fps,突然某个时刻掉到了30fps或者50fps,这个时候,我们的大脑会明显地感觉到有一个卡顿,这也就是我们在开发Android类的应用程序的时候对掉帧这么敏感的原因。因为掉帧会产生明显的卡顿现象,对用户体验非常不好。
想要打造一款性能卓越的APP,作为一个程序员我们必须了解视图渲染得方方面面,尤其适合硬件想过的过程。因为硬件决定了我们的视图是如何渲染的,只有了解硬件渲染视图的机制,我们才能找到一些视图渲染的方法以及排查我们在视图渲染中遇到的一些问题。
2. VSYNC
1) 刷新率:硬件的刷新频率,绝大部分屏幕的刷新频率为60HZ
2) 帧率:GPU一秒内绘制的帧数
3) 帧率和刷新率一起配合才能保证我们的应用程序完美流畅的渲染出来(视频中进行了举例说明)并模拟了当帧率>刷新率情况下,所产生的屏幕显示过程中的“撕裂”现象产生的过程和原因,以及为了解决这种问题,Andriod中引入了双缓冲概念。用实例分析了双缓冲机制。
在帧率<刷新率的情况下,例子中,屏幕的刷新率为60HZ,GPU的帧率为58HZ,有可能屏幕两次显示的图像是GPU渲染的同一帧数据,出现这种情况,会产生一种暂停或卡顿效果,
3.检测UI卡顿及过度绘制
过度绘制:以刷墙为例介绍了什么是过度绘制。过度绘制往往是影响应用程序UI性能的一个重要原因,不过值得庆幸的是Google为我们提供了一个非常方便的工具,可以方便的显示应用程序哪些部分存在过度绘制现象。
过度绘制往往是影响应用程序UI性能的一个重要原因,不过值得庆幸的是Google为我们提供了一个非常方便的工具,可以方便的显示应用程序哪些部分存在过度绘制现象。
1)打开查看过度绘制的工具,在开发者选项中,勾选Show GPU Overdraw选项就可以了,回到应用程序,我们的应用程序会被盖上一层各种颜色组成的蒙板。Show GPU Overdraw这个工具主要是通过各种颜色来通知我们当前区块的像素存在几次绘制现象。
2)GPU Profiler——(Google为我们提供的另一个一个非常方便的工具)
快读查看渲染时间,对比16ms,在开发者选项中,点击Profiler GPU rendering选项,弹出一窗口,选择On Screen As Bars Pick This,即时在屏幕上画出柱状图,下面一选项是:是到ADB Dump里面去,通常选择On Screen As Bars Pick This。
4. 避免UI卡顿
1)避免在onDraw中创建对象对象池
2)减少View层级
3)避免在UI顶层使用RelativeLayout、Measure两层
4)自定义空间控制绘制的复杂度
5. 优化过度绘制
其实绝大部分Android的UI相关的性能问题,都是由于过度绘制所引起的,我们只要掌握了一些优化过度绘制的方法,就可以解决遇到了绝大部分UI相关的性能问题,解决优化过度绘制主要有一下常用方法。
1) 降低View的层级,
View嵌套过多的话,就有可能产生过多的绘制现象,因为有可能你的每一层都需要绘制一遍,嵌套层级多自然绘制次数也多,降低层级的最常用的方法如下:
第一个方法就是:使用RelativeLayout代替LinearLayout,因为LinearLayout本身比较节能,提供的功能也比较少,如果一个布局,要用LinearLayout来实现的话,可能需要嵌套多层的LinearLayout才能达到一定效果,而使用RelativeLayout可能只需要一层就能达到需要的效果,因为RelativeLayout可以方便的指定各个绘制元素的位置,
第二个方法:使用Merge标签,因为使用Merge标签之后,不需要再为Merge进来的Layout复容器,如果不使用Merge标签,需要为这个Layout制定一个根容器,这个根容器在很多情况下是多余的,如果要去掉更容器的话,我们可以使用Merge标签。
2、去掉比必要的背景
去掉Window默认的背景:因为Android在运行的时候会默认在根容器上为我们的window添加一个默认的背景,其实很多时候因为我们本省的Layout的根容器的宽和高是和整个Window的宽和高是一致的,如果我们已经在根容器上设置了背景色,其实Window这个默认的背景色是可以去掉的,因为我们如果在根容器上设置了一个背景色,而Window本身又提供了一个背景色,这两个背景色的存在,就会导致两次绘制现象。
去掉不必要的背景:自己写的UI当中,如果每个子控件都提供了自己的背景色,那么很多时候我们就要考虑,是不是可以把这些控件所在容器的背景色去掉,因为既然每个子元素都提供了背景色,那么容器就不需要背景色了,这样就可以减少一次容器的绘制。
3、ClipRect&QuickReject
我们制作一些自定义控件的时候,我们可以通过优化onDraw方法来对我们的UI渲染进行优化,我们知道,Android系统在进行显示的时候,会对一些系统的控件进行优化,比如,如果我的一个控件被侧拉菜单完全挡住了,那么Android在进行渲染的时候,其实默认是不会渲染那些完全被侧拉菜单挡住的控件的,这样就可以避免一些不必要的绘制操作,当时对于自定义控件,因为Android系统并不知道我自定义控件的onDraw方法里是如何绘制这个控件的,所以没有办法对自定义控件作优化,这个时候就需要程序员自己手动来进行一些优化处理,从而避免一些过度绘制问题。这里最常用的一个办法就是canvas本身提供的ClipRect,这个方法主要用来指定我当前的绘制区域,也就是说除了我这个方法指定的绘制区域以外的部分是不会被渲染出来的
4)ViewStub(最大的优点是当你需要时才会加载,使用他并不会影响UI初始化时的性能。)
ViewStub:可以这样理解,可以把它当做占位符来使用,我们在开发的时候,非常常用的一个场景就是,一个空间一开始显示的时候可能并不需要把它显示出来,只有当服务端返回了数据或者满足某个场景的时候,我们才有吧这个空间展示出来,如果我们不使用ViewStub,而默认使用一些常用的控件来代替ViewStub,就需要我们把空间控件的display属性设置为“none”,即使display属性设置为“none”,这个控件在我们进行inflate(用一个XML源填充view. inflate(上下文对象,资源文件Id,父窗口组一般为null);)的时候,其实也会被inflate出来,并且它也肯能会参与到其中的一些运算当中去,会占用到我们绘制时的一些系统资源,如果用ViewStub来代替这些常用的占位控件的话,ViewStub本省在inflate的时候不会被inflate出来,它本身也不会参与merg和Layout的过程,所以可以帮助我们节省一些系统的运算资源,只有当我们的View真正显示出来的时候,ViewStub才会进行merg和Layout的过程。
5).9图作背景
我们在开发的时候,有一个非常常用的场景就是,在使用ImageView的时候,可能需要为它制定一个带圆角的边框,当作背景,同时,我在ImageView中还要显示我要的图片,如果背景不使用.9图,而是使用普通的图片,然后再普通图片上又盖了一层我们想要显示的图片,这样就会导致两次绘制,使用.9图可以优化掉一次绘制过程,比如我们在使用.9图的时候,让.9图只有边框部分是有颜色的,中间的部分我们可以使用全透明的像素进行填充,Android在进行绘制的时候,对于全透明的这部分像素,2D绘制的时候其实是会优化掉的,这部分全透明像素其实并不参与绘制过程,这样的话,我们的ImageView控件最终只被绘制一次,也就是边框和真正的图片总共绘制一次就可以了。这样就可以减少一次绘制过程
三、 电量优化
本节主要介绍Android中,哪些操作比较消耗电量,以及如何进行优化从而达到节约电量的目的,如今我们处在一个移动的时代,基本上我们所有的日常办公需求都可以在手机上来完成,手机正在成为大家必不可少的一个工具,然而,令人沮丧的是,移动化发展了这么多年,其实电池的续航能力并没有多大的提升,多少人应该都深有体会的就是我的手机需要两天、一天、甚至半天就需要充一次电,往往在最需要的时候,会发现我的手机的电量已经悄悄地跑没了,更令人发指的是,很多APP开发者并没有意识到电量消耗问题的严重性,他们开发的APP,即使不在全台运行,也有可能在后台偷偷的消耗掉大量的电量,从而导致用户手机的电池消耗非常的厉害。
1)25%-30%消耗用在核心功能上,
这些核心功能指的是,比如我在打开一个浏览器的时候,绘制图片所消耗的电量;我们看一个新闻APP的时候,展示新闻所消耗的电量,以及我的玩游戏的时候,游戏画面渲染以及动画所消耗的电量,这些真正核心功能消耗的电量只有25%-30%左右,
(画图、布局、动画)
2)剩下的75%左右,
上传统计数据:这一点的话,写过APP的同学应该都深有体会,我的统计数据基本上是一直运行的
1. Android电量消耗介绍(主要来自一下三个部分)
网络:尤其是手机上的无线网络,其实是非常消耗电量的,因为手机在连接基站的时候,在启动相关的无线网络模块的时候,其实是非常慢也是非常消耗电量的,并且Android为了做一些优化,在无限网络模块启动之后,会让这个模块持续运行一段时间,这样后续如果再有新的请求来的话,就不需要重新启动网络模块,也就是说,我的网络模块从启动到启动一段时间是一直在运行,消耗电量的;
WaleLock:就是阻止我的Android系统进入睡眠状态,本来如果我的系统进入睡眠状态,其实是不怎么消耗电量的,因为这个是我CPU今本上不运作了,但是开启了WakeLock之后,就会把系统从睡眠状态唤醒,唤醒之后,CPU开始运作,消耗电量,如果长时间的打开WakeLock,或者忘记关闭的话,就会导致电池的消耗一直居高不下,
非即时的任务:写代码的时候,大家可能没有注意,把一些非即时的任务当做即时的任务来处理,其实很多任务往往我们在开发的时候,是可以延后做一些批处理,统一执行的,而不是非要在某一时刻即时执行,如果能够把这些非即时任务,消化掉的话,比如把它们转移到批处理里面去,可能会大大的较少APP的电量消耗。
查看电量消耗情况的工具
1)Battery
平时我们在开发的时候,可以方便的在设置里面找到我们应用占用电量的一些相关信息,下图可以看到在Battery选项中可以看到一些信息,看到每个应用所消耗点亮的比例
2)Batter Hsitorian
如果这个图的信息不能够满足你的需求的话吗,Google另外还提供了一个工具,Batter Hsitorian,这个工具可以提供一个更加详细的电池消耗的数据
2. 网络优化
我们知道,我们的手机在和基站通信的时候,是通过手机内置的一些芯片模块来做这些事情的,手机会和基站进行大量的数据交换,所有的数据交换都是走这些芯片的,正常下这些芯片并不是一直在全负荷的工作去消耗大量的电量的,大部分情况下这些芯片是处于休眠状体,也就是一个低功耗的状态,是不怎么消耗系统电量的,一旦有数据发送请求的时候,就会唤醒我们的芯片,这个时候,芯片就会处于一个高频、高负荷的状态,它所消耗的电量也会急剧的增加,一旦数据发送完了,芯片并不会立即停止工作,进入休眠模式,而是会保持一段时间的唤醒模式,这相当于是系统做的一个优化,因为每次唤醒芯片的操作,其实是非常慢的,Android对这方面进行了一些优化,这样的话在一段时间内连续有网络请求任务的时候,就不需要再重新唤醒芯片模块,但是这样依旧会带来另外一个问题,就是这段时间内芯片会一直高强度的消耗电量。
网络优化,有一点要特别注意,要避免出现轮循服务器的情况,因为轮循服务器的话,会造成大量无用的网络请求,你轮循的时候,服务器不一定有数据返回给你,同时导致网络模块多次被唤起,从而导致耗电量急剧的增长,如果一定要有这种轮循请求的话,不要发起这种向服务端的轮循请求,而是Google提供的GCM推送的方式,因为,GCM本身Google进行了非常多的优化,在省电这方面,Google其实做了很多工作,当然由于我们国内国情的原因,你可能GCM没法使用,大家也可以尽量应用国内第三方的推送服务,这些推送服务,他们在底层本省也是对耗电量这方面做了很多优化工作,使用这种第三方的推送服务,也比我们自己去轮循会省好多电量。
数据压缩
数据压缩一个主要的好处就是,它可以大大的缩短网络请求的时间,从而达到省电的目的,当然,数据压缩带来的另外一个代价就是,客户端在解压数据的时候,可能会额外消耗更多的CPU,但绝大多是情况下,CPU在解压这些数据的时候所消耗的电量和时间,远比网络请求获取未压缩数据的时间小很多很所,因为网络请求本身是一个非常慢的过程,所以是一个非常耗电的过程,尤其是像一些文本相关的内容,采用GZIP压缩之后可以极大地减少它们的体积,这方面在省电的效果上是非常的明显的;另外,还有一些像图片相关的数据,可以采用一些体积比较小的格式来替换,比如,可以采用webp格式来代替jpg格式的图片,因为webp格式本身的话,在相同质量的图片上,体积会比jpg小很多,这样的话在网络请求的时候,就可以更快的返回,从而,减少电量消耗
3. WakeLock
WakeLock指的是:一场景为例,很所时候APP可能需要长时间的进行一个任务,即使系统已经处于休眠状态,我们也要把系统唤醒,来进行我们的任务,这个时候通常是由WakeLock这个API来做这个事情的,因为WakeLock可以把我们的系统唤醒,来执行我们的代码,但是在使用WakeLock的时候一定要非常小心,因为WakeLock一旦使用不当可能导致系统一直在运行状态中,从而导致电量极具下降。
4. jobscheduler
它满足的场景就是,我们可以指定几乎任意一个场景,在这个场景只要满足的情况下,JobSchedule就会快速的快速的执行我们想要执行的任务,使用JobSchedule的另外一个好处就是,因为我可能会有好多程序,这些程序它们可能都有一些调度任务,可能是在后台运行的,然后这些,在后台执行的任务可能他们会共享一些公公的条件,比如,某些任务可能都是期望插上电源或者连上WIFI之后执行,如果我这些任务都通过调用JobSchedule这个API来做的话,那么,当我满足其中一个条件之后,JobSchedule会唤醒我当前所有想要执行的这些任务,这样的话其实就可以避免系统被多次的唤醒,然后在系统一次唤醒之后可以做尽可能多的事情,这样的话其实也达到了一个电量优化的目的。
四、 Bitmap优化
1. Android中Bitmap的解码和存储
1)常见的jpg,png,webp是图像的存储格式
其中jpg采用的是有损压缩格式,当图片的色彩比较丰富的时候,jpg格式的压缩率会比较明显;png最初是为了替换取代gif这种图片格式,采用的无损压缩的方式存数图片数据的;webp是Google近几年来新推出的一种图片格式,主要是为了克服jpg和png的一些缺点,比如webp可以再采用无损压缩的情况下,极大的压缩图片的体积,也就是说如果使用webp,我们可以在获取高质量的图片的同时又能保证我们图片的大小在一个合理的范围上,现在比较推荐的就是webp这种格式,但webp有一个问题可能就是Android4.2之后才延伸内置了对webp的支持,在Android4.2以前的话大家需要一些第三方的图片库,比如 , 来显示图片,我们通常所说的jpg,png,webp这些都是图像的存储格式。
2)Android中要显示图片必须要先进行解码(decode)读取数据到内存中
这些格式的图像想要在Android中显示必须要经过先进行解码(decode)这个过程,所谓的解码(decode)其实就是将图像的数据读取到内存中,然后,把它转换成GPU能够识别的格式,再由GPU渲染到我们的显示器上,所谓的解码过程其实可以理解为,我们常见的存储格式本身是一种编码格式,既然有编码就会有对应的解码的过程,Android的解码其实是将存储在内存中的这些格式的图片解码成我们的系统所能识别的一种格式。
3)BitmapFactory提供了常用的一些decode方法
Android解码图片主要是使用了BitmapFactory类提供的一些方法,这个类中有很多好用的静态方法来帮我们,从各种数据源中去解码我们想要的图片数据,Bitmap所占用的内存大其实和我们图片文件的所占内存大小关系并不是很大,比如像jpg这样的图片,本身的压缩率是比较高的,在解码之后可能获得相对比较大的一个图片数据。
4)图片真正占用的内存大小要看decode之后的数据大小
通常在JAVA中一张图片,decode之后其实是以一个字节码的形式存在的,我们可以在内存dump出来的数据中清晰地看到,一个位图所占用的字节的大小。