乐趣区

关于测试:App性能测试揭秘Android篇

阿里云 云原生利用研发平台 EMAS 李嘉华(千瞬)

简介: 性能测试在挪动测试畛域始终是一个大难题,它最直观的体现是用户在前台应用 App 时的主观体验,然而决定体验优劣的背地,波及到了许许多多的技术变迁。浏览此文,带你揭秘 App 性能测试。

前言

性能测试在挪动测试畛域始终是一个大难题,它最直观的体现是用户在前台应用 App 时的主观体验,然而决定体验优劣的背地,波及到了许许多多的技术变迁。

  • 当咱们习惯于诺基亚时,智能机呈现了;当咱们学会 native 开发时,hybrid 来了;当各种 hybrid 框架下的巨型利用偏向成熟时,小程序呈现在了咱们眼前;紧接着直播、iot、ar、vr、人工智能,新的技术与利用场景正在以无奈设想的速度向前倒退。性能测试技术在疾速变动的场景与开发技术背后,面临着微小的挑战,当咱们还在纠结如何测试 a 时,b 就曾经进去了。
  • 性能测试自身,有倒退日渐成熟的解决方案,如线上性能监控 APM、线下性能采集工具;有基于各个利用场景衍生的测试技术,如压力测试、稳定性测试、功耗测试等;也有基于各项性能指标(内存、cpu、电量、流量)而来的各种专项测试能力。

咱们致力于打造线上线下一体的性能解决方案,心愿可能帮忙开发者发现、定位与解决一系列挪动端性能问题。本文将着重介绍 EMAS 性能测试平台的能力与布局,还是那句话:性能决定当初,性能决定将来

通常咱们在采集 Android 设施性能数据时,都是通过 adb shell 获取各项零碎数据,对采集效率、数据准确度等影响很大。阿里云挪动测试做了大量技术优化翻新,目前性能测试采集距离为 1s,并且同时做到了无侵入、低提早、低功耗。

在介绍技术计划之前,这里将本文的计划(app_process)与 adb shell 的计划做一组简略的数据比照。

  • 采集的所有性能数据为:cpu、memory、fps、network
  • 开发环境: java + ddmlib
  • 测试电脑:MacBook Pro (Retina, 15-inch, Mid 2015) 上进行测试
  • 测试设施:OPPO R17/Android 8.0

只管比照的样本数不多,且不同实现形式也会有些许差别,但基于 app_process 的性能采集计划仍然有很显著的劣势:

  • 性能数据误差更小。相比之下性能与精度的晋升是不言而喻的,在局部手机上 app_process 的 cpu 开销甚至低于 1%;
  • 数据采集更及时、响应更快。因为 app_process 接口的高效性,咱们在每一秒钟都会监控被测利用的 pid,实际上性能数据对 APP 重启等动作的响应是实时的;
  • 兼容性更好。ps、top 等命令在不同的设施上可能存在数据格式上的差别,这类不同机型的适配问题在本文计划中是不存在的。

1. APP_PROCESS

在 Android 零碎中,zygote 通过 fork()调用一个 app_process 过程作为 App 的载体,咱们同样也能够通过 app_process 运行一个一般的 java 程序,这个 java 程序能够像 App 一样通过 binder 跨过程与 system_server 通信,实现并调用一些 Android 零碎服务的接口,同时,通过 app_process 启动的程序领有 shell 等同的权限,这样能够实现一些 app 无权限然而 adb 可能实现的命令。

通过下图咱们简略了解一下 Android Binder 与本文的基本原理,更多细节能够自行搜寻学习。通常来说,如果咱们的 App 可能获取到一个 Manager(如 ActivityManager),那么 System_Server 中必然存在对应的 Service(如 ActivityManagerService),那么咱们就能够通过 ActivityManagerProxy 与它通信。

2. 性能指标

目前 挪动测试 性能测试平台反对采集的性能指标如下:

2.1 内存

