Android卡顿优化思考

前言

大家在平时使用手机的时候,是否遇到过我的网络明明很好,怎么一个页面半天跳转不过去,或者是,经常看到在玩王者荣耀和刺激战场时,画面都卡成ppt了,完全是ppt游戏。画面流畅度不够,掉帧特别严重。

基础知识

造成卡顿的原因可能有千百种,不过最终都会反映到 CPU 时间上。我们可以把 CPU 时间分为两种:用户时间和系统时间。

。用户时间就是执行用户态应用程序代码所消耗的时间;系统时间就是执行内核态系统调用所消耗的时间,包括 I/O、锁、中断以及其他系统调用的时间。

1. CPU 性能

我们先来简单讲讲 CPU 的性能,考虑到功耗、体积这些因素,移动设备和 PC 的 CPU 会有不少的差异。但近年来,手机 CPU 的性能也在向 PC 快速靠拢,华为 Mate 20 的“麒麟 980”和 iPhone XS 的“A12”已经率先使用领先 PC 的 7 纳米工艺。也因此在开发过程中,我们需要根据设备 CPU 性能来“看菜下饭”,例如线程池使用线程数根据 CPU 的核心数。

2. 卡顿问题分析指标

出现卡顿问题后,首先我们应该查看 CPU 的使用率。怎么查呢?我们可以通过/proc/stat得到整个系统的 CPU 使用情况,通过/proc/[pid]/stat可以得到某个进程的 CPU 使用情况。

首先看下手机相关的cpu信息

cat /proc/cpuinfo

4551 (com.xxx.test) S 762 761 0 0 -1 1077936448 (1~9)138821 2989 651 0 1161 399 3 2  (10~17)10 -10 81 0 180732865 1463504896 28512 18446744073709551615 (18~25)1 1 0 0 0 0 4612 0 38136 18446744073709551615 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0

解释

  1. pid: 进程ID.
  2. comm: task_struct结构体的进程名
  3. state: 进程状态, 此处为S
  4. ppid: 父进程ID (父进程是指通过fork方式,通过clone并非父进程)
  5. pgrp:进程组ID
  6. session:进程会话组ID
  7. tty_nr:当前进程的tty终点设备号
  8. tpgid:控制进程终端的前台进程号
  9. flags:进程标识位,定义在include/linux/sched.h中的PF_*, 此处等于1077952832
  10. minflt: 次要缺页中断的次数,即无需从磁盘加载内存页. 比如COW和匿名页
  11. cminflt:当前进程等待子进程的minflt
  12. majflt:主要缺页中断的次数,需要从磁盘加载内存页. 比如map文件
  13. majflt:当前进程等待子进程的majflt
  14. utime: 该进程处于用户态的时间,单位jiffies,此处等于166114
  15. stime: 该进程处于内核态的时间,单位jiffies,此处等于129684
  16. cutime:当前进程等待子进程的utime
  17. cstime: 当前进程等待子进程的utime
  18. priority: 进程优先级, 此次等于10.
  19. nice: nice值,取值范围[19, -20],此处等于-10
  20. num_threads: 线程个数, 此处等于221
  21. itrealvalue: 该字段已废弃,恒等于0
  22. starttime:自系统启动后的进程创建时间,单位jiffies,此处等于2284
  23. vsize:进程的虚拟内存大小,单位为bytes
  24. rss: 进程独占内存+共享库,单位pages,此处等于93087
  25. rsslim: rss大小上限

Linux环境下进程的CPU占用率

继续刚刚讲的CPU使用率,以上这个文章有给我们的列出CPU使用率的计算方式,分为单核和多核计算。
如果 CPU 使用率长期大于 60% ,表示系统处于繁忙状态,就需要进一步分析用户时间和系统时间的比例。对于普通应用程序,系统时间不会长期高于 30%,如果超过这个值,我们就应该进一步检查是 I/O 过多,还是其他的系统调用问题。

Android 是站在 Linux 巨人的肩膀上,虽然做了不少修改也砍掉了一些工具,但还是保留了很多有用的工具可以协助我们更容易地排查问题,这里我给你介绍几个常用的命令。例如,top 命令可以帮助我们查看哪个进程是 CPU 的消耗大户;vmstat 命令可以实时动态监视操作系统的虚拟内存和 CPU 活动;strace 命令可以跟踪某个进程中所有的系统调用。

除了 CPU 的使用率,我们还需要查看 CPU 饱和度 。CPU 饱和度反映的是线程排队等待 CPU 的情况,也就是 CPU 的负载情况。

