# 基础性能问题分析（ODM）

## 一、卡顿类问题

### 1. 滑动类卡顿

一般为长列表滑动等单一页面操作，页面应用主线程持续出帧，从SurfaceFlinger也可以看到有一个Activity持续输出buffer，有快滑（手指离屏后页面仍在滑动，对应在input消息后有一段时间出帧）慢滑（手指不离屏滑动，会有持续的input消息）两种情况。这类场景需关注正常绘制流程（应用主线程-RenderThread线程-SF）即可（PS：有些应用为了兼容性，采用webview以及flutter绘制，这个需要单独分析）。

### 2. 动画类卡顿

用户操作后执行固定的动画效果，多数情况在systrace中会有animatortag打印。

#### 2.1 页面跳转动画

两个Activity之间切换的动画一般为系统（system_server）动画，对应在system_server的android.anim.if线程出帧，没有渲染线程，直接通过调用SF的setTransactionState方法参与画面合成。

#### 2.2 转屏动画

应用需要转屏操作时一般会先冻结屏幕，等转屏后的页面准备好后解冻执行转屏动画，冻屏时间过长会给人反应慢的感觉，也会有人认为这是卡顿，因此冻屏时间也需关注。转屏动画与页面跳转动画一样，在android.anim.if线程出帧。

#### 2.4 桌面动画

桌面与应用之间的页面跳转，包括在桌面、recently页面点击应用冷、热启动过程，以及从应用回到桌面、recently页面等操作都可能会执行桌面动画。具体场景可根据input消息打印、activityResume、activityStop等方法的位置判断。桌面动画会在应用主线程正常出帧（launcher主线程-RT线程-SF）。

### 3. 问题分析步骤

#### 3.1 明确问题场景

根据问题描述、截屏、视频、log、systrace等信息初步判断问题场景，以便决定后续分析方向。

##### 3.1.1 整机卡顿、卡死、卡主不动

这类问题常见于项目版本初期，或者市场反馈问题。

整机卡顿问题多是多个场景在同一时间段发生卡顿的问题，也有部分问题可能来自实际用户，实际卡顿场景可能很单一但用户描述不清晰，这时就需要根据已有信息判断是否发生卡顿并拆解出卡顿场景进一步分析。

遇到整机卡顿、卡住、卡死问题，首先排除是否发生ANR、crash等问题，然后拆解出具体卡顿场景继续分析，如果拆解出多个不同场景卡顿需逐一分析（根据systemserver中的focusap以及iq事件等可以大体确认场景），方法对应各场景分析思路，。一般容易造成系统短时间多个场景卡顿的情况如input上报异常、SF出帧HWC合成耗时、CPU限频关核、低内存、高负载等。

##### 3.1.2 单一场景卡顿

根据问题描述或视频、截屏初步判断是卡顿问题还是功能性问题，卡顿场景是否是性能责任田等，保证问题快速流转。如果测试提供信息不全且本地无获取条件及时与测试沟通请测试提供。性能自身问题需明确：

- **问题自检**：卡顿场景、问题信息（systrace、log、截屏、录屏等）是否充足有效、本地能否复现、对比机能否复现等；
- **问题等级**：路径复杂度（及复现概率）、卡顿程度、对比机现象是否一致等。

处理问题时优先关注等级较高的问题，如果测试机卡顿程度比同场景对比机严重，很大可能是系统问题，也需关注。

#### 3.2 定位问题位置

确认问题场景后可找到对应出帧线程，核对卡顿时间（从问题描述、截屏、录屏、log等信息中提取）、input消息、出Buffer情况（SF）、页面生命周期（有跳转情况）等条件，大体圈定systrace范围，如果没有抓到及时请测试复测。

#### 3.3 确认卡顿点并分析原因

常见的卡顿问题大都是丢帧引起的，但造成丢帧的原因多种多样无法全部列举，这里仅列举如何判断是否发生丢帧

##### 3.3.1 丢帧

对于一个帧率60的设备，如果1秒内屏幕显示画面小于60，即发生丢帧。对于我们来说，仅关注应用输出Buffer到SF以及SF输出给屏幕过程是否丢帧即可。

