当咱们说 流畅度 的时候,咱们说的是什么?不一样的人对流畅性(卡顿掉帧)有不一样的理解,对卡顿阈值也有不一样的感知,因此有必要在开始这个系列文章以前,先把涉及到的内容说清楚,防止出现不一样的理解,也方便你们带着问题去看这几篇问题,下面是一些基本的说明java
Systrace 流畅性实战目前包括下面三篇android
Systrace (Perfetto) 工具的基本使用若是还不是很熟悉,那么须要优先去补一下 Systrace 基础知识系列git
如文章开头所述,本文主要是分析流畅度相关的问题。流畅度是一个定义,咱们评价一个场景的流畅度的时候,每每会使用 fps 来表示。好比 60 fps,意思是每秒画面更新 60 次;120 fps,意思是每秒画面更新 120 次。若是 120 fps 的状况下,每秒画面只更新了 110 次(连续动画的过程),这种状况咱们就称之为掉帧,其表现就是卡顿,fps 对应的也从 120 下降到了 110 ,这些均可以被精确地监控到github
同时掉帧帧的缘由很是多,有 APP 自己的问题,有系统缘由致使卡顿的,也有硬件层的、整机卡的,这个能够参考下面四篇文章性能优化
用户在使用手机的过程当中,卡顿是最容易被感觉到的markdown
因此不论是应用仍是系统,都应该尽可能避免出现卡顿,发现的卡顿问题最好优先进行解决数据结构
为了知道卡顿是如何发生的,咱们须要知道应用主线程的一帧是如何工做的app
从 Choreographer 收到 Vsync 开始,到 SurfaceFlinger/HWC 合成一帧结束(后面还包含屏幕显示部分,不过是硬件相关,这里就不列出来了)ide
上面的流程图从 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 的渲染机制详解 这篇文章
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 解读 这篇文章
Android 系统是由事件驱动的,而 input 是最多见的事件之一,用户的点击、滑动、长按等操做,都属于 input 事件驱动,其中的核心就是 InputReader 和 InputDispatcher。InputReader 和 InputDispatcher 是跑在 SystemServer 里面的两个 Native 线程,负责读取和分发 Input 事件,咱们分析 Systrace 的 Input 事件流,首先是找到这里。
上面流程对应的 Systrace 以下
这部分详细的流程能够看 Android Systrace 基础知识 - Input 解读 这篇文章
附件已经上传到了 Github 上,能够自行下载:github.com/Gracker/Sys…
一我的能够走的更快 , 一群人能够走的更远