CPU 饱和度首先会跟应用的线程数有关,如果启动的线程过多,容易导致系统不断地切换执行的线程,把大量的时间浪费在上下文切换,我们知道每一次 CPU 上下文切换都需要刷新寄存器和计数器,至少需要几十纳秒的时间。

我们可以通过使用vmstat命令或者/proc/[pid]/schedstat文件来查看 CPU 上下文切换次数,这里特别需要注意nr_involuntary_switches被动切换的次数

 cat /proc/4551/sched  (4551 ->pid) nr_voluntary_switches: 主动上下文切换次数,因为线程无法获取所需资源导致上下文切换,最普遍的是IO。  nr_involuntary_switches: 被动上下文切换次数,线程被系统强制调度导致上下文切换,例如大量线程在抢占CPU。  se.statistics.iowait_count:IO 等待的次数  se.statistics.iowait_sum: IO 等待的时间 

另外一个会影响 CPU 饱和度的是线程优先级,线程优先级会影响 Android 系统的调度策略,它主要由 nice 和 cgroup 类型共同决定。nice 值越低,抢占 CPU 时间片的能力越强。当 CPU 空闲时,线程的优先级对执行效率的影响并不会特别明显,但在 CPU 繁忙的时候,线程调度会对执行效率有非常大的影响。

需要注意是否存在高优先级的线程空等低优先级线程,例如主线程等待某个后台线程的锁。

以上是整个Andorid卡顿最根本的CPU相关知识。

Android卡顿排查工具

工欲善其事,必先利其器,我们要选择好针对我们app的分析工具,对此,Andorid也提供了相应工具,包括一些卡顿检测工具也很多,这里主要讲线下我们如何去检测并发现问题。我们调用Linux的命令比较繁琐,我们现在也可以获取到很多图形化的界面。

目前实现的流派主要有两种:

  • instrument

获取一段时间内所有函数的调用过程,可以通过分析这段时间内的函数调用流程,再进一步分析待优化的点。

  • sample

有选择性或者采用抽样的方式观察某些函数调用过程,可以通过这些有限的信息推测出流程中的可疑点,然后再继续细化分析。

一、TraceView

TraceView利用 Android Runtime 函数调用的 event 事件,将函数运行的耗时和调用关系写入 trace 文件中,所以他主要对应上述instrument流派的

使用方式:请在您希望系统开始记录跟踪数据的位置调用 startMethodTracing()
在调用中,您可以指定 .trace 文件的名称,系统会将它保存到一个特定于软件包的目录中,该目录专门用于保存目标设备上的永久性应用数据,与 getExternalFilesDir() 返回的目录相同,在大多数设备上都位于 ~/sdcard/ 目录中。此文件包含二进制方法跟踪数据,以及一个包含线程和方法名称的映射表。要停止跟踪,请调用 stopMethodTracing()

        mOpenTrace.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Debug.startMethodTracing("HughTrace");            }        });        mCloseTrace.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Debug.stopMethodTracing();            }        });                //在代码中调用            private void onMainLongTimeWait(){        try {            Thread.sleep(1000);        }catch (InterruptedException e){            e.printStackTrace();        }    }

也可以在对应生命周期进行处理。
接下来需要获取到生成的路径 sdcard/Android/data/com.xxx.app/
最后我们取出文件来放到profile来看下

我们可以看到这个方法调用时间和我们程序基本无差别
另外Traceview 对 release 包支持的都不太好,例如无法反混淆。所以在线下调试还是一个不错的选择,但是,traceview工具本身带来的性能开销过大,有时无法反映真实的情况。比如一个函数本身的耗时是 1 秒,开启 Traceview 后可能会变成 5 秒,而且这些函数的耗时变化并不是成比例放大。

二、Nanoscope

Uber开源的Nanoscope

由于Android studio在应用程序上启用检测后,它将阻止所有JIT / AOT编译代码运行。即,您的应用程序使用逐行解释器运行。这会使您的应用变慢几倍。除此之外,对于所检测的每个Java方法,还将执行数百条其他C ++行(某些示例行)。总体效果:使用AOSP内置的仪器时,您的应用程序运行速度降低了一个数量级。

为此,Nanoscope经过优化,可提供极低的配置开销和完整的仪器。使用Nanoscope时,您可以在应用程序和框架中看到每个Java方法的准确图片。

