应用性能设计及优化专题—性能设计概述篇中介绍了常见的卡顿场景类型、性能调优的基本原则、性能调优分析工具等,本文将围绕可能造成卡顿的应用启动流程、绘制刷新、内存管理三方面,给出一些切实可行的优化建议。
- 应用启动流程
应用的启动速度直接影响着用户体验,因此提升应用启动速度是性能优化过程中必不可少的一环。应用启动主要分为冷启、温启、热启三种方式,在冷启动中,应用从头开始启动。在另外两种状态中,系统需要将后台运行的应用带入前台。建议始终在假定冷启动的基础上进行优化,这样做也可以提升温启动和热启动的性能。
以抖音首次启动的Systrace来分解应用启动关键阶段及应用开发中要注意的点:
应用启动优化建议:
应用的启动优化方案很多,中心思想都围绕优化启动逻辑,提高应用的启动速度,这里重点介绍三种供大家参考:
- 系统调度优化:启动过程中减少系统调用,也不要启动子进程,此外,启动过程中除了 Activity 之外的组件启动也要谨慎处理;
- 主页面布局优化:减少冗余或者嵌套布局来降低视图层次结构,用 ViewStub 替代在启动过程中不需要显示的 UI 控件、使用自定义 View 替代复杂的 View 叠加等;
- APK瘦身:减少资源个数和尺寸(如移除不使用的资源、重用资源、压缩PNG和JPEG文件等)、减少Native和Java代码(减少不必要的生成代码、减少Native库的大小、移除调试符号等)等。
2. 绘制刷新流程
软件交互过程中,需要通过页面刷新来实现数据更新,这个过程内容加载速度、渲染的流畅性十分重要。我们都知道,安卓设备的屏幕刷新率一般是60帧每秒(1/60fps=16.6ms每帧),这就要求渲染的内容要在16ms内加载完,否则页面就会出现丢帧等卡顿问题。
优化建议:
- 绘制渲染
- 避免在绘制(一般是自绘制)过程中执行IO、IPC、互斥锁、wait/sleep等耗时甚至阻塞线程的操作。若有需要可把这些耗时处理放到工作线程,工作线程处理完后把结果缓存或post到主线程。如图片加载、解码的处理放到工作线程,结果在post到UI线程去显示;
- 减少布局嵌套和视图层次结构,这会影响inflate和首次measure时长;
- 尽量避免频繁调整布局、修改形状、修改位图,同时慎用Alpha;
- 使用性能较好的布局类(ConstraintLayout);
- 界面分级,不要把过多的内容放在一个界面上;
- 减少过度绘制。
- 保持主线程高效处理事件
- 必须在UI线程中处理的任务,应高效执行,尽量避免不可控的处理(如IO、IPC、锁、条件变量);
- 能放到工作线程中的操作尽量放到工作线程,建议固定一些工作线程或线程池;
- 对于可指定handler/looper的系统监听回调(如位置监听、ContentObserver),应使用工作线程looper上创建handler;
- 需要更新UI的操作在保证处理比较轻的情况下才可放到UI线程执行,较重的处理应在工作线程,最后把更新动作通过Activity.runOnUiThread(Runnable)、View.post(Runnable)、View.postDelayed(Runnable, long)等丢到UI线程去执行;
- 禁止把临时线程作为Handler的looper线程,因为临时线程退出后,后续的msg是不会得到处理的,且会导致内存泄漏。
3. 内存管理
影响:
- 内存占用对性能的影响不仅是当前的绝对占用会导致系统可用内存变少,频繁的申请和释放也会导致CPU负荷高且触发GC短暂阻塞应用各线程;
- 当系统可用内存不够时,lmkd/kswapd等占用CPU负荷,会杀死应用进程;
- 而当内存占用太多时,可能会触发OOM(dalvik.vm.heapgrowthlimit=384m, 512m)而出现应用闪退;
- 为了规避内存基线而在代码中主动频繁触发gc的做法是不可取的。
内存管理优化建议:
- APK瘦身,资源裁剪压缩和编排,常用类重新编排;
- 合理的数据结构和数据类型,以及字节自然对齐,减少数据传递中的转换;
- 高频使用的对象单例化,特别是纯方法类,跟对象属性无关方法static化;
- 复用:减少子进程数和碎片App个数;使用缓存和对象池;C++代码的对象引用可考虑sp智能指针;
- 弹性设计:根据设备规格对业务进行裁剪和按需启动;应用切换到后台后,可以做一些内存释放动作;正确处理组件onLowMemory和onTrimMemory接口;
- 调试阶段可以使用StrictMode.VmPolicy或LeakCanary来检测Java内存泄漏。
性能的影响因素有很多,除了要在性能方案设计时进行多种考量,也需要在软件的整个生命周期中持续优化。下篇我们将就影响性能的不良实现进行梳理,欢迎持续关注。