[读书笔记] Android应用性能优化
一本比较轻量的书,不到一天就可以看完,优化覆盖的面也挺广,会涉及到字节码汇编的一些知识,还有NDK的入门;不足之处里面的API会显得很老,没有收录到最新的优化技巧,提到的点需要自己去深入研究才会更有收获。
[读书笔记] Android应用性能优化
Java代码优化
斐波那契数列优化
0,1,1,2,3,5,8…由前面两个数相加
从递归到迭代,从迭代到减少迭代次数,再到缓存以多次运算
1 | public static long computeIterativelyFaster(int n) { |
BigInteger的使用
1 | public static BigInteger computeIterativelyFaster(int n) { |
斐波那契Q-矩阵公式:
$$
F_{2n-1} = F_{n}^{2} + F_{n-1}^2\
F_{2n} = (2F_{n-1}+F_{n})*F_{n}
$$
1 | public static long computeRecursively(int n) { |
SQLite
字符串的拼接优化
SQLiteStatement实现只编译一次
使用ContentValues灵活实现
使用事务
FTS全文检索
高效使用内存
内存的使用主要涉及的两个因素:
- 物理内存大小
- 虚拟内存交换能力
数据类型的长度——字节码层面
两个64位证书相加的字节码
1 | 448: e0944002 adds r4, r4, r2 |
两个int型则只需要一条字节码
1 | 16c8: e0810000 add r0, r1, r0 |
排序的实现
Array.sort()对于不同的数据类型是采取不同的算法来进行排序
内存泄漏-StrictMode检测
主要有ThreadPolicy和VmPolicy检测,会记录到日志中,需要我们进一步分析日志
多线程和同步
使用线程Thread和AsyncTask
Handler的机制使用
并发类
Sychronized和volatile关键字
使用线程池,并发缓存ConcurrentHashMap
1 | ExecutorService executorService = Executors.newFixedThreadPool(proc + 2); |
一个应用默认启动的线程
- main
- HeapWorker(执行finalize函数和引用对象清理)
- GC(垃圾回收)
- Signal Catcher(捕捉Linux信号进行处理)
- JDWP(Java Debug wire Protocol,调试协议服务)
- Compiler(JIT即时编译)
- Binder Thread #1(Binder通信)
- Binder Thread #2
图形与UI优化
- 嵌套过深:使用<include>、<merge>、<viewstub>;扁平化布局
工具:hierarchyviewer
- 显示当前页面的树结构
- 对测量、布局和绘制三个步骤使用的时间进行统计
工具:layoutopt
针对单个xml文件给出建议,如下
1 | The root-level <FrameLayout/> can be replaced with <merge/> |
电池续航
影响电量的主要功能:
- 屏幕:WakeLock
- 执行代码:广播,大量运算,定时唤醒
- 数据传输:网络,Wifi
- 位置服务:网络,GPS
- 传感器:加速度计,陀螺仪
- 渲染图像:GPU渲染,看不见我们一定要控制停止其渲染
性能评测和剖析
时间测量
1 | System.currentTimeMillis |
方法跟踪
1 | Debug.class |
TraceView工具
在SDK的tools目录下
1 | tarceview awesometrace.tarce // 启动Traceview工具 |
包含了所有的函数调用,以及调用执行时间和调用次数等信息;
本地方法调用,利用QEMU模拟器跟踪
- 通过-trace选项启动模拟器
emulator -trace mytrace -avd myavd
- 调用Debug.startNativeTracing()和Debug.stopNativeTracing()来进行跟踪,也可以使用F9来启动
- 在AVD中会生成QEMU模拟器跟踪文件
使用tracedmdump命令
定义在build/envsetup.sh文件中,需要下载Android源码才能使用
在AVD的目录下运行tracedmdump mytrace
来创建traceview可以打开的跟踪文件
NDK入门和进阶
NDK是为应用开发本地代码的一套工具
HelloWorld
简单实现一个Java调用C/C++的代码
1 | // 1. Java中声明本地方法,不限定一定是static,也不限定一定是基本类型 |
JNI粘合层,生成JNI的头文件和JNI的C源文件
1 | // 头文件中的方法声明 |
参数说明:
JNIEnv指针:JNIEnv的对象是JNI环境本身,他可以与虚拟机交互
jclass/jobject:方法为静态的则是jclass,否则就是jobject类型
JNI访问Java域、调用方法
主要通过一个id来进行访问调用,这个id可以通过env来进行获取
1 | // 获取ID |
实现本地Activity
通过实现NativeActivity类
manifest.xml:hasCode可以关掉了;指定本地库和方法
1
2
3
4<meta-data android:name"android.app.lib_name"
android:value="myapp"/>
<meta-data android:name"android.app.func_name"
android:value="ANativeActivity_onCreate" />功能通过引入相关的头文件实现
从
void android mian(struct android_app* state)
开始工作实现纯本地Activity
通过自己实现生命周期的回调(即是native_app_glue模块实现的功能)
NDK支持编译汇编代码
很少使用上,太具有系统架构针对性了,需要我们学习指令,x86和ARM汇编的实现都不一样。
书中有汇编的并行实现功能
JNI和原生实现的区别(性能)
原生是编译成Dalvik字节码,JIT优化带来了很大的性能优化
本地函数是编译成汇编代码,更加紧凑和体积小,但是不一定比原生的快
需要注意我们再Java和本地空间的过渡时间消耗
字符串的性能问题
Java的字符串要提供给本地方法使用的话,必须进行转码
Java使用16位的Unicode字符来进行编码
C/C++则大部分使用char *来做字符串用
C扩展性能优化
内置函数——build-in
也被称为内联intrinsics,是有编译器内部进行特别处理的函数,可以在保持代码通用性的同时,充分利用某些平台上特有优化
直接在调用处用实现替换调用,或者在函数定义前加上inline关键字,小心代码变臃肿
拆循环,拆成switch/case;效果是不稳定的,不要测试才知道,假如循环体太大,对指令缓存也只是负面影响。
向量指令
在CPU支持SIMD指令的情况下,可以大大提高性能;不支持时,则会生成很低性能代码
(vector_size (16))
这是一个4个整数的向量
预加载内存
1 | int* dst, const int* src; |
小心使用,不是必须加载进来是会增加缓存的压力,导致性能降低的
渲染、RenderScript
OpenGL ES
书中这节属于图形这一章,在这里描述会更好一点
这是一个渲染库
纹理压缩:
未压缩的256*256的RGBA8888就会占用256KB的内存
ETC1是一个创建纹理的工具(etc1 tool):舍弃透明度,每个像素使用4位。
Mipmap
通过提供多层次细节的纹理,解决在小像素中显示很大的图片,避免浪费内存。
通过派生出很多种尺寸的图片,所以Mipmap包会比原始图像多用掉33%的存储。
RenderScript
针对高性能3D渲染和计算操作的框架
1 | 脚本文件 |
会自动生成:
- ScriptC_helloworld.java
- helloworld.d
- helloworld.bc
1 | // 使用 |
总结
这本书里面使用的代码有点老旧了,一些最新优化方法没有提到,但是他不拘细节的描述,可以在时间很短的情况下看完这本书,这些优化方法还需要自己进一步地去进行深究;这本书的一个好处是对检测的工具也有比较广的介绍,并有提到一些比较冷门的技术点,也是开阔视野的一个不错选择。一句总结:广泛且底层,却不拘细节。
另外这本书十分执着于字节码和汇编代码上面的影响,也带给了自身不少启发。
工具的复习:TraceView、hierarchyviewer、layoutopt
NDK的粗略了解:使用步骤,性能的区别