它的实现原理是直接修改 Android 虚拟机源码,在ArtMethod执行入口和执行结束位置增加埋点代码,将所有的信息先写到内存,等到 trace 结束后才统一生成结果文件。在使用过程可以明显感觉到应用不会因为开启 Nanoscope 而感到卡顿,但是 trace 结束生成结果文件这一步需要的时间比较长。

  • 限制
  1. 需要自己刷 ROM,并且当前只支持 Nexus 6P,或者采用其提供的 x86 架构的模拟器。(英趣只支持ARM架构,所以需要刷ROM比较麻烦)
  2. 默认只支持主线程采集,其他线程需要手动代码设置 Thread.currentThread().setName("START_TRACING");

三、systrace

systrace

systrace简介

systrace 是分析 Android 设备性能的主要工具。不过,它实际上是其他工具的封装容器:它是 atrace 的主机端封装容器,是用于控制用户空间跟踪和设置 ftrace 的设备端可执行文件,也是 Linux 内核中的主要跟踪机制。systrace 使用 atrace 来启用跟踪,然后读取 ftrace 缓冲区并将其全部封装到一个独立的 HTML 查看器中。

systrace 利用了 Linux 的ftrace调试工具,相当于在系统各个关键位置都添加了一些性能探针,也就是在代码里加了一些性能监控的埋点。Android 在 ftrace 的基础上封装了atrace,并增加了更多特有的探针,例如 Graphics、Activity Manager、Dalvik VM、System Server 等。

systrace 工具只能监控特定系统调用的耗时情况,所以它是属于 sample 类型,而且性能开销非常低。但是它不支持应用程序代码的耗时分析,所以在使用时有一些局限性

在考虑设备性能时,容量和抖动是两项重要指标。

  • 容量

容量是设备在一段时间内拥有的某种资源的总量。这种资源可以是 CPU 资源、GPU 资源、I/O 资源、网络资源、存储设备带宽或其他类似指标。

  • 抖动

抖动是一种随机的系统行为,会阻止可察觉任务的运行。通常是必须运行的任务,但可能对在任一特定时间运行没有严格的定时要求。因为抖动具有随机性,所以很难证明某一特定工作负载不存在抖动,也很难证明某已知抖动源是导致某个特定性能问题的原因。

  • 调度程序延迟
  • 中断处理程序
  • 驱动程序代码在抢占或中断被停用的情况下运行时间过长
  • 运行时间较长的软中断
  • 锁争用(应用、框架、内核驱动程序、Binder 锁、mmap 锁)
  • 文件描述符争用,低优先级的线程持有某个文件的锁,以防止高优先级线程运行
  • 在可能会延迟的工作队列中运行界面关键型代码
  • CPU 空闲转换
  • 记录
  • I/O 延迟
  • 创建不必要的进程(如 CONNECTIVITY_CHANGE 广播)
  • 释放内存不足所导致的页面缓存颠簸

systrace使用

如果您需要检查本机系统进程并解决帧丢失导致的界面卡顿问题,请在命令行中使用 systrace 或在 CPU Profiler 中使用经过简化的系统跟踪。CPU Profiler 提供了许多用于分析应用进程的功能。

输出10m app操作的trace文件

cd $ANDROID_HOME/platform-tools/systrace //需要先找到systrace这个目录//Android_HOME的路径//export ANDROID_HOME=/Users/macbook/Library/Android/sdk

找到对应的文件再继续执行以下内容

./systrace.py -t 10 sched gfx view wm am app webview -a com.hugh.basisStarting tracing (10 seconds)Tracing completed. Collecting output...Outputting Systrace results...Tracing complete, writing resultsWrote trace HTML file: file:///Users/macbook/Library/Android/sdk/platform-tools/systrace/trace.html//这边用10s来测试Graphics 在操作中我调用了动画

在chrome中可以看到相关数据

  • Frames

在每个app进程,都有一个Frames行,正常情况以绿色的圆点表示。当圆点颜色为黄色或者红色时,意味着这一帧超过16.6ms(即发现丢帧),这时需要通过放大那一帧进一步分析问题。对于Android 5.0(API level 21)或者更高的设备,该问题主要聚焦在UI Thread和Render Thread这两个线程当中。对于更早的版本,则所有工作在UI Thread。

如图

  • Alert

Systrace能自动分析trace中的事件,并能自动高亮性能问题作为一个Alerts,建议调试人员下一步该怎么做。

比如对于丢帧是,点击黄色或红色的Frames圆点便会有相关的提示信息;另外,在systrace的最右上方,有一个Alerts tab可以展开,这里记录着所有的的警告提示信息。

systrace2 命令

python systrace.py [options] [category1] [category2] ... [categoryN]

  • options