###### 3.3.1.1 应用丢帧

由于3Buffer机制，应用主线程、RT线程出帧稍慢并不一定会丢帧，比较准确的判断应用丢帧的方法是看SF进程下应用输出Buffer的泳道是否连贯、是否每个VSync周期都有可用Buffer供SF消费。

## 二、启动响应

### 响应速度概述

响应速度是应用App性能的重要指标之一。响应慢通常表现为点击效果延迟、操作等待或白屏时间长等，主要场景包括：

- 应用启动场景，包括冷启动、热启动、温启动等
- 界面跳转场景，包括应用内页面跳转、App之间跳转
- 其他非跳转的点击场景（开关、弹窗、长按、控件选择、单击、双击等）
- 亮灭屏、开关机、解锁、人脸识别、拍照、视频加载等场景

从原理上来说，响应速度场景往往是由一个input事件（以Message的形式给到需要处理的应用主线程）触发（比如点击、长按、电源键、指纹等），由一个或者多个Message的执行结束为结尾，而这些Message中一般都有关键的界面绘制相关的Message。衡量一个场景的响应速度，我们通常从事件触发开始计时，到应用处理完成计时结束，这一段时间就称为响应时间。

由于响应速度是一个比较主观的性能指标（而流畅度就是一个很精确的指标，掉一帧就是掉一帧），而且根据角色的不同，对这个性能指标的判定也不同，比如Android系统开发者和应用开发者以及测试同学，对应用冷启动的起点和终点就有不同的判定：

1. 系统开发者往往从input中断开始看，部分以应用第一帧为结束点（因为比较好计算），部分以应用加载完成为结束点（比较主观，除非结束点比较容易通过工具去判断），主要是以优化应用的整体性能为主，涉及到的方面就比较广，包括input事件传递、SystemServer、SurfaceFlinger、Kernel、Launcher等
2. App开发者一般从Application的onCreate或者attachContext开始看，大部分以界面完全加载或者用户可操作为结束点，因为是自己的应用，结束点在代码里面可以主动加，主要还是以优化应用自身的启动速度为主，市面上讲启动速度优化的，大部分是讲这部分
3. 测试同学则更多从用户的真实体验角度来看，以桌面点击应用图标且应用图标变色为第一帧，内容完全加载为结束点。测试过程一般使用高速相机•自动化，通过机械手和图形识别技术，可以自动进行响应速度测试并抓取相关的测试数据

### 响应速度问题分析思路分清起点和终点

分析响应速度，最重要的是要找到起点和终点，上一节讲到，不同角色的开发者，对这个性能指标的判定起点和终点都不一样；而且这个指标有很主观的成分，所以在开始的时候，就要跟各方来确定好起点和终点，具体的数值标准，下面一些手段可以帮助大家来确定

1. 竞品分析。一般来说，响应速度这个指标都会有一个对标的竞品，竞品手机或者竞品App，相同的条件下，竞品手机或者竞品App从点击到响应花费了多少时间，可以作为一个标准
2. 对比前一个版本。有时候系统进行大版本升级或者App进行版本迭代，那么上一个版本的数据就可以拿来作为标准进行对比

一般来说，起点都比较好确定，无非是一个点击事件或者一个自定义的触发事件；而终点的确定就比较麻烦，比如如何确定一个复杂的App（比如淘宝）启动完成的时间点，用Systrace的第一帧或者Log输出的Displayed时间或者onWindowFocusChange回调的时间显然是不准确的。目前市面上使用高速相机+图像识别来做是一个比较主流的做法

### 响应速度常见问题

#### Android 系统自身原因导致响应慢

下面这些列举的是Android系统自身的原因，与Android机器的性能有比较大的关系，性能越差，越容易出现响应速度问题。下面就列出了Android系统原因导致的App响应速度出现问题的原因，以及这个时候App端在Systrace中的表现

1. **CPU频率不足**
   - App端的表现：主线程处于Running状态，但是执行耗时变长

2. **CPU大小核调度：关键任务跑到了小核**
   - App端的表现：Systrace看主线程处于Running状态，但是执行耗时变长

