上周四参加了MDCC大会的 Android,我比较关注的5R,做一个安静的app,图片缓存以及React Native For Android,其中很大一部分的内容都是讲的性能优化,后续还会给大家带来一篇React Native ,下面就来总结一下Android性能优化方面的内容!
人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这种流畅的帧率规定为60fps(1000/60 = 16.67ms/帧), 所以在16ms内没有把这一帧的任务完成,就会出现丢帧的情况,尽量保证每次在16ms内处理完所有的CPU与GPU计算、绘制、渲染等操作,否则会造成丢帧卡顿问题。
Reduce
- Cache/Drawable
- load in demand
- bitmap - scale/format
Reuse
- pools
- inBitmaps
- convertView
- onDraw / for
Recycle
- Inner class / container / static
- Context
- register/ungister
- bitmap/cursor/webview
Refactor
- arrayMap / no enum
- avoid memory fragment
- layout / overdraw
Revalue
- largeHeap
- multi - progress
- 3 - party libs
以上就是胡凯提出的5R原则,随意感受一下,下面主要是从布局优化,绘制优化,内存泄露优化,响应速度优化,ListView优化,Bitmap优化,线程优化等方面给出一些优化意见。
布局优化
布局优化的思想很简单,尽量减少布局的层级,布局层级减少绘制时间就会跟着减少,从而提高性能
如何进行布局优化呢?首先删除布局中的无用的控件和层级,其次有选择的使用性能较低的viewgroup,不如布局中即可以使用RelativeLayout也可以使用LinearLayout时,尽量选择LinearLayout,相对来说RelativeLayout比较复杂一下,需要使用cpu的时间相对长一些,当出现多级嵌套时建议使用RelativeLayout,降低程序的性能
布局优化的另一种方式就是使用 < include >(布局重用),< merge >(降低层级),< viewstub >(按需加载)
可以通过SDK提供的工具HierarchyViewer来进行UI布局复杂程度及冗余等分析,具体如何操作,打开体验一下就知道了。
绘制优化
绘制优化主要是避免view在onDraw方法中进行大量操作
- 不要在onDraw方法中创建局部对象,由于onDraw方法会频繁调用,就要分配给它很多内存,会导致gc(虚拟机在执行GC垃圾回收操作时所有线程(包括UI线程)都需要暂停,当GC垃圾回收完成之后所有线程才能够继续执行),降低程序的执行效率
- 不要在onDraw方法中执行耗时操作,也不要执行循环操作,占用cpu的时间过长,导致view绘制不流畅,耗时操作会引起ANR
- 背景和图片等内存分配优化;尽量减少不必要的背景设置,图片尽量压缩处理显示,尽量避免频繁内存抖动等问题出现。
绘制UI可以通过开发者选项中的GPU过度绘制工具来进行分析。在设置->开发者选项->调试GPU过度绘制(不同设备可能位置或者叫法不同)中打开调试
内存泄露
众所周知,在Java中有些对象的生命周期是有限的,当它们完成了特定的逻辑后将会被垃圾回收;但是,如果在对象的生命周期本来该被垃圾回收时这个对象还被别的对象所持有引用,那就会导致内存泄漏;这样的后果就是随着我们的应用被长时间使用,他所占用的内存越来越大。
内存泄露可以引发很多的问题,常见的内存泄露导致问题如下:
- 应用卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)
- 应用被从后台进程干为空进程(上面系统内存原理有介绍,也就是超过了阈值)
- 应用莫名的崩溃(上面应用内存原理有介绍,也就是超过了阈值OOM)
造成内存泄露泄露的最核心原理就是一个对象持有了超过自己生命周期以外的对象强引用导致该对象无法被正常垃圾回收;可以发现,应用内存泄露是个相当棘手重要的问题,我们必须重视。
内存泄露的优化方案有两个
- 开发过程中避免写出内存泄露的代码
- 使用工具检测内存泄露的方法,推荐简单粗暴的LeakCanary
ListView优化
- 采用ViewHolder并避免在getView中执行耗时操作
- 根据列表的滑动状态控制任务的执行频率
- 尝试开始硬件加速使ListView滑动过程更流畅
Bitmap优化
对于bitmap这个胖子操作它时,要特别注意一下,使用之前先判断大小,通过BItmapFactory.Options来采样,如果想当然的就是用了,oom就会找上门。
线程优化
线程优化的思想就是采用线程池,避免程序中出现大量的thread,线程池可以重用内部的线程,从而避免了线程的创建和销毁所带来的性能开销,同时线程池还能更有效控制线程池的最大并发数,避免大量的线程因互相抢占系统资源从而导致阻塞现象的发生。
性能优化建议
- 正确使用Context
- 使用Android提倡的方法,如 arrayMap
- 减少枚举类型的使用,占用内存相对来说比较大一些
- 合理使用largeHeap
- 慎重使用多进程,很多时候多进程缺点远大于优点
- 慎重使用第三方jar包
- 对象的注册与反注册没有成对出现造成的内存泄露;譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。
- 创建与关闭没有成对出现造成的泄露;譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。
因为Context的引用超过它本身的生命周期,会导致Context泄漏。所以尽量使用Application这种Context类型。 你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。
我们在应用中很多时候想当然的使用hashMap,其实Android 提供了它自己专用的arrayMap,SparseArray,Android提供这几个方法也是有原因的,其内部实现了压缩算法,减少存储控件,节约内存,还有就是查找速度更快,使用二分法查找
当你的程序申请更多内容时,虽说看上去是个不错的方法,但是你内存占用很高的情况下会被优先被杀死,所以合理使用largeHeap
除了webview选择单独的进程之外的慎重考虑使用多进程,即使这个进程什么都不做的情况下,也消耗不小的内存
第三方jar包,在不了解原理,内存占用的情况下慎用,里面可能存在很多你不了解的坑等着你去跳
对于内存优化这个话题,每个人都有自己的理解,欢迎批评指正,关于更多性能优化的问题请参考任玉刚的《Android 开发艺术探索》第15章
杏树林研发 郭莉莉