Android 性能优化工具集合

时间:2021-03-04 03:48:15

摘录自: 《Android移动性能实战——腾讯SNG专项测试团队编著》,这是一本很棒的书,将Android的性能优化写的淋漓尽致。

前言

  这本书是我接触过的第一本的Android性能优化的书。这本书用各种各样的性能检测的工具,将腾讯的作品的优化过程一点点记录下来,启发式的将性能优化这一门学问融入到了一个个单独的案例中,深入浅出的将这一门博大精深的学问展示在了读者的面前。对于一个读者而言,这本书是一本带有故事性的技术书、工具书。

磁盘

  1. 文件读写:每次打开、关闭或者读写文件,操作系统都需要经过从用户态转换为内核态的切换,这种状态的切换本身是很消耗性能的,所以为了提高文件的读写效率,就需要尽量减少用户态和内核态的切换。使用缓存可以避免重复读写,对于需要多次访问的数据,在第一次取出数据的时候,将数据放在缓存中,下次再访问的时候,就可以从缓存中取出来。
  2. Android手机里面的libsqlite.so调用系统的pread64pwrite64函数进行I/O操作。
  3. 序列化ObjectOutputStream序列化磁盘的时候,会把内存中的每个对象保存到磁盘,在保存对象的时候,每一个数据成员会带来一次I/O操作。因此,在使用ObjectOutputStream的时候,在ObjectOutputStream上面再封装一个输出流ByteArrayOuputStream先将对象序列化后的信息写到缓存区中,然后再一次性地写到磁盘上。

    序列化磁盘读写方式 优化前 优化后
    序列化写磁盘 ObjectOutputStream BufferedOutputStream + ObjectOutputStream
    ByteArrayOutputStream + ObjectOutputStream
    序列化读磁盘 ObjectInputStream BufferedInputStream + ObjectInputStream
    ByteArrayInputStream + ObjectInputStream
    序列化磁盘读写方式 优化前后 I/O次数 耗时(ms)
    序列化写磁盘 优化前 499 162.8
    优化后 1 87.3
    序列化读磁盘 优化前 719 171.8
    优化后 1 109.9
  4. Buffer缓冲区大小:在读/写时使用缓冲区可以减少读写次数,从而减少了切换内核态的次数,提高读/写效率,根据实际经验,这里推荐使用的Buffer大小为8KB,这和Java默认的Buffer大小一致,Buffer大小至少应为4KB。当然,Buffer也不是越大越好,Buffer如果太大,会导致申请Buffer的时间边长,反而整体效率不高。可以采用文件大小除以读写次数得到Buffer的大小。这个方法由两个影响因子决定,一是Buffer size不能大于文件大小;二是Buffer size根据文件保存所挂载的目录的Block size来确定Buffer大小,而数据库的pagesize,就是这样确定的。
  5. SQLite:数据库在打开后,先不要关闭,在应用程序退出时再关闭。
  6. SQLite:在数据库中,应减少使用AUTOINCREMENTAUTOINCREMENT可以保证主键的严格递增,但是使用AUTOINCREMENT会增加INSERT耗时1倍以上,所以使用AUTOINCREMENT不可以任性,用在该用的敌方效果才佳。

    SQLite官方:这个AUTOINCREMENT关键字会增加CPU,内存,磁盘空间和磁盘I/O的负担,所以尽量不要用,除非必须。其实通常情况下都不是必需的。

  7. Bitmap解码
    (1)解码Bitmap不要使用decodeFile,因为在Android4.4以上系统效率不高。
    (2)解码Bitmap使用decodeStream,同时传入的文件流BufferedInputStream
    (3)decodeResource同样存在性能问题,请用decodeResourceStream
    解决方案:
    1. 解码Bitmap要使用decodeStream,不要说使用decodeFile,同时传给decodeStream的文件流是BufferedInputStream
    2. decodeResource同样存在这个问题,建议使用decodeResourceStream

专项标准:磁盘

遵循标准 标准 优先级 规则起源
避免主线程I/O 避免主线程操作文件和数据库。 P0 50%以上的卡顿问题都是由主线程I/O引起的。
使用apply代替Sharepreference.commit P1 apply是异步操作,commit是同步操作。
提前初始化Sharepreference P1 在多进程和旧版本的Android中,初始化过程的I/O读/写都是在主线程的。
减少I/O读写量 减少使用select * P1 减少从数据库读取的数据量,减少耗时。
利用缓存减少重复读写 P2 内存缓存命中率极高,投入产出高。
数据库减少使用AUTOINCREMENT P1 因为要多操作一个表,索引Insert耗时增加了2~4倍。
使用合适的数据分页 P0 sqlite读写磁盘是以page为单位的,在3.12.0之前,sqlite默认page size是1KB,从3.12.0开始,page size调整为4KB。
频繁查询表使用索引 P0 索引可以极大地较少读磁盘的数据量,极大地提升效率。
避免无效索引 P0 无效索引的问题通常是严重的。除了触发全表扫描,产生大量的冗余的读/写之外,还降低了写入性能。
减少I/O操作次数 使用8KB Buffer读/写 P0 可以减少2~3倍的耗时。
批量更新数据库使用事务 P0 启用事务,根据业务规模,会大量减少I/O读写量和操作的次数,从而提升效率。
ZIP压缩大量小文件的时候建议使用zipInputStream P2

内存

内存检测工具集

