IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android Matrix之TraceCanary Wiki -> 正文阅读

[移动开发]Android Matrix之TraceCanary Wiki

一.怎么衡量流程性

1.帧率 FPS?

游戏60FPS ,电影 FPS 25FPS ~ 30FPS,人眼 12FPS

FPS 低并不意味着卡顿发生,而卡顿发生 FPS 一定不高。

2.掉帧程度可以更直观地衡量卡顿

16ms,如果一帧的准备时间超出这个值,则认为发生掉帧,假设每帧准备时间约 32ms,每次只掉一帧,那么 1 秒内实际只刷新 30 帧,即平均帧率只有 30FPS,但这时往往不会觉得是卡顿。反而如果出现某次严重掉帧(>300ms),那么这一次的变化,通常很容易感知到。所以界面的掉帧程度,往往可以更直观的反映出卡顿。

BestNormalMiddleHighFrozen
[0:3)[3:9)[9:24)[24:42)[42:∞)

?Activity 掉帧程度的分布情况及平均帧率 *?

相比单看平均帧率,掉帧程度的分布可以明显的看出,界面卡顿(平均帧率低)的原因是因为连续轻微的掉帧(下图1),还是某次严重掉帧造成的(下图2)。?

?再通过 Activity 区分不同场景,计算每个界面在有效绘制的时间片内,掉帧程度的分布情况及平均帧率,从而来评估出一个界面的整体流畅程度。?

二.原理

1.Vsync,Choreographer 编舞者

1.1?View 的绘制

View 的绘制会经过 Measure、Layout、Draw 这 3 个阶段,而这 3 个阶段的工作都是由 CPU 来负责完成。另外 CPU 还会负责一些用户输入、View 动画等事件,这部分工作都是在 UI 线程中完成。

当 CPU 绘制完成之后,会在 RederThread 线程中将这部分数据提交给 GPU。

最后手机屏幕再从这个 Buffer 中读取数据显示到屏幕上。实际上真正对 Buffer 中的数据进行合成显示到屏幕上的是 SurfaceFlinger。

1.2 Vsync 机制

screen refresh rate--屏幕刷新率。指的是手机屏幕每秒钟可以刷新多少次。目前在大多数的厂商手机上的屏幕刷新率是 60HZ,也就是以 16.6ms 进行一次刷新。
frame rate -- GPU 绘制帧率,指的是 GPU 每秒能够合成绘制多少帧。

Android 系统引入了 Vsync 机制。每隔 16ms 硬件层发出 vsync 信号,应用层接收到此信号后会触发 UI 的渲染流程,同时 vsync 信号也会触发 SurfaceFlinger 读取 Buffer 中的数据,进行合成显示到屏幕上。简单来讲就是将上面 CPU 和 GPU 的开始时间与屏幕刷新强行拖拽到同一起跑线。实现下图效果:

1.3 Choreographer 编舞者


那么软件层是如何接受硬件发出的 vsync 信号,并进行 View 绘制操作的呢?答案就是 Choreographer。

Choreographer 是一个承上启下的角色。

承上:接收应用层的各种 callback 输入,包括 input、animation、traversal 绘制。但是这些 callback 并不会被立即执行。而是会缓存在 Choreographer 中的 CallbackQueue 中。

启下:内部的 FrameDisplayEventReceiver 负责接收硬件层发成的 vsync 信号。当接收到 vsync 信号之后,会调用 onVsync 方法 -> doFrame -> doCallbacks,在 doCallbacks 中从 CallbackQueue 中取出进行绘制的 TraversalRunnable,并调用其 run 方法进行绘制。

通过这样一套机制,保证软件层和屏幕刷新处于同一频率,使 Android App 可以以一个比较稳定的帧率运行,减少因为频率波动造成“丢帧”的概率。

2.直接原因,主线程执行繁重的UI绘制、大量的计算或IO等耗时操作。

BlockCanaryArgusAPMLogMonitor?包括Matrix主要思想是,监控主线程执行耗时,当超过阈值时,dump出当前主线程的执行堆栈,通过堆栈分析找到卡顿原因。

3.从监控主线程的实现原理上,主要分为两种:Looper,Choreographer

3.1、依赖主线程 Looper,监控每次 dispatchMessage 的执行耗时。(BlockCanary)

public static void loop() {
    ...
    for (;;) {
        ...
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg);
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        ...
    }
}


