性能优化常用工具

  • Systrace:
    通过 systrace 可对整个系统的运行状况有一个总的大体诊断
    • systrace:user atrace and frace to out an html file
    • atrace:andriod framework trace
    • ftrace:linux kernel trace
  • Traceview
    分析代码的执行及耗时等
  • MAT
    分析内存分析使用
  • Allocation Tracker
    分析内存申请使用
  • Hierarchy Viewer
    分析视图组成
  • GPU Profiling
    分析 GPU 使用情况
  • Menory Profiling
  • Network Profiling

性能优化

多线程

  • AsyncTask:默认所有的 AsyncTask 共用同一个 SERIAL_EXECUTOR,因此每个 AsyncTask 之间是阻塞的,而且每个 AsyncTask 都是一次性的,所以 Async 比较适合执行短时间的异步任务
  • HanlderThread:如果需要长时间持有一个线程,可以使用 HanlderThread,
  • ThreadPool:适合执行大量的并行任务
  • IntentServer:HanlderThread 和 Server 的混合

使用线程的时候需要注意根据执行的任务设置 thread priority,这样有助于 CPU 进行线程调度,特别是使用 HanlderThread 的时候要注意设置 thread priority。

内存优化

  • 避免大量频繁申请临时内存:大量申请临时内存容易引起 GC,而 GC 的时候后会导致整个系统暂停
    • 注意 Autoboxing
      Java 中 primitive 类型的数据会有 autobox,而每一次 autobox 都会生成一个新的对象。可以使用 SparseArray Family(SparseArray、SparseBooleanArray、SparseIntArray、SparseLongArray、LongSparseArray)替换 HashMap 避免 autobox,原理和 ArrayMap 类型。
      Why don't have StringSparseArray?
      SparseArray vs HashMap
    • Index vs Iterate:允许的情况下,避免使用 Iterate。
      相对于直接使用下标定位列表,使用 Iterate 在每次循环的时候都会申请额外的临时内存。当然,只有在列表比较大的时候才需要这种优化。
    • 使用对象池避免内存申请
  • 使用 ArrayMap:
    HashMap VS ArrayMap:
    • 内存消耗:HashMap 由于采用 hash(key) 映射 table 下标,导致 table 的空间没有利用完全,而且即使 HashMap 未空的时候,也会默认为 table 默认分配一定大小,所以内存利用率会低点;而 ArrayMap 维护两个 Array(HashArray 和 KeyValueArray,两者通过 KeyIndex=(hashIndex*2)+1 进行映射),从而提高了内存使用率
    • 扩容:HashMap 扩容的时候需要对整个列表重新进行 hash,效率比较低;而 ArrayMap 扩容的时候直接 copy 一份数据,效率比较高
    • 查找:HashMap 由于采用 hash 映射,所以查找效率较高;而 ArrayMap 本质上使用时二分搜索进行查找,效率相对低点
    • 删除:HashMap 使用 hash 映射或链表操作删除元素,效率较高;ArrayMap 删除元素的时候需要对整个数组进行分段 copy 效率较低 ArrayMap 相对于 HashMap 使用运行效率换取内存空间,一般来说大小 1k 以内都执行效率的影响看忽略不计。
  • 避免 Enums:
    每个 Enums 本质上都是一个 final class,所以占用的内存会比直接使用 int 多,如果可以使用 int + annotation 来实现的话,就没必要使用 Enums
  • 使用 void onTrimMemory(int level) 回调:
    onTrimMemory 会在系统需要回收内存前回调给程序,可以在这里做一些 Cache 的回收操作
  • 避免内存泄露
    • 内存泄露常见场景
      • 异步回调:比如匿名函数默认对外部类有一个强制引用,如果在页面销毁的时候没有清除掉回调,那么直到回调完毕,页面都不会被回收
      • 在静态对象中的引用:静态对象的生命周期和整个 App 的生命周期一样,这样在 App 存活期间会一直持有,导致对象无法回收
      • 通过 Map、List 等容器进行对象管理:如果容器的生命周期比较长,如果要资源不用的时候 remove 掉,否则容器会一直保持对象的引用,导致无法释放。这种情况在实现 callback 管理的时候要注意
    • 内存泄露监控
      通过引入 LeakCanary 和使用 Android 的 StrictMode API,可以很好的监控内存泄露情况
    • 内存泄露分析
      • dump 内存
        命令行 dump 比 IDE 稳定,推荐使用命令行

        1
        2
        3
        adb shell am dumpheap <process> <file>//dump 内存到本地文件
        adb pull <file> <local>//pull 文件到本地
        hprof-conv <android.hprof> <mat.hprof>//转换 hprof 文件,hprof-conv 在 sdk 目录的 platform-tools 文件夹内
      • 内存分析
        使用 Android Studio 的 HPROF Viewer 或 MAT 查看分析 hprof 文件, MAT 提供的功能和信息更多,MAT 建议去官网下载 stand-alone 版本。

渲染优化

  • 避免多次 layout

  • 合理使用硬件加速:硬件加速可以实现在避免重新 measure/layout 的情况下修改并更新 View,在动画运行期间对 View 开始硬件加速可以很好地提高动画性能。 目前 View 支持硬件加速的属性有

    • alpha: Changes the layer's opacity
    • x, y, translationX, translationY: Changes the layer's position
    • scaleX, scaleY: Changes the layer's size
    • rotation, rotationX, rotationY: Changes the layer's orientation in 3D space
    • pivotX, pivotY: Changes the layer's transformations origin

    PS:目前 hardware acceleration 不支持 clip(false) 操作,即如果使用 hardware acceleration 的话,超出容器范围的内容一定不会被绘制

  • 使用 canvas.clip() 避免非必要的更新

  • 减少 View Hierarchy 的层级:过大的 View Hierarchy 层级会导致每次 invalidate 产生大量的计算和 reDraw

  • 对不不需要的 View 可以 remove 掉或者设置 visible 为 GONE,否则视图还是会参与绘制

  • 使用 ViewStub 懒加载 View:部分 View 可能只有特定情况下才回出现,使用懒加载可以避免不必要的计算和内存消耗

其他

  • 使用 SQLite transaction 和 yielded

Ref1:Speed up your app
Ref2:Android Performance Patterns
Ref3:Hardware Acceleration
Ref4:Profile you app
Ref5:Google I/O 2012 - Doing More With Less: Being a Good Android Citizen