options 解释
-o 输出的目标文件
-t N, –time=N 执行时间,默认5s
-b N, –buf-size=N buffer大小(单位kB),用于限制trace总大小,默认无上限
-k ,–ktrace= 追踪kernel函数,用逗号分隔
–from-file= 从文件中创建互动的systrace
-l, –list-categories 列举可用的tags
  • category2
category 解释
gfx Graphics
input Input
webview WebView
wm Window Manager
audio Audio
video Video
camera Camera
res Resource Loading

………… 等等

由于系统预留了Trace.beginSection接口来监听应用程序的调用耗时,那我们有没有办法在 systrace 上面自动增加应用程序的耗时分析呢?

我们可以通过编译时给每个函数插桩的方式来实现,也就是在重要函数的入口和出口分别增加Trace.beginSection和Trace.endSection。当然出于性能的考虑,我们会过滤大部分指令数比较少的函数,这样就实现了在 systrace 基础上增加应用程序耗时的监控。通过这样方式的好处有:

  • 可以看到整个流程系统和应用程序的调用流程。
  • 包括系统关键线程的函数调用,例如渲染耗时、线程锁,GC 耗时等

4.Simpleperf

Simpleperf 文档

Simpleperf主要增对native函数,它可用于分析Android应用程序和在Android上运行的本机进程。它可以在Android上分析Java和C ++代码。

Simpleperf 同时封装了 systrace 的监控功能,通过 Android 几个版本的优化,现在 Simpleperf 比较友好地支持 Java 代码的性能分析。

我们在Android 的profiler当中也可以看到Simpleperf的工具

5.Android Profiler

在 Android Studio 3.2 的 Profiler 中直接集成了几种性能分析工具,其中:

Sample Java Methods 的功能类似于 Traceview 的 sample 类型。

Trace Java Methods 的功能类似于 Traceview 的 instrument 类型。

Trace System Calls 的功能类似于 systrace。

SampleNative (API Level 26+) 的功能类似于 Simpleperf。

Profiler大大降低了开发者的使用门槛,但支持配置的参数也大大不如命令行,也在某些方面有些局限

6.Call Chart 和 Flame Chart

上述的分析工具都支持了Call Chart 和 Flame Chart的展示,接下来来讲讲这两种展示方式

  • Call Chart

Call Chart 是 Traceview 和 systrace 默认使用的展示方式。它按照应用程序的函数执行顺序来展示,适合用于分析整个流程的调用。Call Chart选项卡提供一个方法跟踪的图形表示,其中一个方法调用(或调用者)的周期和时间在水平轴上表示,而它的callees则显示在垂直轴上。

举一个最简单的例子,A 函数调用 B 函数,B 函数调用 C 函数,循环三次,就得到了下面的 Call Chart。

  • Flame Chart

同样举个例子 A->B->C

当我们不想知道应用程序的整个调用流程,只想直观看出哪些代码路径花费的 CPU 时间较多时,火焰图就是一个非常好的选择。

通过图我们就能直观的看到某个函数的调用CPU时间。可以优化我们的序列化方案或者一些io操作等等。

总结

上述的Android 4大工具中,像systrace、Simpleperf 也是利用 Linux 提供的机制实现,因此学习一些 Linux 的基础知识,对于理解这些工具的工作原理以及排查性能问题,都有很大帮助。在使用工具的同时我们要理解工具是如何实现的,对我们分析或是之后做性能监控的组件都有很大的借鉴意义

更多相关文章

  1. 最近面试Android的一些面试题
  2. Android冷启动与热启动概念
  3. Android(安卓)or Linux 的休眠与唤醒
  4. Android性能优化之使用线程池处理异步任务
  5. Android(安卓)Unity3D 相互交互,及退出继承UnityPlayerActivity的
  6. 【Android】webView 使用 系统自带搜索对话框问题
  7. 用Android线程间通信的Message机制
  8. Android(安卓)4.3实现类似iOS在音乐播放过程中如果有来电则音乐
  9. Android下的一种编程框架

随机推荐

  1. Android(安卓)ndk探索之一(利用Android(安
  2. [Android]继承式UI界面布局设计
  3. (转)支付宝 Android(安卓)版使用的开源组件
  4. 别老说要买 iPhone6,三分钟就让你手机价值
  5. Android(安卓)SQLite 数据库详细介绍
  6. Android加载Bitmap出现OutofMemoryError
  7. 直接用 Chrome 扩展来回复 Android(安卓)
  8. Android中的dip,dp,sp,px
  9. Out of Milk Shopping List 用Android打
  10. Android(安卓)UI开发第十四篇――可以移