3.2、依赖 Choreographer 模块,监控相邻两次 Vsync 事件通知的时间差。(ArgusAPM、LogMonitor)

Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    @Override    
    public void doFrame(long frameTimeNanos) {
        if(frameTimeNanos - mLastFrameNanos > 100) {
            ...
        }
        mLastFrameNanos = frameTimeNanos;
        Choreographer.getInstance().postFrameCallback(this);
    }
});


通过代理编译期间的任务 transformClassesWithDexTask,将全局 class 文件作为输入,利用?ASM?工具,高效地对所有 class 文件进行扫描及插桩。4.修改字节码的方式,在编译期修改所有 class 文件中的函数字节码,对所有函数前后进行打点插桩。

1、选择在该编译任务执行时插桩,是因为 proguard 操作是在该任务之前就完成的,意味着插桩时的 class 文件已经被混淆过的。而选择 proguard 之后去插桩,是因为如果提前插桩会造成部分方法不符合内联规则,没法在 proguard 时进行优化,最终导致程序方法数无法减少,从而引发方法数过大问题。

2、为了减少插桩量及性能损耗,通过遍历 class 方法指令集,判断扫描的函数是否只含有 PUT/READ FIELD 等简单的指令,来过滤一些默认或匿名构造函数,以及 get/set 等简单不耗时函数。

3、针对界面启动耗时,因为要统计从 Activity#onCreate 到 Activity#onWindowFocusChange 间的耗时,所以在插桩过程中需要收集应用内所有 Activity 的实现类,并覆盖 onWindowFocusChange 函数进行打点。

4、为了方便及高效记录函数执行过程,我们为每个插桩的函数分配一个独立 ID,在插桩过程中,记录插桩的函数签名及分配的 ID,在插桩完成后输出一份 mapping,作为数据上报后的解析支持。

5.运行时数据存储

编译期已经对全局的函数进行插桩,在运行期间每个函数的执行前后都会调用 MethodBeat.i/o 的方法,如果是在主线程中执行,则在函数的执行前后获取当前距离 MethodBeat 模块初始化的时间 offset(为了压缩数据,存进一个long类型变量中),并将当前执行的是 MethodBeat i或者o、mehtod id 及时间 offset,存放到一个 long 类型变量中,记录到一个预先初始化好的数组 long[] 中 index 的位置(预先分配记录数据的 buffer 长度为 100w,内存占用约 7.6M)。数据存储如下图:

通过向 Choreographer 注册监听,在每一帧 doframe 回调时判断距离上一帧的时间差是否超出阈值(卡顿),如果超出阈值,则获取数组 index 前的所有数据(即两帧之间的所有函数执行信息)进行分析上报。 同时,我们在每一帧 doFrame 到来时,重置一个定时器,如果 5s 内没有 cancel,则认为 ANR 发生,这时会主动取出当前记录的 buffer 数据进行独立分析上报,对这种 ANR 事件进行单独监控及定位。

另外,考虑到每个方法执行前后都获取系统时间(System.nanoTime)会对性能影响比较大,而实际上,单个函数执行耗时小于 5ms 的情况,对卡顿来说不是主要原因,可以忽略不计,如果是多次调用的情况,则在它的父级方法中可以反映出来,所以为了减少对性能的影响,通过另一条更新时间的线程每 5ms 去更新一个时间变量,而每个方法执行前后只读取该变量来减少性能损耗。

6.堆栈聚类问题? *(stackKey,找出超过帧消息时间30%的方法的id并用 | 连接起来)

如果将收集的原始数据进行上报,数据量很大而且后台很难聚类有问题的堆栈,所以在上报之前需要对采集的数据进行简单的整合及裁剪,并分析出一个能代表卡顿堆栈的 key,方便后台聚合。

通过遍历采集的 buffer ,相邻 i 与 o 为一次完整函数执行,计算出一个调用树及每个函数执行耗时,并对每一级中的一些相同执行函数做聚合,最后通过一个简单策略,分析出主要耗时的那一级函数,作为代表卡顿堆栈的key。

小结

最终,我们希望通过观察大盘整体的帧率及掉帧程度,来评估并监控一些重要场景的流畅性。

通过一个闭环的流程,利用 Matrix-TraceCanary 模块从客户端对卡顿进行捕捉与分析上报,通过后台聚类问题堆栈及版本对比,找到卡顿堆栈的责任人,通知其进行解决优化,而最终处理的效果也会在 Matrix 平台中反应出来。

https://mp.weixin.qq.com/s/iy5sU9NnqgzJweUFxxvrAg

它集成很方便,但需要搭建一套数据采集和分析系统,大大地增加了开发者的使用门槛。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-16 22:01:10  更:2021-07-16 22:01:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 0:17:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码