指标阐明

  • TotalPss: 利用理论占用物理内存
  • NativePss: native 过程申请调配的物理内存
  • SwapPss: 动态内存替换区,zRAM 替换可通过压缩内存页面并将其放入动态分配的内存替换区来减少零碎中的可用内存量。因为这是以就义 CPU 工夫为代价来减少大量内存,所以 swapPss 的异样变动可能对系统性能造成影响。(https://source.android.com/devices/tech/config/low-ram.html))

原理

通过 adb shell dumpsys meminfo pid,咱们能够取得如下内容

Applications Memory Usage (in Kilobytes):
Uptime: 543447125 Realtime: 543469686
** MEMINFO in pid 23178 [com.huawei.browser:sandboxed_process0:com.huawei.browser.sandbox.SandboxedProcessService0:6] **
                   Pss  Private  Private  SwapPss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap       99       96        0     2028     6656     4327     2328
  Dalvik Heap        4        0        0      754     3078     1030     2048
 Dalvik Other        4        4        0      366
        Stack        8        8        0       26
    Other dev        4        0        4        0
     .so mmap      535        4        0      319
    .jar mmap      114        0        0        0
    .apk mmap        2        0        0        0
    .dex mmap      622        0        4     2617
    .oat mmap      409        0        0        0
    .art mmap      259       16        0     2183
   Other mmap       14        0        0        6
      Unknown       28       28        0      455
        TOTAL    10856      156        8     8754     9734     5357     4376
 App Summary
                       Pss(KB)
                        ------
           Java Heap:       16
         Native Heap:       96
                Code:        8
               Stack:        8
            Graphics:        0
       Private Other:       36
              System:    10692
               TOTAL:    10856       TOTAL SWAP PSS:     8754

在 Android 10 以下的设施中,咱们能够通过 activityManager.getProcessMemoryInfo(pids) 获取过程相干的内存信息,Android 10 之后的系统对这个接口加了一些限度,数据更新工夫为 5 分钟,须要间接调用 meminfo service 来 dump 获取这部分内容。

2.2 CPU

指标阐明

ProcessCpu:测试过程 CPU 使用率
SystemCpu:整机 CPU 使用率

原理

通过读取 /proc/stat 文件,咱们能够看到上面的内容

cpu  2490696 175785 2873834 17973539 12823 680472 230184 0 0 0
cpu0 621631 33199 739364 12893642 10736 365458 86720 0 0 0
cpu1 623944 30576 688904 677748 609 145744 93230 0 0 0
cpu2 519768 33948 650022 685194 703 78117 23873 0 0 0
cpu3 499978 33082 547153 687802 650 81072 21360 0 0 0
cpu4 32586 4853 41910 774975 36 2097 1025 0 0 0
cpu5 30950 5003 40730 776693 19 2060 999 0 0 0
cpu6 99227 22708 109219 722048 23 3970 2140 0 0 0
cpu7 62610 12414 56531 755434 44 1952 836 0 0 0
intr 209333749 0 0 0 0 35952688 0 11796562 7 5 5 17537 80 2431 0 0 0 1069962 0 35 1334360 0 0 0 0 0 11 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 34984538 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 505 50695 1174791 345 0 0 0 11301652 24660 0 111 0 0 0 0 0 0 0 0 0 0 0 86153 54 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1099230 0 18 1814 0 0 23 514624 1300943 248469 0 0 0 0 0 97168 60709 1641967 609754 38618 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 519 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1556 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 18 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 0 3548401 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 18 0 0 163911 192365 0 0 0 0 1018 0 1 0 2 0 2 0 2 1 0 0 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 56891 4227 147 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 751521 0 0 200 0 0 0 0 0 0 0 0 0 0 0 0 0 27 26 26 0 34 50 330 34 0 0 0 0 0 0 0 0 1223 0 11 0 0 0 26

对于上述 cpu 数据来说,每行 CPU 的数字顺次示意

user (14624) 从系统启动开始累计到以后时刻,处于用户态的运行工夫 
nice (771) 从系统启动开始累计到以后时刻 system (8484) 从系统启动开始累计到以后时刻,处于外围态的运行工夫 
idle (283052) 从系统启动开始累计到以后时刻,除 IO 等待时间以外的其它等待时间 
iowait (0) 从系统启动开始累计到以后时刻,IO 等待时间 irq (0) 从系统启动开始累计到以后时刻,硬中断工夫
softirq (62) 从系统启动开始累计到以后时刻,软中断工夫
  • 咱们能够失去 cpu 运行时长为 cpu = user + nice + system + iowait + irq + softirq,而总时长为 cpu_total = cpu + idle
  • 因为咱们的采集距离简直等于 1s,于是过来 1s 的设施整体 cpu 使用率为 (cpu – cpu_pre) / (cpu_total – cpu_total_pre)

通过读取 /proc/pid/stat 文件,咱们能够看到上面的内容

6873 (a.out) R 6723 6873 6723 34819 6873 8388608 77 0 0 0 41958 31 0 0 25 0 3 0 5882654 1409024 56 4294967295 134512640 134513720 3215579040 0 2097798 0 0 0 0 0 0 0 17 0 0 0

这里数据较多,顺次示意

pid=6873 过程 (包含轻量级过程,即线程) 号
comm=a.out 应用程序或命令的名字
task_state=R 工作的状态,R:runnign, S:sleeping (TASK_INTERRUPTIBLE), D:disk sleep (TASK_UNINTERRUPTIBLE), T: stopped, T:tracing stop,Z:zombie, X:dead
ppid=6723 父过程 ID
pgid=6873 线程组号
sid=6723 c 该工作所在的会话组 ID
tty_nr=34819(pts/3) 该工作的 tty 终端的设施号,INT(34817/256)= 主设施号,(34817- 主设施号)= 次设施号
tty_pgrp=6873 终端的过程组号,以后运行在该工作所在终端的前台工作 (包含 shell 应用程序) 的 PID。task->flags=8388608 过程标记位,查看该工作的个性
min_flt=77 该工作不须要从硬盘拷数据而产生的缺页(次缺页)的次数
cmin_flt=0 累计的该工作的所有的 waited-for 过程已经产生的次缺页的次数目
maj_flt=0 该工作须要从硬盘拷数据而产生的缺页(主缺页)的次数
cmaj_flt=0 累计的该工作的所有的 waited-for 过程已经产生的主缺页的次数目
utime=41958 该工作在用户态运行的工夫,单位为 jiffies
stime=31 该工作在外围态运行的工夫,单位为 jiffies
cutime=0 累计的该工作的所有的 waited-for 过程已经在用户态运行的工夫,单位为 jiffies
cstime=0 累计的该工作的所有的 waited-for 过程已经在外围态运行的工夫,单位为 jiffies
priority=25 工作的动静优先级
nice=0 工作的动态优先级
num_threads=3 该工作所在的线程组里线程的个数
it_real_value=0 因为计时距离导致的下一个 SIGALRM 发送过程的时延,以 jiffy 为单位.
start_time=5882654 该工作启动的工夫,单位为 jiffies
vsize=1409024(page)该工作的虚拟地址空间大小
rss=56(page) 该工作以后驻留物理地址空间的大小
Number of pages the process has in real memory,minu 3 for administrative purpose.
这些页可能用于代码,数据和栈。rlim=4294967295(bytes)该工作能驻留物理地址空间的最大值
start_code=134512640 该工作在虚拟地址空间的代码段的起始地址
end_code=134513720 该工作在虚拟地址空间的代码段的完结地址
start_stack=3215579040 该工作在虚拟地址空间的栈的完结地址
kstkesp=0 esp(32 位堆栈指针) 的以后值, 与在过程的内核堆栈页失去的统一.
kstkeip=2097798 指向将要执行的指令的指针, EIP(32 位指令指针)的以后值.
pendingsig=0 待处理信号的位图,记录发送给过程的一般信号
block_sig=0 阻塞信号的位图
sigign=0 疏忽的信号的位图
sigcatch=082985 被俘获的信号的位图
wchan=0 如果该过程是睡眠状态,该值给出调度的调用点
nswap 被 swapped 的页数,以后没用
cnswap 所有子过程被 swapped 的页数的和,以后没用
exit_signal=17 该过程完结时,向父过程所发送的信号
task_cpu(task)=0 运行在哪个 CPU 上
task_rt_priority=0 实时过程的绝对优先级别
task_policy=0 过程的调度策略,0= 非实时过程,1=FIFO 实时过程;2=RR 实时过程

通过 app_process 更优雅

实际上咱们并不确定上述数据格式在不同的零碎版本或者机型上是否存在兼容性,这也是潜在的危险。
而通过 app_process 咱们能够间接反射调用 Process 的 readProcFile 接口,很容易取得 utime 与 stime,这样就齐全打消了兼容性问题危险。

  • 因为咱们的采集距离为 1s,能够计算过程 cpu 使用率为 ((utime + stime) – (utime_pre + stime_pre)) / (cpu_total – cpu_total_pre)

接口调用伪代码如下

Method readProcFile = android.os.Process.class.getMethod("readProcFile", String.class, int[].class, String[].class, long[].class, float[].class);
readProcFile.setAccessible(true);
readProcFile.invoke(null, statFile, PROCESS_STATS_FORMAT, null, statsData, null);
readProcFile.invoke(null, "/proc/stat", SYSTEM_CPU_FORMAT, null, sysCpu, null);

2.3 流量

指标阐明

recv:被测利用的上行流量
send:被测利用的上行流量

原理

通过读取 /proc/pid/net/dev 文件(低版本间接应用接口 TrafficStats.getUidRxBytes()),咱们能够取得如下数据,其中 wlan0 示意 wifi 流量,rmnet0 示意 sim 卡流量

解析 /proc/%d/net/dev 示例后果
Inter-|   Receive                                                |  Transmit
face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
rmnet4:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_tun03:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_r_ims01:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_tun02:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
dummy0:       0       0    0    0    0     0          0         0     1610      23    0    0    0     0       0          0
rmnet2:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_tun11:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_ims00:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_tun10:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_emc0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_tun13:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_tun00:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_tun04:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet5:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
wlan0: 1241518561  840807    0    0    0     0          0         7  7225770   73525    0    6    0     0       0          0
rmnet_r_ims00:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet3:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_tun01:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
sit0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_tun14:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
ip_vti0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
ip6tnl0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet1:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
ip6_vti0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_r_ims11:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_r_ims10:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet6:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
rmnet_tun12:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
lo: 3796620     292    0    0    0     0          0         0  3796620     292    0    0    0     0       0          0
rmnet_ims10:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

2.4 晦涩度

指标阐明

fps:用户可见的每秒显示帧数
jank:卡顿产生次数

FPS 原理

对于 fps 的统计存在很多个版本,基于不同计划统计的 fps 其含意齐全不一样。
这里次要讲讲 挪动测试 在 fps 上的抉择与计划:

Choreographer

Choreographer 须要在 App 中实现,常见于 APM 等性能监控计划上,简略介绍一下原理:

  1. Vsync 信号一般来说由硬件产生,负责产生硬件 Vsync 的是 HWC;
  2. DispSync 将 Vsync 生成 VSYNC_APP 和 VSYNC_SF 信号,之后由 Choreographer 和 SurfaceFlinger 应用;
  3. SurfaceFlinger 收到 VSYNC_SF 信号,开始第 N-1 帧的合成与绘制;
  4. App 收到 VSYNC_APP 信号,开始第 N 帧渲染;

因为大部分设施都是 60HZ 的刷新频率,所以 VSYNC 信号的周期通常是 16.6ms,这个信号周期的长短能够很直观的反映利用代码实现的性能。但如果 App 处于“静止”状态,VSYNC 信号仍然会继续产生,这时 GPU 绘制可能并未理论产生,这个统计值通常高于咱们视觉看到的帧数。fps 的定义为“每秒显示帧数”或“赫兹”,一般来说 FPS 用于形容影片、电子绘图或游戏每秒播放多少帧。

所以我更加偏向于 Choreographer 采集的 VSYNC 信号是一个晦涩度指标(SM),而非实在 FPS。

SurfaceFlinger

SurfaceFlinger 承受来自多个数据源的数据缓冲区数据,通过 GPU 合成并发送给显示设施。这是咱们通常形容的 fps,也是客户实在可视可体验到的的帧数数据。

在安卓零碎中,WindowManagerService 会对每一个 contentView 创立相干的 UI 载体 Surface,SurfaceFlinger 次要负责将这个 Surface 渲染到手机屏幕上。

除了 Android 主窗口的焦点 Activity 与绝对应的 ContentView 之外,还存在一种非凡的 SurfaceView, 他会独享一个 Surface,这个 Surface 独立渲染十分高效,反对 OpenglES 渲染。也就是说可能会呈现两类窗口 fps。一个是 Activity 窗口帧率和 SurfaceView 窗口帧率。

一般来说,游戏、视频类利用都是通过这种 SurfaceView 来进行绘制,为了可能尽可能精确的获取被测利用的帧率,咱们默认优先获取 SurfaceView 的 FPS。

如下是 优酷视频 咱们能获取到的 Surface 如下:

而后,再通过 dumpsys SurfaceFlinger –latency SurfaceView com.youku.phone/com.youku.ui.activity.DetailActivity 能够精确获取到视频窗口的帧绘制信息。

采纳以上办法统计 fps 通常会有以下疑难:

  • SurfaceFlinger 必须始终显示内容,所以当下层并没有新的缓存数据时,SurfaceFlinger 会持续显示以后数据,因而通过这种办法统计进去的 fps 值个别较低,动态页面可能为 0,这样的 fps 值是否具备迷惑性;
  • 比方上图的视频利用只有 25fps,咱们滑动时可能都有 40+ fps,这个数据能阐明什么样的问题;
  • fps 稳定范畴这么大,fps 值怎么才能够形容利用的晦涩水平。

答复下面的问题,首先要须要从新定义 FPS != 晦涩度。

这里援用苹果 WDDC2018 开发者大会的一个分享(https://developer.apple.com/videos/play/wwdc2018/612/))。左图试图以 60fps 运行程序,理论只能达到 40fps,而右图实现了稳固的 30fps,右图的晦涩度是显著要高于左图的,这种景象称为 Micro Stuttering。

这里不再深究 Micro Stuttering 的产生起因,回到 FPS 自身,首先 FPS 并不是越高越好,也不是越低就越差。它反映的是一种视觉惯性景象,FPS 值该当是越稳固越好。正如后面优酷视频的例子中,FPS 根本稳固在 25 左右,同样的在各类视频利用中,咱们发现 FPS 简直都是稳固在 20+,这曾经足够给咱们带来良好的观看体验了。

视觉惯性

视觉预期帧率,用户潜意识里认为下帧也应该是以后帧率,比方咱们玩游戏始终是 60 帧,用户潜意识里认为下帧也应该是 60 帧率。刷新始终是 25 帧,用户潜意识里认为下帧也应该是 25 帧率。然而如果 60 帧一下跳变为 25 帧,就会产生显著的卡顿感。

电影帧

电影帧率个别是 24 帧。电影帧单帧耗时为 1000ms/24≈41.67ms。电影帧率是一个临界点。低于这个帧率,人眼能够感觉出画面的不连续性。

JANK 原理

既然 fps 无奈残缺的形容利用的晦涩度,那么是否能够有一个指标示意利用的晦涩水平,换言之,是否形容利用的卡顿水平。答案是 jank。

了解 jank,就肯定要了解 google 设计的三重缓存机制(如下)。三重缓存指的是 A、B、C 三个缓存构造,当 GPU 未能在一次 VSync 工夫内实现 B 的解决,此时 display、gpu、cpu 同时在解决 A、B、C 三个缓存,实现资源最大化的利用。

咱们能够通过 dumpsys gfxinfo packageName 获取到的 janky frames 如下。这里的 Janky frames 是当一帧的工夫大于 16.67ms 时,就计为一次 Janky frame。

从上文提到的三重缓存机制咱们能够进行剖析,B 先导致了一次视觉上的 jank,C 实践上也是 jank(跨 VSync),然而因为此时屏幕上显示的是 B,C 尽管 delay 了一帧,然而 C 看起来依然是紧跟着 B 显示在屏幕上,而且 A 顺利的在 16.67ms 实现了绘制,实际上用户视觉上只少看了一帧,而 Janky frames 是 2。咱们发现,当 Janky frames 高达近 40% 甚至 50% 时,咱们仍然感触不到卡顿,这个值并不是现实中的反映晦涩度的指标。

Applications Graphics Acceleration Info:
Uptime: 171070276 Realtime: 962775383
** Graphics info for pid 13422 [com.zhongduomei.rrmj.society] **
Stats since: 152741070392878ns
Total frames rendered: 110
Janky frames: 7 (6.36%)
50th percentile: 9ms
90th percentile: 13ms
95th percentile: 18ms
99th percentile: 36ms
Number Missed Vsync: 2
Number High input latency: 0
Number Slow UI thread: 6
Number Slow bitmap uploads: 3
Number Slow issue draw commands: 0

基于以上思考,咱们从新定义 jank 的计算形式:

  • 视觉连续性问题:帧时长 > 前三帧均匀时长 *2
  • 卡顿问题:帧时长 > 电影帧时长 * 2

假如利用依照电影帧 41.67ms 运行,若帧时长大于 2*41.67ms,意味着在缓存机制下,仍然必现一次卡顿问题。

3. 其它接口

通过 app_process,咱们还可能实现很多其它乏味的事件。

  • 取得已装置利用列表。通过 android.content.pm.PackageManager,咱们能够取得所有已装置利用,同时取得所有利用的图标,也能够提前取得应用程序的所有 Service,目前小程序大多应用 Service 层来做逻辑解决、数据申请以及接口调用,提前确认要测试的 Service 能够更加准确的实现小程序测试;
  • 取得更多的设施硬件信息,如 gpu 信息等等;
  • 获取设施视音频流;
  • 局部实现静默装置等。
退出移动版