本文是《移动App性能评测与优化》的读书笔记。
PS:说是读书笔记,其实就是摘录。
移动App的性能测试主要包括:内存使用情况、电量消耗、功能的流畅度等;
1. 内存
1.1 内存的主要组成索引:
-
Native Heap : Native 代码分配的内存,虚拟机和Android框架本身也会分配;
-
Dalvik Heap : Java 代码分配的对象;
-
Dalvik Other : 类的数据结构和索引;
-
so mmap : Native 代码和常量;
-
dex mmap :Java 代码和常量;
1.2 内存测试工具
-
Android Studio / Memory Monitor : 观察 Dalvik 内存;
-
dumpsys meminfo : 观察整体内存;
-
smaps : 观察整体内存的详细组成;
-
MAT : 详细分析 Dalvik内存;
1.3 一个类的内存消耗
虚拟机在创建对象时的操作:
-
loadClass,将类信息从 dex 文件中加载进内存:
-
读取 .dexx mmap 中 class 对应的数据;
-
分配 native-heap 和 dalvik-heap 内存创建 class 对象;
-
分配 dalvik-LinearAlloc 存放 class 数据;
-
分配 delvik-aux-structure 存放 class 数据;
-
-
new instance 操作,创建对象实例
-
执行 .dex mmap 中
和 的代码; -
分配 delvik-heap 创建 class对象实例;
-
dex mmap
在Android应用中的作用是映射classes.dex文件
1.4 dex 优化
省略掉dex的文件结构(自行查阅)
为了节省空间,dex将原先在 class 文件中重复的信息集中放置在一起,并以索引和指针的形式支持快速访问。
dex 文件中数据基本是按类名的字母顺序进行排列的,这样同样包名的类会排在一起,但程序实际执行时,同一个包下的类并不会全部调用到,而是跨包进行交互,但 mmap 加载了整个页面,可能会有很多无用的数据。
优化;
在APK的编译流程中,Proguard 混淆工具正好是能够对类名进行修改的,可以根据程序运行时的逻辑,将那些会互相调用的类改为为同一个 package 名,这样就可以使它们的数据排布在一起。
1.5 MAT(Memory Analyzer Tool)
使用MAT来分析应用的内存使用情况。通常在使用MAT打开hprof
文件后,能够在首页看到Top Comnsumers和 component Report等功能,我们可以快速定位一些大块的内存消耗。但我们在分析时会发现系统资源类占据了很大一部分内存,因此为去除这部分对分析的干扰,我们在使用AndroidSDK
提供的hprof-conv
转换时需要增加一个参数:
hporf- conv [-z] <infile><outfile> -z:exclude non-app heaps,such as Zygote
如果hprof文件是已经转换过的,则可以使用OQL:
//在数据中寻找应用的Application类对象,将对象地址转换为十进制后输入以下查询语句:
select * from instanceof java.langObject s where s.@objectAddress> 1107296256
//(后面那串数字应该是Application类对象的地址)
采用这两种方法后,再使用MAT来分析就可以比较容易发现自身代码的内存问题。
1.6 测试经验
-
MAT 是探索 Java 堆并发现问题和好帮手,能够迅速发现常见的图片和大数组等问题;
-
内存碎片问题一般隐藏在对象的地址中;
-
如需要测试非 Dalvik部分,有必要了解 Linux 的进程和内存原理、内存共享机制,熟悉常用命令行工具;
-
内存分配的最小单位是页面,通常为4KB,这个限制会引发各种问题;
1.7 性能优化
-
尽量不要在循环中创建很多临时变量;
-
可以将大型的循环拆散、分段或者按需执行;
-
引入SDK库和调用新的系统API里需要考虑成本;
-
除了Dalvik堆内存,还有其他类型的内存在了解了原理后也能够进行分析和优化;
-
dex 文件有很多优化空间。在仔细统计并调整了dex文件的顺序后,往往可以节约1M以上的 mmap 内存;
2. 耗电
在保证用户的必要体验前提下,尽可能减少不必要的操作。几个优化方法:
方法一:CPU时间片
当应用退到后台运行时,尽量减少应用的主动运行,当检测到CPU时间片消耗异常时,深入线程进行分析;
使用 DDMS 的 traceview 工具:获取进程运行过程中的 traceview,定位CPU占用率异常的方法。
方法二 wake lock
前台运行时运不要去注册 wake lock。 此时注册没有任何意义,却会被计算到应用电量消耗中。后台运行时,在保证业务需要的前提下,应尽量减少注册 wake lock;降低对系统的唤醒频率,使用 partial wake lock 代替 wake lock;
方法三 传感器
合理地设置 GPS 的使用时长和使用频率;
方法四 云省电策略
可考虑定期上报用户手机电量数据的方式来分析问题;
3. 流畅度
3.1 分析工具
-
hierarchy Viewer ,帮助我们去分析UI布局的情况;
-
Tracer for OpenGL ES,可以记录和分析APP每一帧的绘制过程,以及列出所有乃至的OpenGL ES 的绘制函数和耗时;该工具操作后会生成一份记录App绘制过程和gltrace文件,
-
Lint 扫描,发现代码中的流畅度性能问题;
-
Traceview,跟踪程序性能,具体到每一个函数的耗时和调用次数
-
Systrace ,获取App运行时线程的信息以及Api执行情况
< merge > 标签:用于减少View树的层次来优化 Android 的布局,通过该标签可以把 < merge > 标签里的UI合到上一层的 layout中。
< ViewStub> 标签,最大的优点是当你需要时才会加载,使用它并不会影响UI初始化时的性能。各种不常用的布局可以使用该标签来减少内存使用量,加快渲染速度。< ViewStub> 是一个不可见的,大小为0的View。
对于不常用的 UI 可以考虑使用 < ViewStub> 标签替代 GONE 来提高 UI 性能:
将 View 的可见性设置为 GONE,在 Inflate 布局时 View仍然会被 Inflate,也就是说仍然会创建对象,会被 实例化。而 ViewStud 是一个 轻量级的 View,它是一个看不见、不占布局位置、占用资源非常小的控件。
3.2 Perforjmance中的16个问题
-
DrawAllocation: 避免在绘制或者解析布局(draw/layout)时分配对象,比如在Ondraw()中实例化 Paint 对象;
-
Wakelock, 手机不能进入休眠状态,导致手机一直保持在高耗电状态;
-
Recycle :某些资源,比如 TypedArrays 、 VelocityTrackers,用完后应该被回收,但是忘记回收。
-
ObsoleteLayoutParam : Layout中无用的参数;
-
UseCompoundDrawables,可优化的布局;
-
HandlerLeak: Handler 的使用不当导致内存泄漏;
-
UseSparseArrays ,尽量用 Android 的SparseArray 代替 Hashmap;
-
UseValueOf : 需要常量对象时,不应该直接 new, 应该使用 ValueOf 转换。比如需要整数 42 的对象,不要直接用 new Integer(42),应该用 Intener.vallueOf(42),这样可以省内存;
-
DisableBaselineAlignment: 如果 LinearLayout 被用于嵌套 layout空间 计算,它的 android:baselineAligned 属性应该设置成 false ,以加速 layout 计算;
-
InefficientWeight : 当线性布局里只有一个控件,并且使用了weight 属性,最好把 weidth 和 height 设置为0,这样可以省略布局的 measure 过程;
-
FloatMath, 使用 FloatMath 代替 Math;
-
NestedWeights : 避免嵌套 weight ,那将拖累执行效率。
-
UnusedResources / UnusedIds, 未被使用的资源会使程序变大,并且编译速度降低;
-
Overdraw: 如果为 RootView 指定一个背景 Drawable,会先用Theme 的背景绘制一遍,然后才用指定的背景,这就是所谓的 “Overdraw” ,可以设置 theme 的background 为 null 来避免;
-
UselessLeaf / UselessParent : View 或 view 的父亲没有用,应该把它移除,避免影响加深布局的层次;
-
UnusedNamespace : 有些代码没必要使用 namespace ,会影响代码执行效率;
4. 网络优化
考虑点:
-
分小片传输一个文件(图片),这样当某一个分片失败时,只需要重传这一个分片就可以,而不用重传整个文件;
-
不同类型的移动互联网下的分片初始大小应该有所不同;
-
在上传一个文件的过程中,应当尽可能动态增大分片大小,以减小分片数量;
-
确定每个分片是否要继续增大之前,要检查网络类型是否发生了改变;
-
分片一旦传输失败,应当使用该网络下的初始分片大小进行重试;
重点优化优质网络下的传输速度,而不特意优化差网络下的速度;
5. apk瘦身
5.1 瘦身关键点:
-
代码部分:冗余代码、无用功能、代码混淆、方法数缩减;
-
资源部分:冗余资源、资源混淆、图片处理(压缩、图片转换、点9图化等);
-
对整个安装包做7zip极限压缩;
Android 系统安装一个应用的过程中,其中有一步是对 Dex 进行优化,优化的过程是使用专门的工具 DexOpt。DexOpt 是在第一次加载Dex文件的时候执行的。在DexOpt的过程会生成一个ODEX文件。
早期的 DexOpt 有两个问题:
-
DexOpt 会把每一个类的方法的id 检索起来,存在一个链表的结构里的,但是这个链表的长度是用一个 short 类型来保存的,导致了方法 id 的数目不能超过 65536(2^16);
-
DexOpt 使用 LinearAlloc 来存储应用的方法信息,LinearAlloc是一个固定大小的缓冲区(4,5,8,16),当方法数量过多也会导致超出缓冲区大小时,也会造成 DexOpt 崩溃;
5.2 缩减方法数的方法
-
避免在内部类中访问外部类的私有方法或变量;当在 java 内部类(包括 匿名内部类)中访问 外部类的私有方法或变量时,编译器会生成额外的方法;
-
避免调用派生类中的未被覆盖( override) 的方法;避免在派生类中调用未覆盖的基类的方法;避免用派生为对象调用派生类中未被覆盖的基类的方法。因为当调用派生类中的未被覆盖的方法时,会多产生一个方法数;
-
去掉部分类的get 、set 方法;
5.3 代码混淆
代码混淆( Obfuscated code)也叫花指令;对代码进行 Proguard 后,也可以比较大的减小代码的体积(即 dex 的体积);