基于Perfetto 解读一帧的生产消费流程 Android >= S Qualcomm

广告

首先帮我朋友打个广告 我们一起在运营一个视频号 感兴趣的可以帮忙点击右边这个小铃铛 铃铛

1.这个流程里面的东西如果展开其实是有很多的 内容其实还是比较浅显的 sf处就不贴源码了 关一个Vsync就有的解释 当然笔者在流程上先形成一个思维闭环
2.如有小伙伴需要 笔者可提供所有原材料供二次编辑

先吐槽
其实我觉得大部分Android开发者都是聚集在上层 java层 或者说的具体点就是业务层 app层 始终没有脱离业务场景
我对应用开发范围的定义是 不限于hal层 c++代码实现层 只要涉及到业务场景的 都是应用开发
随着工作中遇到的一些00后 水平是真的不错 在这里也提醒那些80后90后 快了奥 小心被挤下来 逆水行舟 不进则退 出来混是要还的

本文阐述的预期
1.view的绘制流程 以及 送显到屏幕一整个过程
2.trace的分析方法
3.因为很多看似一点思路都没有的问题 其实是基础不够牢靠 希望笔者接下来的阐述 前期可以让大家节省多的熟悉成本

一.从一个view的setText开始

1.1view开始setText

Button btnTraceClick = findViewById(R.id.btn_trace_click);btnTraceClick.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Trace.beginSection("super.yu click#btn test");btnTraceClick.setText("帅是内在 但骚不是");Trace.endSection();}
});

可以看到2处 是加上去的trace setText就从这里开始 是会走下去请求vsync-app 即app主线程有更新ui的请求 但此时没有往下走 因为1处已经有一个requestNextVsync vsync-app的请求 等待sf进程回调上来 Choreographer#onVsync 告诉app可以doFrame 此时才会绘制 4处是线程运行状态

如果长时间的runnable或runnable preempted或running状态 60帧 超过16.6ms 那就可以看做是一个卡顿或掉帧 优化的思路可以是此处cpu有哪个进程运行时间较长 app线程得不到调度 负载较高 找对应模块的人分析 或修改优先级 等 如果是system_server例如binder 锁竞争 耗时 则要通过阅读源码去定位 或 app自身是否存在主线程耗时 出现诸如 下述log 考虑是否mainthread有耗时操作 ui结构过于复杂等等 思路不仅限于此 在Perfetto可以很直观的看出来

I/Choreographer: Skipped 196 frames! The application may be doing too much work on its main thread. 

在这里插入图片描述
分别对应2和3处

此时由于已经在1处requestNextVsync vsync-app请求 在2更新ui就不会往下走 所以只会有句scheduleTraversals 所以3处的onVsync回调其实是上一次ui更新请求的 所以ui的请求一直到屏幕显示至少得在第二个vsync信号到来

在这里插入图片描述
在这里插入图片描述
我们从这里的vsync请求往下赘述也是对应1处
在这里插入图片描述

