淘先锋技术网

首页 1 2 3 4 5 6 7

前言

Android App 应用卡顿场景十分场景,今天我将针对Android 卡顿,讲解Android卡顿分析定位的方法。


1、 如何定义发生了卡顿?

当出现 「App的FPS平均值小于30,最小值小于24」的现象,即 说明Android 应用发生了卡顿问题。


2、为什么卡顿问题难以定位?

  1. 卡顿产生的原因复杂:涉及 内存、绘制、IO、CPU等;
  2. 线上的卡顿问题在线下难以复现:因为它与当时的场景是强相关的,比如说线上用户的磁盘IO空间不足了,它影响了磁盘IO的写入性能,所以导致卡顿。

针对这种问题,我们最好在发现卡顿时尽量地去记录用户当时发生卡顿时的具体场景信息


3、 卡顿分析方法

造成卡顿的原因有很多种,但最终都会反映到 「CPU时间」 上。

本文介绍的卡顿分析方法原理,则是基于 「使用shell命令分析CPU耗时」 来进行的

储备知识

1、 CPU时间包含用户时间和系统时间:

用户时间:执行用户态应用程序代码所消耗的时间。

系统时间:执行内核态系统调用所消耗的时间,包括I/O、锁、中断和其它系统调用所消耗的时间。

2、 CPU问题分类

CPU的问题大致可以分为以下三类:「1、CPU资源冗余使用」

  • 「算法效率太低」:明明可以遍历一次的却需要去遍历两次,「主要出现在查找、排序、删除等环节。」
  • 「没有使用cache」:明明解码过一次的图片还去重复解码。
  • 「计算时使用的基本类型不对:明明使用int就足够,却要使用long,这会导致CPU的运算压力多出4倍。」

「2、CPU资源争抢」

  • 「抢主线程的CPU资源」:这是最常见的问题,并且在Android 6.0版本之前没有renderthread的时候,主线程的繁忙程度就决定了是否会引发用户的卡顿问题。
  • 「抢音视频的CPU资源」:音视频编解码本身会消耗大量的CPU资源,并且其对于解码的速度是有硬性要求的,如果达不到就可能产生播放流畅度的问题。我们可以「采取两种方式去优化:1、尽量排除非核心业务的消耗。2、优化自身的性能消耗,把CPU负载转化为GPU负载,如使用renderscript来处理视频中的影像信息。」
  • 「大家平等,互相抢」:比如在自定义的相册中,我开了20个线程做图片解码,那就是互相抢CPU了,结果就是会导致图片的显示速度非常慢。这简直就是三个和尚没水喝的典型案例。因此,在自定义线程池的时候我们需要按照系统核心数去控制线程数。

「3、CPU资源利用率低」

对于启动、界面切换、音视频编解码这些场景,为了保证其速度,我们需要去好好利用CPU。「而导致无法充分利用CPU的因素,不仅有磁盘和网络I/O,还有锁操作、sleep等等。对于锁的优化,通常是尽可能地缩减锁的范围。」

具体分析

方式1:通过读取/proc/stat与/proc/[PID]/stat文件来计算 & 评估系统的CPU耗时情况

当应用出现卡顿问题之后,首先我们应该查看系统CPU的使用率。

首先,我们通过「读取 /proc/stat 文件获取总的 CPU 时间」,并「读取 /proc/[PID]/stat 获取应用进程 的CPU 时间」,然后,「采样两个足够短的时间间隔的 CPU 快照与进程快照来计算其 CPU 使用率」

计算总的 CPU 使用率

1、采样两个足够短的时间间隔的 CPU 快照,即需要前后两次去读取 /proc/stat 文件,获取两个时间点对应的数据,如下所示:

因为我的手机是8核,所以这里的cpu个数是8个,从cpu0到cpu7,「第一行的cpu即是8个cpu的指标数据汇总」,因为是要计算系统cpu的使用率,那当然应该以cpu为基准了。两次采样的CPU指标数据如下:

其对应的各项指标如下:

拿cpu1(9931551 1082101 9002534 174463041 340947 1060438 1088978 0 0 0)的数据来说,下面,我就来详细地解释下这些指标的含义。

  • user(9931551):表示从「系统启动开始至今处于用户态的运行时间」,注意「不包含 nice 值为负的进程」
  • nice(1082101) :表示「从系统启动开始至今nice 值为负的进程所占用的 CPU 时间」
  • system(9002534):表示「从系统启动开始至今处于内核态的运行时间」
  • idle(174463041) :表示「从系统启动开始至今除 IO 等待时间以外的其他等待时间」
  • iowait(340947):表示「从系统启动开始至今的IO 等待时间」。(从Linux V2.5.41开始包含)
  • irq(1060438):表示「从系统启动开始至今的硬中断时间」。(从Linux V2.6.0-test4开始包含)
  • softirq(1088978):表示「从系统启动开始至今的软中断时间」。(从Linux V2.6.0-test4开始包含)
  • stealstolen(0) :表示当在虚拟化环境中运行时在其他操作系统中所花费的时间。在Android系统下此值为0。(从Linux V2.6.11开始包含)
  • guest(0) :表示当在Linux内核的控制下为其它操作系统运行虚拟CPU所花费的时间。在Android系统下此值为0。(从 V2.6.24开始包含)

