《Android性能优化》下载地址:计算机书籍控
如果下载链接失效或者不完整,可百度搜索下载
目录
作者说:优化像是一门艺术
代码优化不是应用开发的首要任务,提供良好的用户体验并专注于代码的可维护性,这才是首要任务。代码优化应该最后才做,甚至完全可能不用做。不过,良好的优化可以使程序性能直接打到一个可接受的水平,因而也就无需再重新审视代码中的缺陷并耗费更多的精力去解决它们。
如果不知道代码的使用方式,就不能正确地优化代码,在找到要解决问题的关键点之前不要优化。
1.1 Android如何执行代码
Android开发者使用JAVA,不过Andriod平台不用JAVA虚拟机(JVM)来执行代码,而是把应用编译成Dalvik字节码,使用Dalvik虚拟机来执行。JAVA代码仍然编译成JAVA字节码,但随后JAVA字节码会被dex编译器(dx,SDK工具)编译成Dalvik字节码。最终,应用只包含Dalvik字节码,而不是JAVA字节码。Dalvik的编译器可以将字节码编译为本地代码,由设备处理器直接执行。
1.2 算法的优化
简单递归—>减少递归次数—>迭代—>减少迭代次数—>BigInteger解决溢出—>混合BigInteger和基本类型—>缓存
缓存思想:
LruCache 先丢弃最近最少使用的项目
1.3 API等级
1.4 数据结构
好的算法和数据结构是实现代码高效的关键。比如:当HashMap(键为对象)键为Integer时,可以用SparseArray(键为整数)代替,减少对象创建,优化内存。
1.5 SQLite优化
1.5.1 SQLite语句
1)compileStatement、populateWithCompileStatement、populateWithCompileStatementOneTransaction减少SQLite语句的编译次数
如下,在循环外只编译一次:
2)使用API(InsertHelper插入多行、ContentValues,它们类似populateWithCompileStatement)减少SQLite语句的编译次数
1.5.2 事物
如果不显式创建任何事务,那么SQLite会为每个增删改查操作创建一个事务,并在每次操作后立即提交。显式创建事务有2个基本特性:
1)原子提交
原子提交意味着数据库的所有修改都完成或都不做,事务不会提交部分修改。
2)性能更好
1.5.3 查询
限制数据库访问的方式(减少访问时间)来加快查询速度,如减少列的查询
1.6 代码检测
检测应用中执行缓慢的代码或潜在缓慢的代码
字节码需要由虚拟机解释,而本地代码可以由设备处理器直接执行,没有任何中间步骤,执行速度更快,有时快很多。Dalvik的编译器可以将字节码编译为本地代码,减少应用字节码的解释次数(理想情况下仅有一次)。使用NDK时,是在开发环境中将代码编译为本地代码,而不是在Android设备上,直接使用自己生成的本地代码,可以让应用运行得更快。
2.1 System.loadLibrary
.
在静态初始化块内调用System.loadLibrary加载本地库,是加载库的最简单的方法。这种块中的代码是在虚拟机加载类时执行的,静态初始化块可能会显著增加开销。
.
Dalvik的编译器可以显著提高性能,本地实现并不总是比启用Dalvik的编译器的JAVA实现快
.2.2 字符串处理函数
JNI提供了多种方式来处理字符串,它们都用相同的方式完成大量工作:
JAVA字符串必须被转换成C/C++字符串;
C/C++字符串必须释放
应该在代码中尽可能使用GetStringRegion、GetString-UTFRegion,好处如下:
避免可能的内存分配;
复制String要用的一部分到预先分配的缓冲区(可能在栈上);
不需要释放字符串,避免了忘记释放字符串的风险。
..2.3 反射调用JAVA函数
出于性能方面的考量,不想每次访问域或调用方法时都去获取一次域或方法的ID。
虚拟机加载类时域和方法的ID就被设置,只要类被加载,改ID就有效。如果类由虚拟机卸载并再次加载,新的ID可能与旧的不同。也就是说,高效的方式是在类被加载时获取ID,即在静态初始化块中获取。
..2.4 内置函数(内联函数)
内置函数(内联函数),是由编译器内部进行特别处理的函数。内置函数常常用来构造某些语言支持不了的特性,编译器会替换掉这个函数调用,把它展开为一系列针对目标平台处理并进行典型优化的指令。因为函数调用的操作代价比较高,内联函数(即直接在屌用处实现,替换调用)可以使代码运行得更快。把函数变为内联非常简单,只要在函数定义前加上“inline”关键字即可。
.2.5 循环展开
.2.6 内存预读取
如果对要访问的具体数据和要执行的指令有一定了解,可以针对这些数据和指令,在使用前进行预加载(或预取)。
把数据从外部存储器中移动到缓存要花时间,提前给予充分的时间,把数据从外部存储器中的数据转移到缓存中,可以带来更好地性能,因为这样可以提高访问的数据或指令的缓存命中率。
可以使用GCC的__builtin_preretch()预加载数据。
要小心使用预加载内存,在某些情况下,它会降低性能。缓存总数是固定的,有多少进来就得有多少出去,确保预加载的是必需的代码,否则会让无用数据污染缓存,降低性能。
使用内置函数预加载内存:
性能主要取决于以下三个因素:
.3.1 数据类型
使用较小的类型并不总会提高性能,实际上可能需要更多的指令。
只用指令数量作为判断代码快慢的依据是不充分的。因为并不是所有指令的执行时间都相同,当今的CPU很复杂,不能仅靠计算指令数目推测某个操作要花费多少时间。
short数组排序远快于其他类型数组;
.3.2排布数据
避免频繁创建对象,避免get方法的开销
.3.3 内存泄露
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
.3.4 垃圾收集
垃圾收集可能会在不定的时间出发,你几乎无法控制它发生的时机。有时,你可以通过System.gc()提醒一下Android,但垃圾收集的发生时间最终由Dalvik虚拟机决定。有一下5种情况会触发垃圾收集:
垃圾收集要花费时间,减少分配/释放对象的数量可以提高性能。
.3.5 API
内存少的时候:
.4.1 线程优先级
..4.2 多核并发缓存
..4.3 线程和Activity生命周期同步
代码中创建的线程不会自动获知Activity生命周期中的变化。例如,调用Activity的onStop()方法使得Activity不可见,或者调用Activity的onDestroy()方法都不会自动给你创建的线程发通知。这意味着需要做额外的工作让自己的线程与应用的生命周期保持同步。
为了确定是否需要优化,优化后是否有提升,针对性能进行度量是非常必要的。
在大多数情况下,性能使用函数完成操作所花费的时间作为测量依据。例如,游戏的性能往往用每秒多少帧进行度量,这个直接由渲染帧所需要的时间决定:为达到每秒60帧的更定速率,每帧渲染和显示的时间应小于16.67毫秒显示。小于100毫秒的响应时间才会让人又瞬时的感觉。
.5.1 时间测量
..5.2 方法调用跟踪
5.2.1 startMethodTracing()
5.2.2 TraceView工具
5.2.3 本地方法跟踪
..6.1 禁用和启用广播接收器
如果应用尚未启动,广播接收器收到通知后,应用会启动。可以这样解决这个问题:
..6.2 网络
6.2.1 禁止后台传输数据
6.2.2 通知用户打开WIFI,漫游关闭流量
6.2.3 数据传输
如果能控制数据的传输类型,就可以先压缩数据,再传输到设备上。虽然压缩数据耗费CPU,也多了些电量,但传输速度大大加快,从而延长了电池寿命。
- 使用GZIP压缩文本数据,使用GZIPInputStream类访问数据;
- 如果可能的话,使用JPEG而不是PNG格式的图像文件;
- 使用匹配设备分辨率的资源(比如,不必为96x54大小的显示空间下载1920x1080的图片)。
..6.3 位置
6.3.1 及时注销位置监听器
6.3.2 避免频繁接收位置更新
6.3.3 提供多种位置服务
GPS带来高精度的同时,在时间和电量上的耗费也高。
一个好的权衡 策略是注册网络定位服务,它比GPS省电,同时注册被动定位,以便可以从GPS得到更准确的位置信息。
6.3.4 Criteria找出最优的定位服务
6.3.5 获取最后已知位置
即使注册了位置监听器,应用通常也要首先检索最后的已知位置,以增强响应性,因为通常得过几秒钟才能收到位置更新。
..6.4 传感器
..6.5 图形渲染
优化图形渲染程序,降低功耗
..6.6 提醒
调度提醒:
WakeLock
防止出现问题
GitHub
关注专题Android开发常用开源库
简书
微信公众号
QQ群