温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Android中如何优化卡顿

发布时间:2021-06-29 16:09:19 来源:亿速云 阅读:110 作者:Leah 栏目:移动开发

本篇文章为大家展示了Android中如何优化卡顿,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

  1. CPU 性能
    在开发过程中,我们可以通过下面的方法获得设备的 CPU 信息。
    // 获取 CPU 核心数
    cat /sys/devices/system/cpu/possible

    // 获取某个 CPU 的频率
    cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq

  2. 卡顿问题分析指标
    出现卡顿问题后,首先我们应该查看CPU 的使用率。怎么查呢?我们可以通过/proc/stat
    得到整个系统的 CPU 使用情况,通过/proc/[pid]/stat
    可以得到某个进程的 CPU 使用情况。
    关于 stat 文件各个属性的含义和 CPU 使用率的计算,你可以阅读《Linux 环境下进程的 CPU 占用率》和Linux 文档。其中比较重要的字段有:
    proc/self/stat:
    utime:       用户时间,反应用户代码执行的耗时  
    stime:       系统时间,反应系统调用执行的耗时
    majorFaults:需要硬盘拷贝的缺页次数
    minorFaults:无需硬盘拷贝的缺页次数

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

此外也可以通过 uptime 命令可以检查 CPU 在 1 分钟、5 分钟和 15 分钟内的平均负载。比如一个 4 核的 CPU,如果当前平均负载是 8,这意味着每个 CPU 上有一个线程在运行,还有一个线程在等待。一般平均负载建议控制在“0.7 × 核数”以内。
00:02:39 up 7 days, 46 min,  0 users,  
load average: 13.91, 14.70, 14.32

另外一个会影响 CPU 饱和度的是线程优先级,线程优先级会影响 Android 系统的调度策略,它主要由 nice 和 cgroup 类型共同决定。nice 值越低,抢占 CPU 时间片的能力越强。当 CPU 空闲时,线程的优先级对执行效率的影响并不会特别明显,但在 CPU 繁忙的时候,线程调度会对执行效率有非常大的影响。
关于线程优先级,你需要注意是否存在高优先级的线程空等低优先级线程,例如主线程等待某个后台线程的锁。从应用程序的角度来看,无论是用户时间、系统时间,还是等待 CPU 的调度,都是程序运行花费的时间。
Android 卡顿排查工具
Traceview 和 systrace 都是我们比较熟悉的排查卡顿的工具,从实现上这些工具分为两个流派。
第一个流派是 instrument。获取一段时间内所有函数的调用过程,可以通过分析这段时间内的函数调用流程,再进一步分析待优化的点。
第二个流派是 sample。有选择性或者采用抽样的方式观察某些函数调用过程,可以通过这些有限的信息推测出流程中的可疑点,然后再继续细化分析。
这两种流派有什么差异?我们在什么场景应该选择哪种合适的工具呢?还有没有其他有用的工具可以使用呢?下面我们一一来看。

  1. Traceview
    Traceview是我第一个使用的性能分析工具,也是吐槽的比较多的工具。它利用 Android Runtime 函数调用的 event 事件,将函数运行的耗时和调用关系写入 trace 文件中。
    由此可见,Traceview 属于 instrument 类型,它可以用来查看整个过程有哪些函数调用,但是工具本身带来的性能开销过大,有时无法反映真实的情况。比如一个函数本身的耗时是 1 秒,开启 Traceview 后可能会变成 5 秒,而且这些函数的耗时变化并不是成比例放大。
    在 Android 5.0 之后,新增了startMethodTracingSampling
    方法,可以使用基于样本的方式进行分析,以减少分析对运行时的性能影响。新增了 sample 类型后,就需要我们在开销和信息丰富度之间做好权衡。

无论是哪种的 Traceview 对 release 包支持的都不太好,例如无法反混淆。其实 trace 文件的格式十分简单,之前曾经写个一个小工具,支持通过 mapping 文件反混淆 trace。

  1. systrace
    systrace是 Android 4.1 新增的性能分析工具。我通常使用 systrace 跟踪系统的 I/O 操作、CPU 负载、Surface 渲染、GC 等事件。
    systrace 利用了 Linux 的ftrace调试工具,相当于在系统各个关键位置都添加了一些性能探针,也就是在代码里加了一些性能监控的埋点。Android 在 ftrace 的基础上封装了atrace,并增加了更多特有的探针,例如 Graphics、Activity Manager、Dalvik VM、System Server 等。
    systrace 工具只能监控特定系统调用的耗时情况,所以它是属于 sample 类型,而且性能开销非常低。但是它不支持应用程序代码的耗时分析,所以在使用时有一些局限性。
    由于系统预留了Trace.beginSection
    接口来监听应用程序的调用耗时,那我们有没有办法在 systrace 上面自动增加应用程序的耗时分析呢?
    划重点了,我们可以通过编译时给每个函数插桩的方式来实现,也就是在重要函数的入口和出口分别增加Trace.beginSection
    和Trace.endSection
    。当然出于性能的考虑,我们会过滤大部分指令数比较少的函数,这样就实现了在 systrace 基础上增加应用程序耗时的监控。通过这样方式的好处有:
    •   可以看到整个流程系统和应用程序的调用流程。包括系统关键线程的函数调用,例如渲染耗时、线程锁,GC 耗时等。
    •   性能损耗可以接受。由于过滤了大部分的短函数,而且没有放大 I/O,所以整个运行耗时不到原来的两倍,基本可以反映真实情况。
    systrace 生成的是 HTML 格式的结果。

目前两个工具都只支持 debugable 的应用程序,如果想测试 release 包,需要将测试机器 root。对于这个限制,我们在实践中会专门打出 debugable 的测试包,然后自己实现针对 mapping 的反混淆功能。

随着 Android 版本的演进,Google 不仅提供了更多的性能分析工具,而且也在慢慢优化现有工具的体验,使功能更强大、使用门槛更低。而 Android Studio 则肩负另外一个重任,那就是让开发者使用起来更加简单的,图形界面也更加直观。
在 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 界面在某些方面不如这些工具自带的界面,支持配置的参数也不如命令行,不过 Profiler 的确大大降低了开发者的使用门槛。
另外一个比较大的变化是分析结果的展示方式,这些分析工具都支持了 Call Chart 和 Flame Chart 两种展示方式。下面我来讲讲这两种展示方式适合的场景。

  1. Call Chart
    Call Chart 是 Traceview 和 systrace 默认使用的展示方式。它按照应用程序的函数执行顺序来展示,适合用于分析整个流程的调用。举一个最简单的例子,A 函数调用 B 函数,B 函数调用 C 函数,循环三次,就得到了下面的 Call Chart。

Call Chart 就像给应用程序做一个心电图,我们可以看到在这一段时间内,各个线程的具体工作,比如是否存在线程间的锁、主线程是否存在长时间的 I/O 操作、是否存在空闲等。

  1. Flame Chart
    Flame Chart 也就是大名鼎鼎的火焰图。它跟 Call Chart 不同的是,Flame Chart 以一个全局的视野来看待一段时间的调用分布,它就像给应用程序拍 X 光片,可以很自然地把时间和空间两个维度上的信息融合在一张图上。上面函数调用的例子,换成火焰图的展示结果如下。

当我们不想知道应用程序的整个调用流程,只想直观看出哪些代码路径花费的 CPU 时间较多时,火焰图就是一个非常好的选择。例如,之前我的一个反序列化实现非常耗时,通过火焰图发现耗时最多的是大量 Java 字符串的创建和拷贝,通过将核心实现转为 Native,最终使性能提升了很多倍。

上述内容就是Android中如何优化卡顿,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI