本文主要摘抄自google发布的《Android的性能优化》专题。原文链接:http://hukai.me/android-performance-patterns/#disqus_thread
带[]的后面会有详解。本人也处于学习阶段,如果哪里写的不对,希望大神们多多指点。
性能优化主要分三方面讨论:渲染机制、内存与GC、电量优化。
1. 渲染机制[1]
原因 | 工具 | 解决办法 |
---|---|---|
布局复杂 | HierarchyViewer(DDMS) | 减少不必要的布局 |
Overdraw(过度渲染) | Show GPU Overdraw(开发者选项) | OpenGL ES [2] /刷新部分控件[3] |
动画执行次数过多 | TraceView(DDMS) | 减少动画执行次数 |
2. 内存与GC
Android系统中有自动管理内存的机制[4],但是还是需要在平时写代码时严谨一些,避免造成OOM。
解决方法:
- 不要做你不必要的工作;
- 不要申请不必要的内存;
- 你明明知道一个方法返回一个String之后,你需要对这个String重新进行修改,那么就不要返回一个String,返回一个StringBuffer会是你更好的选择。
- 使用int比使用Integer占用更少的空间。这个大家肯定都是晓得的。
- 数组比一个Map拥有更好的性能。
- 如果你的方法不需要访问类字段,那么让你的方法是static的吧,这将会带来15%-20%速度的提升。
- 对于常量,请尽量使用static and final定义。如果使用final定义常量之后,会减少编译器在类生成时初始化方法[5]调用时对常量的存储,对于int型常量,将会直接使用其数值来进行替换,而对于String对象将会使用相对廉价的“string constant”指令来替换字段查找表。虽然这个方法并不完全对所有类型都有效,但是,将常量声明为static final绝对是一个好的做法。
- 避免Getters/Setters。虽然在一般的面向对象的设计模式中使用Getter和Setter是稀松平常的事情,但是在Android中使用getters/Setters是一个非常糟糕的主意,方法的调用相对于直接查找字段来说十分的昂贵。在没有JIT**[6]**的情况下,直接对字段进行访问要比通过Getter访问快了近3倍。在有JIT的情况下,前者比后者快近7倍。
- 使用最新的循环方式。比如增强for。
- 避免使用浮点类型。在某些可以的情况下,将浮点替换成整型数据,然后进行计算会得到更精确的结果和更快的速度。
- 小心使用Native Methods。
3.电量优化
- 我们应该尽量减少唤醒屏幕的次数与持续的时间,使用WakeLock来处理唤醒的问题,能够正确执行唤醒操作并根据设定及时关闭操作进入睡眠状态。
- 某些非必须马上执行的操作,例如上传歌曲,图片处理等,可以等到设备处于充电状态或者电量充足的时候才进行。
- 触发网络请求的操作,每次都会保持无线信号持续一段时间,我们可以把零散的网络请求打包进行一次操作,避免过多的无线信号引起的电量消耗。关于网络请求引起无线信号的电量消耗,还可以参考这里http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html
详解
1. 渲染机制
大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,动画就会流畅的运行。
- VSYNC:将硬件在一秒内刷新屏幕的次数与GPU在一秒内绘制操作的帧数相同步。
- 16ms:人眼与大脑之间的协作无法感知超过60fps的画面更新。所以每一帧的绘制需要1000ms/60fps=16ms/帧。
2. OpenGL ES
是OpenGL的子集,专门用于手机、游戏主机的三维图形API。
用法与ViewHolder相似,先将需要渲染的hold在GPU的内存中,然后在需要时取出。
3. 刷新部分控件
主要介绍两个方法:
- 通过canvas.clipRect()指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。
- 使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。
4. 自动管理内存的机制
Android系统中有一个Generational Heap Memory的模型,系统会根据内存中不同的内存数据类型分别执行不同的GC操作。分为三个区域:Young Generation、Old Generation、Permanent Generation。
最近刚分配的对象会放在Young Generation区域,在Young Generation区域中存放一定时间后,会被移动到Old Generation中,然后被移动到Permanent Generation中。
自动管理内存的机制:如果存放的对象的总大小超出当前区域的阈值,就会触发垃圾回收器。在垃圾回收器执行时,所有线程都会被暂停,所以,垃圾回收器的运行时间也要缩短。垃圾回收器的运行时间与要处理的对象数量有关,还与当前处于哪个区域有关,Young最短,Old其次,Permanent最长。
Memory Churn(内存抖动)时也会触发垃圾回收器。内存抖动是因为大量的对象被创建又在短时间内马上被释放。
5. clinit方法
在编译生成class文件时,会自动产生两个方法,一个是类的初始化方法, 另一个是实例的初始化方法
:在jvm第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行
:在实例创建出来的时候调用,包括调用new操作符;调用Class或java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。
6. JIT
在Java编程语言和环境中,即时编译器(JIT compiler,just-in-time compiler)是一个把Java的字节码(包括需要被解释的指令的程序)转换成可以直接发送给处理器的指令的程序。当你写好一个Java程序后,源语言的语句将由Java编译器编译成字节码,而不是编译成与某个特定的处理器硬件平台对应的指令代码(比如,Intel的Pentium微处理器或IBM的System/390处理器)。字节码是可以发送给任何平台并且能在那个平台上运行的独立于平台的代码。