3. **SystemServer 繁忙，主要影响**
   - 响应App主线程Binder调用处理耗时
     - App端的表现：Systrace看主线程处于Sleep状态，在等待Binder调用返回
   - 应用启动过程逻辑处理耗时
     - App端的表现：Systrace看主线程处于Sleep状态，在等待Binder调用返回

4. **SurfaceFlinger 繁忙，主要影响应用的渲染线程的dequeueBuffer、queueBuffer**
   - App端的表现：Systrace看应用渲染线程的dequeueBuffer、queueBuffer处于Binder等待状态

5. **系统低内存，低内存的时候，很大概率出现下面几种情况，都会对SystemServer和应用有影响**
   - 低内存的时候，有些应用会频繁被杀和启动，而应用启动时一个重操作，会占用CPU资源，导致前台App启动变慢
     - App端的表现：Systrace看应用主线程Runnable状态变多，Running状态变少，整体函数执行耗时增加
   - 低内存的时候，很容易触发各个进程的GC，用于内存回收的HeapTaskDeamon、kswapd0出现非常频繁
     - App端的表现：Systrace看应用主线程Runnable状态变多，Running状态变少，整体函数执行耗时增加
   - 低内存会导致磁盘IO变多，如果频繁进行磁盘IO，由于磁盘IO很慢，那么主线程会有很多进程处于等IO的状态，也就是我们经常看到的UninterruptibleSleep
     - App端的表现：Systrace看应用主线程UninterruptibleSleep和UninterruptibleSleep-IO状态变多，Running状态变少，整体函数执行耗时增加

6. **系统触发温控频率被限制：由于温度过高，CPU最高频率被限制**
   - App端的表现：主线程处于Running状态，但是执行耗时变长

7. **整机CPU繁忙：可能有多个高负载进程同时在运行，或者有单个进程负载过高跑满了CPU**
   - App端的表现：从Systrace来看，CPU区域的任务非常满，所有的核心上都有任务在执行，App的主线程和渲染线程多处于Runnable状态，或者频繁在Runnable和Running之间切换

#### 代码逻辑

6. 是否在等Binder耗时比较久（Sleep状态）→检测Binder服务端，一般是SystemServer

7. 是否在等待子线程返回数据（Sleep状态）→应用自身问题，通过查看wakeup信息，来找到依赖的子线程

8. 是否在等待子进程返回数据（Sleep状态）→应用自身问题，通过查看wakeup信息，来找到依赖的子进程或者其他进程(一般是ContentProvider所在的进程)

9. 是否有大量的Runnable→系统问题，查看CPU部分，看看是否已经跑满

10. 是否有大量的IO等待（UninterruptibleSleep|WakeKill-BlockI/O）→检查系统是否已经低内存

11. RenderThread是否执行dequeueBuffer和queueBuffer耗时→查看SurfaceFlinger

12. 如果分析是系统的问题，则根据上面耗时的点，查看系统对应的部分，一般情况要优先查看系统是否异常，参考上面列出的的系统原因，主要看下面四个区域（Systrace）

13. **Kernel 区域**
    - 查看关键任务是否跑在了小核→一般小核是0-3（也有特例），如果启动时候的关键任务跑到了小核，执行速度也会变慢
    - 查看频率是否没有跑满→表现是核心频率没有达到最大值，比如最大值是2.8Ghz，但是只跑到了1.8Ghz，那么可能是有问题的
    - 查看CPU使用率，是否已经跑满了→表现是CPU区域八个核心上，任务和任务之间没有空隙
    - 查看是否低内存
      - 应用进程状态有大量的UninterruptibleSleep|WakeKill-BlockI/O
      - HeapTaskDeamon任务执行频繁
      - kswapd0任务执行频繁

14. **SystemServer 进程区域**
    - input 事件读取和分发是否有异常→表现是input事件传递耗时，比较少见
    - binder 执行是否耗时→表现是SystemServer对应的Binder执行代码逻辑耗时
    - binder 等am、wm锁是否耗时→表现是SystemServer对应的Binder都在等待锁，可以通过wakeup信息跟踪等锁情况，分析等锁是不是由于应用导致的
    - 是否有应用频繁启动或者被杀→在Systrace中查看startProcess，或者查看EventLog