/frameworks/base/core/java/android/view/ViewRootImpl.java#scheduleTraversals
void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;// 发送一个屏障信号 下次loop来 doFramemTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();// 编舞者 post发送请求mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);...
/frameworks/base/core/java/android/view/Choreographer.java#postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,Object action, Object token, long delayMillis) {...synchronized (mLock) {final long now = SystemClock.uptimeMillis();final long dueTime = now + delayMillis;mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);// dueTime 肯定是大于或等于now 所以除了首次一个loop会直接走这里 其他情况会走下述的msgif (dueTime <= now) {scheduleFrameLocked(now);} else {Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);msg.arg1 = callbackType;msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, dueTime);}...
|
void doScheduleCallback(int callbackType) {synchronized (mLock) {if (!mFrameScheduled) {final long now = SystemClock.uptimeMillis();// 此处 肯定是在Choreographer注册了一个回调 我们先不关注他 这个回调就是后面3Choreographer#onVsync 也就是 vsync-appif (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {scheduleFrameLocked(now);...
|
private void scheduleVsyncLocked() {try {// 这里的trace会和图上一一对上Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");mDisplayEventReceiver.scheduleVsync();} ...
中间省略一步hal也就是DisplayEventDispatcher.java
/frameworks/native/libs/gui/DisplayEventDispatcher.cpp#scheduleVsync
status_t DisplayEventDispatcher::scheduleVsync() {if (!mWaitingForVsync) {ALOGV("dispatcher %p ~ Scheduling vsync.", this);...if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount, &vsyncEventData)) {ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "", this,ns2ms(static_cast<nsecs_t>(vsyncTimestamp)));}// app层开始请求vsync 此处开启binder请求status_t status = mReceiver.requestNextVsync();...

一步一步从java层 到c++ vsync-app 整个流程还是比较长的 大家可以看出 其实Java就是个壳子 他可以是kotlin可以是js也可以是Flutter 因为硬件屏幕刷新是固定 不需要每次都去校对硬件vsync当前是会否可以绘制 所以模拟一个软件的信号源 在这里把代码贴出来 可以看一下 其实就是代码流程 当然怎么去注册的 怎么回调的我们在此就不去深究了

此时3处回调vsync-app 也就是Choreographer#onVsync app就开始绘制了 其实绘制本质就是在组织结构体 组织绘制的命令 在这里我们留一个后续探究的点 也就是一个canvas gui的本质是什么 我们都知道java层的bitmap的draw api的调用方式 但java就是个壳子 真正绘制不是在这里 相当于aidl绑定两个connection就可以通信 但实质是native指向server/client端一侧的地址 从而实现进程通信的场景 所以 到底是gpu合成还是hwc合成 是有一个判断依据的 对于高刷的场景 例如游戏 都是hwc合成 简单的场景 走的是gpu合成 否则 gpu负载太高 功耗就会有增加 也是终端项目中需要check的地方

frameworks/native/libs/gui/DisplayEventReceiver.cpp
status_t DisplayEventReceiver::requestNextVsync() {if (mEventConnection != nullptr) {mEventConnection->requestNextVsync();return NO_ERROR;}return mInitError.has_value() ? mInitError.value() : NO_INIT;
}// EventThread这是软件模拟硬件vsync 后续会讲到
frameworks/native/services/surfaceflinger/Scheduler/EventThread.cpp
binder::Status EventThreadConnection::requestNextVsync() {ATRACE_CALL();mEventThread->requestNextVsync(this);return binder::Status::ok();
}

接下来再来个图
在这里插入图片描述
大家要注意的是 我们目前为止setText的渲染其实是1处右边那代码块 此处还是之前的ui更新操作

1处ui线程ui就开始绘制了 值得注意的是 1处如果draw时长超过16.6ms那么大概率就是应用本身阻塞主线程 我们把trace堆栈放大
在这里插入图片描述
这一步我理解是遍历 比如animation input啊 有哪些 measure layout丈量等 是把ui结构进行数据化 比如这个view的坐标 color等

这里是引用一篇博客里面的解释 但我的理解就是 为了后续的遍历而去组织数据结构 分门别类
Choreographer.doFrame 计算掉帧逻辑
Choreographer.doFrame 处理 Choreographer 的第一个 callback : input
Choreographer.doFrame 处理 Choreographer 的第二个 callback : animation
Choreographer.doFrame 处理 Choreographer 的第三个 callback : insets animation
Choreographer.doFrame 处理 Choreographer 的第四个 callback : traversal

setRefreshRateIfNeed这个应该是手机厂家提供出的接口 不管 traversal 他就是遍历 draw 就是 draw 着重介绍一下postAndWait 这里就会到RenderThread 应用的渲染线程 postAndWait唤醒线程的run 此时我们进入到应用的RenderThread 拓展一下 如果是游戏进程的话 一般是unitymain gfx线程 flutter为什么会比rn要快 因为他直接和sf打交道 不需要再转换一层 想了解的可以看看官方的架构图 流程继续 这里要注意的是 这里的执行顺序是从左往右 单独模块从上到下执行 然后再回到起始点 往右执行

// 代码太多 不一一解释 这里就是把一个frame组织成一个结构体 cpu/gpu可读懂的结构体
frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
void DrawFrameTask::postAndWait() {ATRACE_CALL();AutoMutex _lock(mLock);mRenderThread->queue().post([this]() { run(); });mSignal.wait(mLock);
}
// 从这里我们就可以看到我们熟悉的canvas 当然真正的渲染不是在java进行的
// dequeueBufferDuration 这里有个queue buffer的轮转 我们后续分析
void DrawFrameTask::run() {const int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];ATRACE_FORMAT("DrawFrames %" PRId64, vsyncId);...// Grab a copy of everything we needCanvasContext* context = mContext;nsecs_t dequeueBufferDuration = 0;if (CC_LIKELY(canDrawThisFrame)) {dequeueBufferDuration = context->draw();} else {...
...
}

这里postAndWait后会到2处 也就是自身的渲染线程了 但是2处就是渲染个寂寞 真正渲染的地方是在3处 我们把2处放大一下
在这里插入图片描述
我们现在到应用出帧的地方 也就是renderthread 可以看出 DrawFrames 66363537 和上述1处 Choreographer#doFrame 66363537 id是一样的 但是没有进行渲染 是cpu在执行其他线程 没有得到调度 是因为该进程中的一个线程在初始化 有一定的负载
在这里插入图片描述
dequeueBuffer - VRI[MainActivity]#0(BLAST Consumer)0 此处MainActivity应该是Producter才对 不应该是Consumer 在这里我们需要引入两个知识点BufferQueue和GPU Fence

// ********** @引用_start 努比亚技术团队**********

BufferQueue要解决的是生产者和消费者的同步问题 应用程序生产画面 SurfaceFlinger消费画面 SurfaceFlinger生产画面而HWC Service消费画面 用来存储这些画面的存储区我们称其为帧缓冲区buffer

在BufferQueue的设计中 一个buffer的状态有以下几种:

FREE:表示该buffer可以给到应用程序 由应用程序来绘画

DEQUEUED:表示该buffer的控制权已经给到应用程序侧,这个状态下应用程序可以在上面绘画

QUEUED: 表示该buffer已经由应用程序绘画完成 buffer的控制权已经回到SurfaceFlinger手上

ACQUIRED:表示该buffer已经交由HWC Service去合成了 这时控制权已给到HWC Service

FREE->DEQUEUED->QUEUED->ACQUIRED->FREE

CPU和GPU的工作完全是异步的 Fence提供了一种方式来处理不同硬件对共享资源的访问控制

// ********** @引用_end 努比亚技术团队**********
在这里插入图片描述
其实真正渲染的地方是在3处 我们放大一下 渲染线程中我们只需要重点了解dequeuebuffer和queuebuffer
在这里插入图片描述
此时 应用的renderThread从自身的bufferqueue申请一块buffer用来绘制 需要注意的是从R之后为了分担sf的压力 bufferqueue都在各自应用进程里进行 所以dequeuebuffer此处没有binder调用dequeueBuffer 拿一块buffer的地址下标 也就是往结构体填充指令的数组 下图放大

在这里插入图片描述

/frameworks/native/libs/gui/BufferQueueProducer.cpp
status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,uint32_t width, uint32_t height, PixelFormat format,uint64_t usage, uint64_t* outBufferAge,FrameEventHistoryDelta* outTimestamps) {ATRACE_CALL();{ // Autolock scopestd::lock_guard<std::mutex> lock(mCore->mMutex);// trace中 dequeueBuffer - VRI[MainActivity]#0(BLAST Consumer)0 也是此处的拼接mConsumerName = mCore->mConsumerName;
...

VRI[MainActivity]#0(BLAST Consumer)0: 0 这里的solt是0

在dequeuebuffer 右边还有一句 HWC release fence 19 has signaled 这里dequeuebuffer后这个solt地址并不是立即就往上填充数据 是要等待 GPU释放对应的Fence 只是告诉你我要释放了 相当于bt模组和modem 你请求查询数据 然后模组告诉你有数据了 然后你还得调用个get请求去获取这些数据

接下来就是queuebuffer部分 图片部分放大

在这里插入图片描述

// 表示hwc release fence 19 buffer 还给了bufferqueue 但gpu还没有绘制完
Trace GPU completion fence 19// 将绘制好的buffer返回Surfacefinger
eglSwapBuffersWithDamageKHRstatus_t BufferQueueProducer::queueBuffer(int slot,const QueueBufferInput &input, QueueBufferOutput *output) {ATRACE_CALL();ATRACE_BUFFER_INDEX(slot);int64_t requestedPresentTimestamp;bool isAutoTimestamp;android_dataspace dataSpace;Rect crop(Rect::EMPTY_RECT);int scalingMode;...if (frameAvailableListener != nullptr) {// 按照需要回调至app层frameAvailableListener->onFrameAvailable(item);......

此时就会到SurfaceFlinger进程 值得注意的是 BufferQueue 可以看BLASTBufferQueue

在这里插入图片描述
在这里插入图片描述
waiting for presentFence 699 可以看出kernel消费情况
在这里插入图片描述
所以 setText 开始到显示 最终是这样的
在这里插入图片描述
其实整体内容还是比较浅显的 sf这块还是比较复杂的 设计的场景有点多 后续也只能找一个闭环去用贴源码解释

感谢观看

参考文献


  1. https://blog.csdn.net/rzleilei/article/details/94639329
  2. 作者:努比亚技术团队
    链接:https://www.jianshu.com/p/3c61375cc15b
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/13888.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java方法的递归

Java方法的递归 前言一、递归的概念示例代码示例 二、递归执行过程分析代码示例执行过程图 三、递归练习代码示例按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4)递归求 1 2 3 ... 10写一个递归方法&#xff0c;输入一个非负整数&#xff0c;返回组成它的数字之和. …

go语言的一些常见踩坑问题

开始之前&#xff0c;介绍一下​最近很火的开源技术&#xff0c;低代码。 作为一种软件开发技术逐渐进入了人们的视角里&#xff0c;它利用自身独特的优势占领市场一角——让使用者可以通过可视化的方式&#xff0c;以更少的编码&#xff0c;更快速地构建和交付应用软件&#…

【无重复字符的最长子串】python,滑动窗口+哈希表

滑动窗口哈希表 哈希表 seen 统计&#xff1a; 指针 j遍历字符 s&#xff0c;哈希表统计字符 s[j]最后一次出现的索引 。 更新左指针 i &#xff1a; 根据上轮左指针 i 和 seen[s[j]]&#xff0c;每轮更新左边界 i &#xff0c;保证区间 [i1,j] 内无重复字符且最大。 更新结…

JVM学习-垃圾回收器(一)

垃圾回收器 按线程数分类 串行垃圾回收器 串行回收是在同一时间段内只允许有一个CPU用于执行垃圾回收操作&#xff0c;此时工作线程被暂停&#xff0c;直至垃圾收集工作结束 在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合&#xff0c;串行回收器的性能表…

http和https的区别,怎么免费实现https(内涵教学)

超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息&#xff0c;HTTP协议以明文方式发送内容&#xff0c;不提供任何方式的数据加密&#xff0c;如果攻击者截取了Web浏览器和网站服务器之间的传输报文&#xff0c;就可以直接读懂其中的信息&#xff0c;因此&…

etcd 和 MongoDB 的混沌(故障注入)测试方法

最近在对一些自建的数据库 driver/client 基础库的健壮性做混沌&#xff08;故障&#xff09;测试, 去验证了解业务的故障处理机制和恢复时长. 主要涉及到了 MongoDB 和 etcd 这两个基础组件. 本文会介绍下相关的测试方法. MongoDB 中的故障测试 MongoDB 是比较世界上热门的文…

AI网络爬虫:批量爬取电视猫上面的《庆余年》分集剧情

电视猫上面有《庆余年》分集剧情&#xff0c;如何批量爬取下来呢&#xff1f; 先找到每集的链接地址&#xff0c;都在这个class"epipage clear"的div标签里面的li标签下面的a标签里面&#xff1a; <a href"/drama/Yy0wHDA/episode">1</a> 这个…

短视频矩阵系统4年独立开发正规代发布接口源码搭建部署开发

1. 短视频矩阵源码技术开发要求及实现流程&#xff1a; 短视频矩阵源码开发要求具备视频录制、编辑、剪辑、分享等基本功能&#xff0c;支持实时滤镜、特效、音乐等个性化编辑&#xff0c;能够实现高效的视频渲染和处理。开发流程主要包括需求分析、技术选型、设计架构、编码实…

Web前端开发技术、详细文章、(例子)html 列表、有序列表、无序列表、列表嵌套

目录 列表概述 列表类型与标记符号 无序列表 语法&#xff1a; 语法说明&#xff1a; 无序列表标记的 type 属性及其说明 代码解释 有序列表 基本语法 属性说明 1、列表 o1标记的属性 2、列表项li标记的属性 有序列表 o1标记的属性、值 代码解释 列表嵌套 基本…

FreeBSD/Linux下的系统资源监视器排队队

bpytop bpytop 是一个基于 Python 的资源监视器&#xff0c;可以在 FreeBSD 上使用。它提供了对文件写入磁盘、网络、CPU 和内存占用的监视功能。 pkg install bpytop 或者用ports安装 cd /usr/ports/sysutils/bpytop/ make install clean bashtop bashtop 也是一个基于 P…

化简资源分配图判断是否发生死锁

目录 1.资源分配图的概念 2.判断是否发生死锁 1.资源分配图的概念 资源分配图表示进程和资源之间的请求关系&#xff0c;例如下图&#xff1a; P代表进程&#xff0c;R代表资源&#xff0c;R方框中 有几个圆球就表示有几个这种资源&#xff0c;在图中&#xff0c;R1指向P1&a…

C++ RPC ORM 高速解析

支持所有常用编程语 https://capnproto.org/GitHub - capnproto/capnproto: Capn Proto serialization/RPC system - core tools and C library https://capnproto.org/capnproto-c-win32-1.0.2.zip 常用命令&#xff1a; capnp help capnp compile -oc myschema.capn…

Excel中sum的跨表求和

#实际工作中&#xff0c;一个xlsx文件中会包含多个Excel表格&#xff0c;一般会有“总-分”的关系&#xff0c;如何把分表里的数字汇总到总表里呢&#xff1f; 一般有上图所示的两种表达方式。 可以使用通配符 *&#xff1a;代表任意个数、任意字符&#xff1b; &#xff1f;&…

quartz定时任务

Quartz 数据结构 quartz采用完全二叉树&#xff1a;除了最后一层每一层节点都是满的&#xff0c;而且最后一层靠左排列。 二叉树节点个数规则&#xff1a;每层从左开始&#xff0c;第一层只有一个&#xff0c;就是2的0次幂&#xff0c;第二层两个就是2的1次幂&#xff0c;第三…

DOS学习-目录与文件应用操作经典案例-attrib

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一.前言 二.使用 三.案例 一.前言 DOS系统中的attrib命令是一个用于显示或更改文件&#…

设计模式——职责链(责任链)模式

目录 职责链模式 小俱求实习 结构图 实例 职责链模式优点 职责链模式缺点 使用场景 1.springmvc流程 ​2.mybatis的执行流程 3.spring的过滤器和拦截器 职责链模式 使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成…

github设置项目分类

https://www.php.cn/faq/541957.html https://docs.github.com/zh/repositories/working-with-files/managing-files/creating-new-files

什么是回表,如何解决回表问题

下面表中:主键id是聚簇索引&#xff0c;name是辅助索引。 执行这样一条SQL: select name from A where name"s;name字段是有索引&#xff0c;所以MYSQL在通过name进行査询的时候&#xff0c;是需要扫描两颗Btree树的。 第一遍:先通过二级索引定位主键值1。第二遍:根据主键…

免费发布web APP的四个途径(Python和R)

免费发布数据分析类&#x1f310;web APP的几个途径&#x1f4f1; 数据分析类web APP目前用来部署生信工具&#xff0c;统计工具和预测模型等&#xff0c;便利快捷&#xff0c;深受大家喜爱。而一个免费的APP部署途径&#xff0c;对于开发和测试APP都是必要的。根据笔者的经验…

word-形状绘制、smartart、visio

一、人员架构图绘制 小技巧&#xff1a; 1、ctrlshift水平复制 2、点击图形&#xff0c;右键设置为默认形状 3、插入-形状-右键-锁定绘图模式&#xff0c;按esc退出状态 4、插入-形状-新建绘图画布&#xff0c;代替组合问题 画布中存在锚点&#xff0c;便于直线连接 二、s…