前置知识
- 有Android开发基础
- 了解Android运行原理
- 了解何为性能优化,以及清楚性能优化的思路
前言
在前文中,我们讲到了何为性能优化,以及详解了性能优化的四大类,如果你还没看过上述的两篇文章,你可以点击这里(初识性能优化 、 探析Android中的四类性能优化)进行查阅,有了这些理论知识再来看本篇文章会更加的通透。
本文将简单讲述一下 Android 流畅性问题上面的耗时成因,对其进行简单的分析。希望通过本文,你可以学习到更多的可优化问题
影响流畅性的耗时点
在 探析Android中的四类性能优化 一文中,我们已经了解到流畅性优化中的优化重点就是让主线程尽量只做交互(Input Event)以及刷新(UI Draw),再抽象出来讲就是让各类影响流畅性的耗时变得更加微小。
而使用性能分析工具()总结 Android 中的各类耗时,你会发现耗时的成因大致分为以下几点:
- CPU Duration:循环(错误的循环),反射,序列化问题,类解析
- CPU self Duration :CPU自身的耗时
- Wall Duration:有资源抢占,导致的等待时间
- IO Wait:IO 操作,等待 IO 的结果。没事别在主线程做 IO 操作(数据库其实有做这些优化)
- IPC:Binder 调用耗时。虽然 Google 做了优化,但是调用任务很多的时候,耗时还是很严重。
- Lock Wait:主线程等锁,等其他线程或者自我超时唤醒。
- CPU Schedule:主程序是可执行状态,但是由于主线程被调低或者有大量密集型操作、也会导致主线程获取不到时间片。
而根据上述的归因,我们知道了具体有哪些操作可被优化,但是并非说把耗时操作都一股脑直接放去子线程后台操作即可。我们对主线程进行优化的时候,也要注意后台线程的优化。
因为后台线程如果持续的密集的进行操作的时候,你把主线程优化得再好,它也难以调度到 CPU 进行操作,因为后台线程已持续的绑定大核了。当然,是否要注意这些问题,也需要观察我们该时刻运行的是什么,如果运行的是 UI 密集型的操作,例如视频播放,那么我们就需要留给前台更多的 CPU 调度机会;如果不是,那么我们可能就不需要关注后台持续绑定大核的问题了。所以说,不同运行环境中的耗时成因也是不同的。
APP启动中的耗时问题
下面我们引用抖音的启动耗时(冷启动)案例来讲解,分析一下其中的耗时点。
由下图可以知道,其中 45% 的时间在执行CPU高密集型操作;接近 40% 的时间在执行 IO 操作,估计是调度 xml 布局;而 11% 的时间是在 Sleep 等锁状态;且有 4% 的时间在执行CPU调度任务,APP 的启动是很高优先级的,不应该有 CPU 的调度来对其产生影响。
上述讲述的是冷启动的问题,而我们的启动是分为三种的:冷启动、温启动和热启动。三类启动时以 APP 启动执行的生命周期来区分的。下面引用官网中对这三种启动的说明,我们可以得出他们启动的时间开销是这样子的:冷启动 > 温启动 > 热启动。
- 冷启动是指应用从头开始启动:系统进程在冷启动后才创建应用进程。发生冷启动的情况包括应用自设备启动后或系统终止应用后首次启动。这种启动给最大限度地减少启动时间带来了最大的挑战,因为系统和应用要做的工作比在另外两种启动状态中更多。
- 温启动包含了在冷启动期间发生的部分操作;同时,它的开销要比热启动高。有许多潜在状态可视为温启动。(不用重新启动应用的进程)例如:
- 用户在退出应用后又重新启动应用。进程可能已继续运行,但应用必须通过调用
onCreate()从头开始重新创建 activity。 - 系统将您的应用从内存中逐出,然后用户又重新启动它。进程和 activity 需要重启,但传递到
onCreate()的已保存的实例 state bundle 对于完成此任务有一定助益。
- 应用的热启动比冷启动简单得多,开销也更低。在热启动中,系统的所有工作就是将您的 activity 带到前台。只要应用的所有 activity 仍驻留在内存中,应用就不必重复执行对象初始化、布局膨胀和呈现。
从上述的三类启动中提取耗时点如下
- Cold Start(需要 Time < 3s)
- Create process:冷启动的最根本点,是应用的进程未被创建,所以它有一个很耗时的点就是应用进程的创建。
- ContentProvider init:由于有这个
ContentProvider注册逻辑,所以很多第三方 SDK 集成进去的时候,都可以达到自动初始化的目的。但是这些不同的 SDK 其自身很多的逻辑也会导致启动有了很大的负载。Google 的解决方案是使用 Jetpack 的 App Startup 组件进行统一的启动序列的管理,进行 ContentProvider 的串写化。 - Application#onCreate():注册全局性的逻辑
- Warm Start(需要 Time < 1s)
- Activity#onCreate():进程已存在,Activity 被销毁后再次启动
- Inflate view hierarchy:View 的加载耗时
- Hot Start(需要瞬间打开)
- Activity#onStart():切换到后台再次打开的场景
渲染中的耗时问题
由前文中,我们知道大致的 UI 渲染流程如下,经过对其整理,大致得出右侧渲染耗时的具体点。
inflater:这是展示布局的第一步,把布局从 xml 中取出
init:在布局中初始化哪些数据
bind:布局和页面的绑定
M/L/C:三大绘制方法,也有很多耗时逻辑
overdraw:多层级绘制,层级越深,需要执行的命令也越多,负载也会越重。
而对于渲染频率,有以下的点
Animator FPS:很多时候动图帧率是较为低的,我们依旧是使用默认的 60 帧来绘制,也是导致了不必要的负载。
VSync Leak:VSync 泄露是指前一个页面的绘制逻辑未结束,下一个页面的绘制逻辑就继续了,就会出现不必要的问题。
requestLayout Loop:在 requestLayout 里面再次调用 requestLayout,导致绘制请求的循环。
而对于耗时问题,我们对其的优化不应该只是串型的,同时要注意并行的问题。例如下图,在 doFrame 回调流程中,其需要调用的一些任务还在非 UI 线程中执行,这个时候形成了资源的竞争,其阻塞也会导致其时间相应的变长。那么使用其他线程来执行就没有太大意义了。我们在优化的时候,也就需要使得其并行效率也是最高的。
简单的耗时分析
一个布局从 xml 到成为一个 View 对象有以下耗时点
- IO:把 XML 布局加载进来的时候,就会涉及到 IO 操作
- 类反射:XML 中的各种布局嵌套关系,由类反射来执行操作
- View 初始化:拿到 View 布局之后,还会需要初始化其中的初始数据
- AssertManager资源锁:View 中还涉及到 Assert 的资源锁
本文的归因分析到此结束,下一篇文章,我们会简单的讲一下一些案例的分析和处理方法。





