随着移动应用在中国6、7年的发展,手机应用的开发已经很完善了,如果boss让你开发一个应用,你会发现你要开发的功能在公司里面基本都能找到类似的实现代码,就算在公司找不到,在网上也有大把的demo,这样程序开发的门槛就降低了,无非是代码的解读与拼凑(仅在实现功能的角度来说),所以为了成为一个优秀的程序员,大家就不能仅满足于实现功能,更重要的是写出优秀的程序。在这篇文章里,我会结合过去的开发经验—特别是图库的开发,谈谈对于Android性能优化的个人见解。
一、 概述
概括来说,我认为Android应用性能优化包括如下几方面:
内存 、线程、流程逻辑、数据结构、逻辑算法、系统特性、编程语言
1. 内存优化
开发过程中内存优化涉及到如下区域:
寄存器(Register):速度最快,位于服务器内部,一般我们无法控制
栈(Stack) :存放基本数据类型与指向对象的指针的地方
堆(Heap):存放对象的地方,受GC管理
Android 应用有内存大小限制,超过了会引发oom,具体的限制值可以在/system/build.prop中查看:
-dalvik.vm.heapstartsize 表示默认分配的内存
-dalvik.vm.heapgrowthlimit 表示默认情况下内存的最大允许值
-dalvik.vm.heapsize当我们在AndroidManifest.xml中设了largHeap = true时,内存的最大允许值。
当然我们在开发的时候也可以通过Runtime.getRuntime().maxMemory();取得当前最大允许的内存值。
5R:
内存的优化工作也可以从以下5个方面思考。
1.Reckon(计算)
首先需要知道你的app所消耗内存的情况,知己知彼才能百战不殆
2.Reduce(减少)
消耗更少的资源
3.Reuse(重用)
当第一次使用完以后,尽量给其他的使用
5.Recycle(回收)
返回资源给生产流
4.Review(检查)
回顾检查你的程序,看看设计或代码有什么不合理的地方
1.
1.1. 充分使用缓存机制
我们的应用并不是使用内存越少越好,相反充分的使用内存,能有效的提高效率,chrome浏览器占用内存明显比较高,一定程度上也可以看做内存换效率。
以从网上加载一张图片为例:
其中涉及到图片下载、图片保存、图片解码、内存管理、图片信息管理。
自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,这使得上述的方案相当无效。幸好Google给了我们Lrucache,它将被引用的对象保存在LinkedHashMap中,并且当缓存超过指定大小后,释放最不常使用对象。
关于图片缓存,我们有很多开源项目:
Glide,(首选)Android-Universal-Image-Loader 图片缓存、picasso square开源的图片缓存、ImageCache图片缓存,包含内存和Sdcard缓存。
1.2. 尽量不使用全局变量
首先这里不包括static final的基本数据类型。
处女座的老板可能会要求你完全消灭全局变量。全局变量不单会一直占据内存资源,关键是内存不足的情况下,系统会回收一部分资源,由于APP切换到后台,所以之前的全局变量可能被回收,这样应用面临一个很不确定的风险。
要解决这个问题,建议:
1) 把全局变量序列化之后保存到本地。
2) 把变量放到Application中声明,除非应用退出,否则Application不会被回收,作为Application的成员变量也就不会被回收,同时,在应用内部Application的成员变量的作用域也类似于全局变量了。
1.3. 提防内存泄漏,比如context被长于Activity的声明周期的对象引用,bitmap要recycle,数据流要close,数据库要close,广播要unregister
1.4. 按需加载图片
1) 通过设置BitmapFactoryOption.sampleSize 修改加载的图片大小而不是加载了图片之后再缩放。
2) 设置图片像素的质量。分别是:
ALPHA_8:每个像素占用1byte内存 (只有透明度)
ARGB_4444:每个像素占用2byte内存 (有透明度但没那么细腻)
ARGB_8888:每个像素占用4byte内存(默认)
RGB_565:每个像素占用2byte内存 (没有透明属性)
3) 通过计算当前应用允许的最大占用内存值,决定缓存的大小(可以在LruCache中设定),一般是最大值的1/8,按应用实际需求决定。
1.5 inBitmap
BitmapFactory.Option.inBitmap
如果设置了这个字段,bitmap在加载数据时可以复用这个字段所指向的bitmap的内存空间。
新增的这种内存复用的特性,可以优化掉因旧bitmap内存释放和新bitmap内存申请所带来的性能损耗。
一般把inBitmap指定为可回收的bitmap,已达到复用这个bitmap的空间的目的
需要注意的是inBitmap只能在3.0以后使用。在4.4之前,只能重用相同大小的bitmap的内存区域,而4.4之后你可以重用任何bitmap的内存区域,只要这块内存比将要分配内存的bitmap大就可以
1.6 使用NDK
由于JVM中java内存堆和Native空间的内存堆是独立的,我们一般在java空间开发比较多,内存空间比较吃紧,可以把部分逻辑放到native空间。
2. 线程优化
一般的应用都会涉及到多线程吧,特别是耗时逻辑放在主线程容易引起anr,很影响用户体验(为了让屏幕的刷新帧率达到 60fps,我们需要确保 16ms 内完成单次刷新的操作,一旦刷新帧率降到 20fps 左右,用户就可以明显感知到卡顿不流畅了)。我们应该关注的怎样合理使用、管理多线程。
1) 选择适当的工具
AyncTask AsyncTask 提供了一种简单便捷的异步机制,适合简单任务
HandlerThread HandlerThread 比较合适处理那些在工作线程执行,需要花费时间偏长的任务
IntentService IntentService 就不仅仅具备了异步线程的特性,还同时保留了 Service 不受主页面生命周期影响的特点
2) 关注线程的并发数
通常核心线程数设为CPU数量+1
最大线程数设为CPU数量*2+1
获取CPU数量的方法Runtime.getRuntime().availableProcesses():获取活动的cpu的数量
sys/devices/system/cpu 获得真实cpu的核数
3) 关注线程的优先级
对于Android平台上的线程优先级设置来说可以处理很多并发线程的阻塞问题,比如很多无关紧要的线程会占用大量的CPU时间,虽然通过了MultiThread来解决慢速I/O但是合理分配优先级对于并发编程来说十分重要。
我们可以通过Process设置线程优先级:比如
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
创建ThreadPoolExecutor时可以传入new PriorityThreadFactory("thread-pool",android.os.Process.THREAD_PRIORITY_BACKGROUND)
具体Thread的优先级包括:
Int THREAD_PRIORITY_AUDIO //标准音乐播放使用的线程优先级
int THREAD_PRIORITY_BACKGROUND //标准后台程序
int THREAD_PRIORITY_DEFAULT // 默认应用的优先级
int THREAD_PRIORITY_DISPLAY //标准显示系统优先级,主要是改善UI的刷新
int THREAD_PRIORITY_FOREGROUND //标准前台线程优先级
int THREAD_PRIORITY_LESS_FAVORABLE //低于favorable
int THREAD_PRIORITY_LOWEST //有效的线程最低的优先级
int THREAD_PRIORITY_MORE_FAVORABLE //高于favorable
int THREAD_PRIORITY_URGENT_AUDIO //标准较重要音频播放优先级
intTHREAD_PRIORITY_URGENT_DISPLAY //标准较重要显示优先级,对于输入事件同样适用。
推荐使用RxJava 2.x,内部使用了线程池,并能灵活切换线程、配置线程优先级。
PS:
经典组合RxJava(多线程流式调用)+Retrofit(基于注解的请求框架)+OkHttp(高效网络框架)能解决大多数app需求
3. 流程逻辑
流程逻辑的优化比较宽泛、灵活。
举个例子:
一个图片社交软件,最基础也是最基本的图片展示功能,当然可以用简单的方式从后台拉到图片就展示出来,但为了更好的网络体验,可以大概如此从流程上作优化:
如上图,实际上是把一个简单的事情精细化、复杂化,基本的原理是从流程逻辑上左优化,从而为性能/用户体现服务。
4. 数据结构
一方面是了解已有的工具类的内部原理,一方面是自定义类要注意性能。
比如:
- 尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销
- 尽量用SpareArray代替HashMap
- 多用System.arraycopy、String.indexOf()、String.lastIndexOf()等使用c底层实现的方法,代替自己写循环
- 对于频繁I/O,推荐使用高性能的MemoryFile类
5. 逻辑算法
一般的Android App处理的数据量是比较少的,所以对算法优化的需求比较少,但我们也不能完全排除算法优化上的需求。比如从后台来回来的数据量特别多,统计处理的本地Log特别多,我们就要考虑到算法优劣的问题。
6. 系统特性
- 某些配置,会对我们的app性能有影响。比如:
android:hardwareAccelerated 可以在Application、Activity、Window、View四个级别进行硬件加速控制,硬件加速执行的所有的绘图操作都是使用GPU在View对象的画布上来进行的,但需注意它会使app占用更多的内存资源,以及对自定义的view和drawable未必能完全支持。
android:largeHeap 可以让App可使用的内存堆更大,app的确需要更多内存的时候可以配置,比如图库。
- layout布局性能对比:
ConstraintLayout>RelativeLayout>LinearLayout
一般来说ConstraintLayout的性能是最好的,这主要是得益与它使用了Cassowary算法。
7. 编程语言
Android App编程中可能会用到Java、C/C++(Dart暂且不说)。由于C/C++可以直接操作内存空间,所以效率是比Java高,Google也为我们提供了调用C/C++的方式:JNI,但需要注意,使用jni本身就有一点的资源消耗,如果不是对性能有要求的操作,使用jni可能会让你得不偿失。