此外,这些数值的单位都是 jiffies,「jiffies 是内核中的一个全局变量,用来记录系统启动以来产生的节拍数,在 Linux 中,一个节拍大致可以理解为操作系统进程调度的最小时间片,不同的 Linux 系统内核中的这个值可能不同,通常在 1ms 到 10ms 之间」

了解了/proc/stat命令下各项参数的含义之后,我们就可以由前后两次时间点的CPU数据计算得到cpu1与cpu2的活动时间,如下所示:

因此可得出总的CPU时间,如下所示:

最后,我们就可以计算出「系统CPU的使用率」::

可以看到,前后两次时间点间的CPU使用率大概为8%,说明我们系统的CPU是处于空闲状态的,「如果CPU 使用率一直大于 60% ,则表示系统处于繁忙状态,此时就需要进一步分析用户时间和系统时间的比例,看看到底是系统占用了CPU还是应用进程占用了CPU」

方式2:使用top命令查看应用进程的CPU消耗情况

此外,由于Android是基于Linux内核改造而成的操作系统,自然而然也能使用Linux的一些常用命令。比如我们可以使用top命令查看哪些进程是 CPU 的主要消耗者。

从以上可知我们的Awesome-WanAndroid应用进程占用了15.6%的CPU。最后,这里再列举下最常用的top命令,如下所示:

方式3:使用PS软件

除了top命令可以比较全面地查看整体的CPU信息之外,如果我们只想「查看当前指定进程已经消耗的CPU时间占系统总时间的百分比或其它的状态信息」的话,可以使用ps命令,常用的ps命令如下所示:

其中输出参数的含义如下所示:

  • USER:用户名
  • PID:进程ID
  • PPID:父进程ID
  • VSZ:虚拟内存大小(1k为单位)
  • RSS:常驻内存大小(正在使用的页)
  • WCHAN:进程在内核态中的运行时间
  • Instruction pointer:指令指针
  • NAME:进程名字

最后的输出参数S表示的是进程当前的状态,总共有10种可能的状态,如下所示:

可以看到,我们当前主进程是休眠的状态。

方式4:使用dumpsys cpuinfo命令

使用dumpsys cpuinfo命令获得的信息比起top命令得到的信息要更加精炼,如下所示:

从上述信息可知,「第一行显示的是cpuload (负载平均值)信息」:Load: 1.92 / 1.59 / 0.97 这「三个数字表示逐渐变长的时间段(平均一分钟,五分钟和十五分钟)的平均值,而较低的数字则更好」。数字越大表示有问题或机器过载。需要注意的是,这里的「Load需要除以核心数」,比如我这里的系统核心数为8核,所以最终每一个单核CPU的Load为0.24 / 0.20 / 0.12,「如果Load超过1,则表示出现了问题」

此外,「占用系统CPU资源最高的是system_server进程」,而我们的wanandroid应用进程仅占用了 1.4%的CPU资源,其中有0.9%的是用户态所占用的时间,0.4%是内核态所占用的时间。最后,我们可以看到系统总占用的CPU时间是13%,这个值是根据「前面所有值加起来 / 系统CPU数」的处理的,也就是「104% /  8 = 13%」


其余分析指标

除了上述方式来分析系统 & 应用的CPU使用情况外,我们还应该关注 「卡顿率 & 卡顿树」 这两个指标。

1、卡顿率

卡顿也可以有其对应的UV、PV卡顿率,「UV就是Unique visitor,指的就是一台手机客户端为一个访客,00:00-24:00内相同的客户端只被计算一次」。而「PV即Page View,即页面浏览量或点击量」。所以UV、PV卡顿率的定义即为如下所示:

因为卡顿问题的采样规则跟内存问题是相似的,一般都是采取「抽样上报」的方式,并且都应该「按照单个用户来抽样」「一个用户如果命中采集,那么在一天内都会持续的采集数据」

2、卡顿树

我们可以实现卡顿的火焰图,即卡顿树,在一张图里就可以看到卡顿的整体信息。由于卡顿的具体耗时跟手机性能,还有当时的使用场景、环境等密切相关,而且卡顿问题在日活大的应用上出现的场景非常多,所以对于大于我们指定的卡顿阈值如1s\2s\3s时,我们就可以抛弃具体的耗时,只「按照相同堆栈出现的比例来聚合各类卡顿信息」。这样我们就能够「很直观地从卡顿树上看到到底哪些堆栈出现的卡顿问题最多」,以便于我们能够「优先去解决 Top 的卡顿问题」,达到使用最少的精力获取最大的优化效果的目的。

投稿作者:JsonChao,博客:https://jsonchao.github.io/


关于卡顿分析方法讲解到这里,下一篇,我将继续分享 Android卡顿优化方案「Carson每天利用碎片化时间为你解析一个Android知识难点」,长按扫描关注公众号,我们明天见哦!696898cc32de14d4061be4227419be1b.png


最后福利:学习资料赠送

c88a4796e4cac6c7f55939efeaff3674.png
  • 福利:由本人亲自撰写 & 整理的「Android学习方法资料」
  • 数量:10名
  • 参与方式:「点击文章右下角”在看“ -> 回复截图到公众号 即可,我将从中随机抽取」
26a6760a1262037edf3ed43641518e22.png 点击“在看”就能升职 & 加薪水哦! e5b17e1e84742d0b01b139f836e7d90e.gif