当我们说 流畅度 的时候,我们说的是什么?不同的人对流畅性(卡顿掉帧)有不同的理解,对卡顿阈值也有不同的感知,所以有必要在开始这个系列文章之前,先把涉及到的内容说清楚,防止出现不同的理解,也方便大家带着问题去看这几篇问题,下面是一些基本的说明
Systrace 流畅性实战目前包括下面三篇
Systrace (Perfetto) 工具的基本使用如果还不是很熟悉,那么需要优先去补一下 Systrace 基础知识系列[4]
如文章开头所述,本文主要是分析流畅度相关的问题。流畅度是一个定义,我们评价一个场景的流畅度的时候,往往会使用 fps 来表示。比如 60 fps,意思是每秒画面更新 60 次;120 fps,意思是每秒画面更新 120 次。如果 120 fps 的情况下,每秒画面只更新了 110 次(连续动画的过程),这种情况我们就称之为掉帧,其表现就是卡顿,fps 对应的也从 120 降低到了 110 ,这些都可以被精确地监控到
同时掉帧帧的原因非常多,有 APP 本身的问题,有系统原因导致卡顿的,也有硬件层的、整机卡的,这个可以参考下面四篇文章
用户在使用手机的过程中,卡顿是最容易被感受到的
所以不管是应用还是系统,都应该尽量避免出现卡顿,发现的卡顿问题最好优先进行解决
为了知道卡顿是如何发生的,我们需要知道应用主线程的一帧是如何工作的
从 Choreographer 收到 Vsync 开始,到 SurfaceFlinger/HWC 合成一帧结束(后面还包含屏幕显示部分,不过是硬件相关,这里就不列出来了)
上面的流程图从 Systrace (Perfetto)的角度来看会更加直观
具体的流程参考上面两个图以及代码就会很清楚了,上述整体流程中,任何一个步骤超时都有可能导致卡顿,所以分析卡顿问题,需要从多个层面来进行分析,比如应用主线程、渲染线程、SystemServer 进程、SurfaceFlinger 进程、Linux 区域等
我对卡顿的定义是:稳定帧率输出的画面出现几帧没有绘制 对应的应用单词是 Smooth VS Jank
比如下图中,App 主线程有在正常绘制的时候(通常是做动画或者列表滑动),有一帧没有绘制,那么我们认为这一帧有可能会导致卡顿(这里说的是有可能,由于 Triple Buffer 的存在,这里也有可能不掉帧)
下面从三个方面定义卡顿
这里没有提到应用主线程,是因为主线程耗时长一般会间接导致渲染线程出现延迟,加大渲染线程执行超时的风险,从而引起卡顿;而且应用导致的卡顿原因里面,大部分都是主线程耗时过长导致的
卡顿还要区分是不是逻辑卡顿,逻辑卡顿指的是一帧的渲染流程都是没有问题的,也有对应的 Buffer 给到 SurfaceFlinger 去合成,但是这个 App Buffer 的内容和上一帧 App Buffer 相同(或者基本相同,肉眼无法分辨),那么用户看来就是连续两帧显示了相同的内容。这里一般来说我们也认为是发生了卡顿(不过还要区分具体的情况);逻辑卡顿主要是应用自身的代码逻辑造成的
由于卡顿的原因比较多,如果要分析卡顿问题,首先得对 Android 系统运行的机制有一定的了解,下面简单介绍一下分析卡顿问题需要了解的系统运行机制:
App 进程在创建的时候,Fork 完成后会调用 ActivityThread 的 main 方法,进行主线程的初始化工作
frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
......
// 创建 Looper、Handler、MessageQueue
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
......
// 开始准备接收消息
Looper.loop();
}
// 准备主线程的 Looper
frameworks/base/core/java/android/os/Looper.java
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
// prepare 方法中会创建一个 Looper 对象
frameworks/base/core/java/android/os/Looper.java
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
// Looper 对象创建的时候,同时创建一个 MessageQueue
frameworks/base/core/java/android/os/Looper.java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread()
}
主线程初始化完成后,主线程就有了完整的 Looper、MessageQueue、Handler,此时 ActivityThread 的 Handler 就可以开始处理 Message,包括 Application、Activity、ContentProvider、Service、Broadcast 等组件的生命周期函数,都会以 Message 的形式,在主线程按照顺序处理,这就是 App 主线程的初始化和运行原理,部分处理的 Message 如下
frameworks/base/core/java/android/app/ActivityThread.java
class H extends Handler {
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
public static final int STOP_SERVICE = 116;
public void handleMessage(Message msg) {
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
}
}
上一节应用的主线程初始化完成后,主线程就进入阻塞状态,等待 Message,一旦有 Message 发过来,主线程就会被唤醒,处理 Message,处理完成之后,如果没有其他的 Message 需要处理,那么主线程就会进入休眠阻塞状态继续等待
从下图可以看到 ,Android Message 机制的核心就是四个:Handler、Looper、MessageQueue、Message
网上有很多关于 Message 机制代码细节的分析,所以这里只是简单介绍 Message 机制的四个核心组件的作用
从第一节 App 主线程运行原理可知,ActivityThread 的就是利用 Message 机制,处理 App 各个生命周期和组件各个生命周期的函数
首先我们需要知道什么是屏幕刷新率,简单来说,屏幕刷新率是一个硬件的概念,是说屏幕这个硬件刷新画面的频率:举例来说,60Hz 刷新率意思是:这个屏幕在 1 秒内,会刷新显示内容 60 次;那么对应的,90Hz 是说在 1 秒内刷新显示内容 90 次
与屏幕刷新率对应的,FPS 是一个软件的概念,与屏幕刷新率这个硬件概念要区分开,FPS 是由软件系统决定的 :FPS 是 Frame Per Second 的缩写,意思是每秒产生画面的个数。举例来说,60FPS 指的是每秒产生 60 个画面;90FPS 指的是每秒产生 90 个画面
VSync 是垂直同期( Vertical Synchronization )的简称。基本的思路是将你的 FPS 和显示器的刷新率同期起来。其目的是避免一种称之为”撕裂”的现象.
一般来说,屏幕刷新率是由屏幕控制的,FPS 则是由 Vsync 来控制的,在实际的使用场景里面,屏幕刷新率和 FPS 一般都是一一对应的,具体可以参考下面两篇文章:
上一节讲到 Vsync 控制 FPS,其实 Vsync 是通过 Choreographer 来控制应用刷新的频率的
Choreographer 的引入,主要是配合 Vsync,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机. 至于为什么 Vsync 周期选择是 16.6ms (60 fps) ,是因为目前大部分手机的屏幕都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 Vsync 的周期也设置为 16.6 ms,每隔 16.6 ms,Vsync 信号到来唤醒 Choreographer 来做 App 的绘制操作 ,如果每个 Vsync 周期应用都能渲染完成,那么应用的 fps 就是 60,给用户的感觉就是非常流畅,这就是引入 Choreographer 的主要作用
Choreographer 扮演 Android 渲染链路中承上启下的角色
下图就是 Vsync 信号到来的时候,Choreographer 借助 Message 机制开始一帧的绘制工作流程图
这部分详细的流程可以看 [Android 基于 Choreographer 的渲染机制详解] [11] 这篇文章
BufferQueue 是一个生产者(Producer)-消费者(Consumer)模型中的数据结构,一般来说,消费者(Consumer) 创建 BufferQueue,而生产者(Producer) 一般不和 BufferQueue 在同一个进程里面
在 Android App 的渲染流程里面,App 就是个生产者(Producer) ,而 SurfaceFlinger 是一个消费者(Consumer),所以上面的流程就可以翻译为
知道了 Buffer 流转的过程,下面需要说明的是,在目前的大部分系统上,每个应用都有三个 Buffer 轮转使用,来减少由于 Buffer 在某个流程耗时过长导致应用无 Buffer 可用而出现卡顿情况
下图是双 Buffer 和 三 Buffer 的一个对比图
三个 Buffer 的好处如下
坏处就是 Buffer 多了会占用内存
这部分详细的流程可以看 [Android Systrace 基础知识 - Triple Buffer 解读] [12] 这篇文章
Android 系统是由事件驱动的,而 input 是最常见的事件之一,用户的点击、滑动、长按等操作,都属于 input 事件驱动,其中的核心就是 InputReader 和 InputDispatcher。InputReader 和 InputDispatcher 是跑在 SystemServer 里面的两个 Native 线程,负责读取和分发 Input 事件,我们分析 Systrace 的 Input 事件流,首先是找到这里。
上面流程对应的 Systrace 如下
这部分详细的流程可以看 [Android Systrace 基础知识 - Input 解读] [13] 这篇文章
附件已经上传到了 Github 上,可以自行下载:https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action[17]
[1]Systrace 流畅性实战 1 :了解卡顿原理: https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/
[2]Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析: https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/
[3]Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问: https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/
[4]Systrace 基础知识系列: https://www.androidperformance.com/2019/05/26/Android_Systrace_0/
[5]Android 中的卡顿丢帧原因概述 - 方法论: https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/
[6]Android 中的卡顿丢帧原因概述 - 系统篇: https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/
[7]Android 中的卡顿丢帧原因概述 - 应用篇: https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/
[8]Android 中的卡顿丢帧原因概述 - 低内存篇: https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/
[9]Android 新的流畅体验,90Hz 漫谈: https://androidperformance.com/2019/05/15/90hz-on-android/
[10]Android Systrace 基础知识 - Vsync 解读: https://androidperformance.com/2019/12/01/Android-Systrace-Vsync/
[11]Android 基于 Choreographer 的渲染机制详解: https://androidperformance.com/2019/10/22/Android-Choreographer/
[12]Android Systrace 基础知识 - Triple Buffer 解读: https://androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer/
[13]Android Systrace 基础知识 - Input 解读: https://androidperformance.com/2019/11/04/Android-Systrace-Input/
[14]Systrace 流畅性实战 1 :了解卡顿原理: https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/
[15]Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析: https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/
[16]Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问: https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/
[17]https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action: https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action
[18]关于我: https://www.androidperformance.com/about/
[19]博客内容导航: https://androidperformance.com/2019/12/01/BlogMap/
[20]优秀博客文章记录 - Android 性能优化必知必会: https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8