工具 问题 能力
top/procrank 内存占用过大,内存泄露 发现
meminfo Native内存泄露,是否存在Activity、ApplicationContext泄露、数据库缓存命中率低 发现+初步定位
MAT、Finder、JHAT Java层的重复内存、不合理图片解码、内存泄露等等 发现 + 定位
libc_malloc_debug_leak.so Native内存泄露(JNI层) 发现 + 定位
LeakCanary Activity内存泄露 自动发现 + 定位
StrictMode Activity内存泄露 自动发现 + 初步定位
APT 内存占用过大,内存泄露 发现
GC Log from Logcat、GC Log生成图表 人工触发GC for Explicit而导致的卡顿,Heap内存不足触发GC for Alloc而导致的卡顿 发现 + 初步定位
Systrace GC导致的卡顿 发现
Allocation Tracer 申请内存次数过多和过大、辅助定位GC Log发现的问题 发现 + 定位
chrome devtool HS的内存问题 发现 + 定位

  Android使用的是一个去掉了swap的linux内核(至少在Android4.4以前的版本都是这样的),这样就阻碍了Android上的应用程序使用Page out(应用程序使用的内存,对操作系统而言都是一张张的page,而对于老化的page,操作系统可以将它们从内存中置换到硬盘上,这种操作叫做page out),这一常规的内存操作。
  Android框架对于进程内存的第二个管控特征是,每个进程都有一个内存最高阀值(纯净的Native内存申请不算在内),一旦进程申请内存突破了这个阀值,将会产生异常,并退出运行时的内存空间。简单的说,也就是Android为每个进程已经分好了一块蛋糕,至于你吃或者不吃,是你的自己的事情了。但这是否意味着Android应用程序为了效率考虑,应该玩命的申请内存,使自己的内存沿着天花板滑行,这样是否最健康呢?答案也不一定。
  Android的第三个管控特征是,进程都有可能被杀。在物理内存吃紧的时候(通常在使用meminfo查看内存概况的PSS总值达到设备物理内存80%的时候),Android框架就开始根据一套*的LRU进程Cache列表来杀死进程,被杀死的进程在死前将会得到通知,用以保存现场。而这部分被杀死的进程所腾出来的物理内存,就可以用于某些应用程序的内存申请需求。


  Android内存框架下的各种管控特征:

  • 没有Page out, 所以物理内存更加金贵。
  • 每个进程都有一个内存上限,所以蛋糕是已经被分配好的。
  • 所有的进程都有被杀的可能,所以要做好被杀准备。

专项标准:内存

遵循原则 标准 优先级 规则起源
避免内存泄露 避免Activity泄露 P0 大部分严重的内存泄露都是Activity泄露,因为这意味着被引用的View、图片等全部泄露。
减少常驻内存 尽量使用RGB565 P1 手机QQ使用RGB565将节省部分图片的内存,高达50%。
避免内存重复 P1 手机QQ去掉头像30%的重复缓存,提升缓存的命中率和流畅度。
res/drawable里的图片,建议使用Drawable.createFromStream来加载 P1 使用错误的文件夹,导致图片被放大,最终APP使用的内存增加。
将图片放置到合适的资源文件夹(hdpi、xxhdpi等) P1 使用错误的文件夹,导致图片被放大,最终APP使用的内存增加。
减少GC Bitmap尽量使用inBitmap P1 可以减少GC,提升流畅度。
建议使用SpraseMap或者ArrayMap P2
建议StringBuilder重用(如果由线程使用可配合ThreadLocal) P1 手机QQ与QQ空间日志改造,利用delete来替代new,给予合理的初始化长度,写日志性能提升多倍。

网络

工具集

工具 问题 能力
Wireshark 最专业的网络分析工具,全部网络性能问题的分析定位都可以查看它。 发现+定位
Har+Pagespeed 吧pcap转为har,上传到http://stevesouders.com/flint/ 发现+定位
fiddler 主要针对HTTP,帮助发现HTTP众多性能问题,还能模拟错误和演示的HTTP返回 发现+定位
tcpdump 抓包工具,需要ROOT权限 发现+定位
traceroute 定位网络路由问题,包括就近接入、跨运营商问题 发现+定位
ARO 无压缩、重复下载、缓存失效等,还有雅虎军规中的其他问题 自动发现+定位
WebP/BPG 图片压缩方案,前者基于webm的帧内压缩,后者基于H.264的帧内压缩 解决
SPDY/HTTP2.0/QUIC 网络协议,利用FastTcpOpen减少握手次数,利用UDP更好的适应网络抖动 解决
WebPageTest.org 如果要做Web应用的数据上报,建议参考之。它提供了LoadTime、StartRender、SpeedIndex,DOM Elements等耗时 发现+定位
tPackageCapture 无ROOT转包 定位
ATC 最专业的弱网络模拟工具,除能模拟窄带、延时、丢包、损坏包外,最关键的还有包乱序的情况 发现

工具库

工具 说明
AndroidGodEye AndroidGodEye是一个可以在PC浏览器中实时监控Android数据指标(比如性能指标,但是不局限于性能)的工具,你可以通过wifi/usb连接手机和pc,通过pc浏览器实时监控手机性能。

系统分为三部分:
1. Core 核心部分,提供所有模块;
2. Debug Monitor部分,提供Debug阶段开发者面板;
3. Toolbox 快速接入工具集,给开发者提供各种便捷接入的工具。

AndroidGodEye提供了多种监控模块,比如cpu、内存、卡顿、内存泄漏等等,并且提供了Debug阶段的Monitor看板实时展示这 些数据。而且提供了api供开发者在release阶段进行数据上报。

附录