性能优化
为什么要性能优化?
Android设备作为一种移动设备,CPU和内存往往受到一定的时限制。
- 过多的使用内存会导致内存溢出,即OOM(Out Of Memory)(Memory慢摸儿瑞)
- 过多的使用CPU会导致手机出现卡顿甚至出现无法响应的情况,即ANR(Application Not Responding)
一些有效的优化方法
- 布局优化
使用<include>、<merge>和<ViewStub>标签
- 绘制优化(再有自定义View的情况下)
在OnDraw()中一不要创建新的对象,二不要做耗时任务
- 内存泄露优化
养成良好的编程意识比如注意静态变量、单例模式和属性动画造成的内存泄漏
- 相应速度优化
避免ANR,比如不要再主线程里做超过5s的耗时任务,不要再onReceive里面做超过10s的任务,还要注意由于在主线程中等待长时间的同步锁而导致的ANR
- ListView优化
在Adapter中利用ViewHolder,在滑动时不要加载数据,开启硬件加速
- Bitmap优化
利用BitmapFactory.Options对象对图片进行压缩
- 线程优化
采用线程池从而避免大量的创建销毁线程,还能控制并发数避免阻塞现象
一丶布局优化
主要思想:布局中的层级少了,这就意味着Android的绘制工作少了,那么程序的性能自然就提高了。
要提取布局间的公共部分,通过提高布局的复用性从而减少测量 & 绘制时间
尽可能少用布局属性 wrap_content
布局属性 wrap_content 会增加布局测量时计算成本,应尽可能少用
1 选择 耗费性能较少的布局
- 性能耗费低的布局 = 功能简单 = FrameLayout、LinearLayout
- 性能耗费高的布局 = 功能复杂 = RelativeLayout(每一个控件都有一个Id,都会在R文件里创建对象,在UI界面复杂的时候可以用。)
- 注:
- 嵌套所耗费的性能 > 单个布局本身耗费的性能
- 即 完成需求时:宁选择 1个耗费性能高的布局,也不采用嵌套多个耗费性能低的布局
- 减少布局的层级(嵌套)
- 原理:布局层级少 ->> 绘制的工作量少 ->> 绘制速度快 ->> 性能提高
1.<include>标签(布局复用需用到的标签)
<include>标签可以将指定布局加载到当前的布局文件中,通过layout属性来设置指定的布局文件,<include>标签只支持带有android:layout_*这种属性。
2.<merge>标签(整体为merge)
<merge>标签主要要和<include>标签搭配使用,当<include>标签的最外层布局和所处的当前布局一样时,比如说都是垂直方向的LinearLayout,此时便可以将<include>标签的最外层布局的LinearLayout改成<merge>标签
3.<ViewStub>标签
<ViewStub>标签主要是一个轻量级的按需加载的布局控件,特点是像<include>标签一样包含一个布局文件,还有一个是可以在必要的时候显示和消失,所以ViewStub控件有利于显示一些网络加载或者异常时的界面。
绘制优化
绘制优化是指View的onDraw方法要避免大量的操作,主要体现在内存和CPU两个方面:
- onDraw中不要创建新的局部对象
因为onDraw可能会被频繁的调用而导致大量的对象被创建,这会导致占用过多的内存从而频繁地gc;(GC垃圾回收,执行GC时所有的线程操作都会暂停,等待GC的操作完成,大量不停的GC操作会占用帧间隔时间的16ms,在这16ms时间里做了长时间的GC操作,从而使其他操作时间变短)
- onDraw中不要进行耗时的操作
频繁的耗时操作会占用CPU的时间大量的时间,导致程序卡顿。
1.3 内存泄漏优化
内存泄漏:程序在销毁时,还有一些引用没有被释放(或者说GC没有回收),导致这些对象占用的内存无法被使用(无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。)
影响
应用卡顿:泄漏的内存影响了GC的内存分配,过多的内存泄漏会影响应用的执行率
GC回收的原则:被全局变量(static)、栈变量和寄存器等直接引用和间接引用的对象不能被回收。
常见的内存泄漏:
1:静态变量导致的内存泄漏(静态变量伴随着整个App的存亡。)
这个静态变量mContext引用这这个Activity,
2.单利模式,泄漏的原因是OrmHelper会一直持有context对象
3.属性动画导致内存泄漏 (在android3.0 Google 提供了属性动画),动画播放完之后, 尽管我们看不到动画的播放效果,但在内部处于无限循环播放的状态
解决办法:解决方法就是在onDestory或动画结束监听中调用animator.cancle()
4.集合对象没有清理造成内存泄漏,我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。
5.资源对象没有手动关闭或处理,资源性对象(BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap)
6.强引用数据,new 出对象的时候,对象被引用的时候称之为强引用,当不在引用的时候就不再叫强引用,GC可以对其进行一个回收。
匿名内部类泄漏总结
你不知道这个匿名内部类会不会造成内存泄漏,那么你把它改为静态的,在onstop/ondestroy 置为null,
总结
开发中尽量避免写出有内存泄漏的代码,但并不是每个内存泄漏都需要解决,我们只是尽量的让内存泄漏减少,来优化app内存。
内存泄漏的优化分为两个方面,一方面实在开发过程中避免写出泄漏的代码,另一方面则是通过一些分析工具MAT,LeakCanary来找出潜在的内存泄漏继而解决。
内存泄漏分析工具(前提是你得有一个Eclipse)
下载网址 :http://www.eclipse.org/mat/downloads.php;
安装教程:https://blog.csdn.net/xorxos/article/details/50207869
LeakCanary:
导依赖:
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
如果以上三个依赖不行,试一下下面的
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
记得在
<application
android:name=".AppApplication">
运行App,第一次运行,没问题,退出App开始第二次运行。如果有内存泄漏会在你手机进行推送(这个需要时间去等待GC的回收)
那么怎么去决绝被泄漏的对象呢 ,请看下面网址
https://www.jianshu.com/p/3f1a1cc1e964
主要关注的就俩种:
堆内存泄漏(Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.
系统资源泄露(Resource Leak).主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
内存溢出:
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
响应速度优化(说白了就是让你的界面变得流畅)
不要去频繁大量的new对象,
FPS 表示每秒传递的帧数。在理想情况下,60 FPS 就感觉不到卡,这意味着每个绘制时长应该在16 ms 以内。但是 Android 系统很有可能无法及时完成那些复杂的页面渲染操作。Android 系统每隔 16ms 发出 VSYNC 信号,触发对 UI 进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需的 60FPS。如果某个操作花费的时间是 24ms ,系统在得到 VSYNC 信号时就无法正常进行正常渲染,这样就发生了丢帧现象。那么用户在 32ms 内看到的会是同一帧画面,这种现象在执行动画或滑动列表比较常见,还有可能是你的 Layout 太过复杂,层叠太多的绘制单元,无法在 16ms 完成渲染,最终引起刷新不及时。
响应速度优化的核心思想是避免在主线程做耗时的操作,将耗时的操作放在线程里执行。同时还要注意另一种不是很明显的造成ANR的原因:在主线程中长时间等待同步锁
ListView
- 采用ViewHolder进行缓存并且避免在getView中执行耗时操作
Bitmap优化(图片压缩,内存占用计算=宽×高×色彩空间)
(采样率压缩,质量压缩,缩放法压缩(martix))
为什么要有图片压缩
android中图片是以bitmap形式存在的,那么bitmap所占内存,直接影响到了应用所占内存大小。android中图片是以bitmap形式存在的,那么bitmap所占内存,直接影响到了应用所占内存大小。
以下是图片的压缩格式:(图片质量)
ALPHA_8 ARGB_4444 ARGB_8888 RGB_565
其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。
ALPHA_8
表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度
ARGB_4444
表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节
ARGB_8888
表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节
RGB_565
表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节
----------------------------------------------------------------------------------------------------------------------------
Bitmap优化主要是通过BitmapFactory的Options对象进行的方法:
利用Options先计算出采样率inSampleSize并返回给Options,最后通过改变的Options对图片进行压缩
在解析Bitmap时,有个可选的Options对象,其中inSampleSize参数可以影响缩放比的结果。当使用该参数值时要求大于1且是2的倍数,比如在inSampleSize=2时,缩放比被缩小2倍(该功能只有缩小没有放大的可能),即“缩放比=原始缩放比×(1/2)”。对内存结果的影响是缩小4倍,因为宽/高都被缩小2倍。该值默认不生效,需要手动设置。
当options.inJustDecodeBounds 的值是true,只是读取图片的边,而不是读取整个图片,
所以Bitmap bitmap = BitmapFactory.decodeFile(imagePath,options);得到的bitmap是空的。
Bitmap 网址:https://www.jianshu.com/p/f08f8b56e299
线程优化
线程优化主要体现在避免大量的创建和销毁线程,因此线程优化的思想就是使用线程池。使用线程池的好处就是,可以重用线程避免创建和销毁大量线程,还可以控制并发数以避免阻塞。线程池的基本作用就是进行线程的复用。(线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失
2、大量的线程创建、执行和销毁是非常耗cpu和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM
3、大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿)
使用线程池管理线程的优点
1、线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销
2、线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量
3、在执行大量异步任务时提高了性能
4、Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等
一些性能优化建议
- 避免创建过多的对象
- 不要过多使用枚举
- 常量使用static final修饰
- 使用Android特有的数据结构比如SparseArray和Pair等
- 适当使用软引用和弱引用
- 采用内存缓存LreCache和磁盘缓存DiskLruCache
- 尽量采用静态内部类
关于优化的还有很多:
https://www.jianshu.com/p/c55ef05c0047