移动App性能评测与优化

本文是《移动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]  -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 的体积);

6 参考文献:

1 移动App性能评测与优化

你可能感兴趣的:(移动App性能评测与优化)