Android Graphics 显示系统 - 监测、计算FPS的工具及设计分析

“ 在Android图像显示相关的开发、调试、测试过程中,如何能有效地评估画面的流畅度及监测、计算图层渲染显示的实时FPS呢?本篇文章将会提供一种实用、灵巧的思路。

01

设计初衷

面对开发测试中遇到的卡顿掉帧问题,如何在复现卡顿的过程中持续监控FPS 和丢帧情况?特别是视频播放的场景,掉帧与FPS不稳定会带来糟糕的用户体验。因此需要有一款小工具可以实时监测图层渲染、显示的FPS变化,以便于判断是否FPS不稳定或掉帧。

02

设计原理

参考了网上现有的 FPS 计算方式原理,一定程度上满足自己的预期需求,但要么自己设计脚本计算逻辑处理,要么就要修改到源码。

方式1. 写作FPS计算脚本

基于dumpsys SurfaceFlinger --latency Layer-name或dumpsys gfxinfo获取信息,然后设计计算逻辑。

方式2. 修改SF源码呼叫computeFps

修改SurfaceFlinger的逻辑,利用原生computeFps方法来计算指定图层的FPS.

float FrameTimeline::computeFps(const std::unordered_set<int32_t>& layerIds)

那有没有其它方式呢?

当然!

我们留意到在SurfaceFlinger的源码中有一个FpsReporter,里面有提供注册监听器的接口,看起来可以利用!

class FpsReporter : public IBinder::DeathRecipient {
public:FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger,std::unique_ptr<Clock> clock = std::make_unique<SteadyClock>());....// Registers an Fps listener that listens to fps updates for the provided layervoid addListener(const sp<gui::IFpsListener>& listener, int32_t taskId);// Deregisters an Fps listenervoid removeListener(const sp<gui::IFpsListener>& listener);....
}

再接着看下IFpsListener的定义,顾名思义,当fps变化时就会回调到

/frameworks/native/libs/gui/aidl/android/gui/IFpsListener.aidl
oneway interface IFpsListener {// Reports the most recent recorded fps for the tree rooted at this layervoid onFpsReported(float fps);
}

那用户层有无提供设置的接口呢?

先看SurfaceComposerClient中的定义,确实也存在add/remove方法

/frameworks/native/libs/gui/include/gui/SurfaceComposerClient.h
static status_t addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener);
static status_t removeFpsListener(const sp<gui::IFpsListener>& listener);

那设计原理有了:设计一个C++小工具,注册fps listener,就可以监测特定task的fps了。

03

设计实现

源码下载

废话不说直接开源给大家,如果觉得有用 期待您点赞

yrzroger/FpsMonitor: 监测Android平台实时刷新率FPS (github.com)

使用说明

  1. 下载main branch,基于Android 14平台开发,代码放到android源码目录下

  2. 执行mm编译获得可执行档 FpsMonitor

  3. 方式测试设备 adb push FpsMonitor /data/local/tmp/

  4. 执行 adb shell am stack list 获取要监测的应用的 taskId

  5. adb shell /data/local/tmp/FpsMonitor -t taskId 运行程序

  6. 输入‘q’ 或者 Ctrl+C 退出监测

图片

效果展示

在console log中打印出实时的FPS 

图片

在屏幕左上角展示当前的屏幕的刷新率和Task图层渲染显示的帧率

图片

04

设计分析

  • 自己实现 IFpsListener,在onFpsReported中做想做的事情

struct TaskFpsCallback : public gui::BnFpsListener {
binder::Status onFpsReported(float fps) override {// 收到FPS,do what you want
}
}
  • 注册监听器到SurfaceFlinger

    sp<TaskFpsCallback> callback = new TaskFpsCallback();if (SurfaceComposerClient::addFpsListener(mTaskId, callback) != OK) {ALOGD("addFpsListener error!");return -1;}
  • UI显示

RefreshRateOverlay类中封装实现UI显示的逻辑,这部分源自SurfaceFlinger中的逻辑,本质也是创建Native Surface,然后使用Skia在Surface上绘制数字,不细讲。

05

再探FpsReporter::dispatchLayerFps

大家要留意一点,注册的IFpsListener是和Task绑定的,而一个Task通常会关联多个图层,因为有Child Layer,所以任何一个图层的变化都是导致FPS变化。

因此:

我们的小工具有一个局限,那就是跟踪单一图层的FPS会受到场景限制,比如视频播放时,如果视频图层上有其他UI变化(隶属同一个Task),那计算出的FPS就不仅仅是解码视频显示的FPS了

SurfaceFlinger中合成阶段会去调用FpsReporter::dispatchLayerFps

void SurfaceFlinger::postComposition(nsecs_t callTime) {....Mutex::Autolock lock(mStateLock);if (mFpsReporter) {mFpsReporter->dispatchLayerFps();}....
}

dispatchLayerFps会去遍历所有layers,找到和task对应的layer及child layers,然后呼叫computeFps计算,最终回调listener->onFpsReported

void FpsReporter::dispatchLayerFps() {const auto now = mClock->now();if (now - mLastDispatch < kMinDispatchDuration) {return;}std::vector<TrackedListener> localListeners;{std::scoped_lock lock(mMutex);if (mListeners.empty()) {return;}std::transform(mListeners.begin(), mListeners.end(), std::back_inserter(localListeners),[](const std::pair<wp<IBinder>, TrackedListener>& entry) {return entry.second;});}std::unordered_set<int32_t> seenTasks;std::vector<std::pair<TrackedListener, sp<Layer>>> listenersAndLayersToReport;mFlinger.mCurrentState.traverse([&](Layer* layer) {auto& currentState = layer->getDrawingState();if (currentState.metadata.has(gui::METADATA_TASK_ID)) {int32_t taskId = currentState.metadata.getInt32(gui::METADATA_TASK_ID, 0);if (seenTasks.count(taskId) == 0) {// localListeners is expected to be tinyfor (TrackedListener& listener : localListeners) {if (listener.taskId == taskId) {seenTasks.insert(taskId);listenersAndLayersToReport.push_back({listener, sp<Layer>::fromExisting(layer)});break;}}}}});for (const auto& [listener, layer] : listenersAndLayersToReport) {std::unordered_set<int32_t> layerIds;layer->traverse(LayerVector::StateSet::Current,[&](Layer* layer) { layerIds.insert(layer->getSequence()); });listener.listener->onFpsReported(mFrameTimeline.computeFps(layerIds));}mLastDispatch = now;
}

06

拓展

是否可以设置APP实现FPS监测呢?

在WMS中,也是有提供registerTaskFpsCallback的接口,可以在app层面使用监测FPS,本质原理是一样的,只是通过JNI呼叫到了SurfaceComposerClient::addFpsListener。

/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java@Override@RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER)public void registerTaskFpsCallback(@IntRange(from = 0) int taskId,ITaskFpsCallback callback) {if (mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FPS_COUNTER)!= PackageManager.PERMISSION_GRANTED) {final int pid = Binder.getCallingPid();throw new SecurityException("Access denied to process: " + pid+ ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER);}if (mRoot.anyTaskForId(taskId) == null) {throw new IllegalArgumentException("no task with taskId: " + taskId);}mTaskFpsCallbackController.registerListener(taskId, callback);}@Override@RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER)public void unregisterTaskFpsCallback(ITaskFpsCallback callback) {if (mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FPS_COUNTER)!= PackageManager.PERMISSION_GRANTED) {final int pid = Binder.getCallingPid();throw new SecurityException("Access denied to process: " + pid+ ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER);}mTaskFpsCallbackController.unregisterListener(callback);}

如何监测单一图层的FPS,避免上面提到的局限呢?

可以考虑采用文章开头提到的方式1 、 方式2。或者对SurfaceFlinger动动手脚。


本文基于 Android U 源码解析,请结合完整源码阅读!


关注公众号,优质内容

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

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

相关文章

大模型AIGC转行记录(一)

自从22年11月chat gpt上线以来&#xff0c;这一轮的技术浪潮便变得不可收拾。我记得那年9月份先是在技术圈内讨论&#xff0c;然后迅速地&#xff0c;全社会在讨论&#xff0c;各个科技巨头、金融机构、政府部门快速跟进。 软件开发行业过去与现状 我19年决定转码的时候&…

代码随想录算法训练营第四十五天| 300.最长递增子序列、 674. 最长连续递增序列、 718. 最长重复子数组

300.最长递增子序列 题目链接&#xff1a;300.最长递增子序列 文档讲解&#xff1a;代码随想录 状态&#xff1a;不会&#xff0c;递推状态的时候只想着如何从dp[i-1]推导dp[i]&#xff0c;没想过可能需要枚举dp[0-i] 思路&#xff1a; 找出所有比自己小的数字的dp[j],在这些dp…

【K8s】【问题排查】k8s只能本地服务器访问服务,其他节点无法访问服务

出现原因&#xff1a; 问题描述&#xff1a;k8s部署服务之后&#xff0c;只能在Pod所在的节点通过node ip 对外暴露的端口请求&#xff1b;无法使用CLUSTER-IP端口访问。也不能在其他Node节点通过Pod所在的节点通过node ip 对外暴露的端口请求&#xff1b; 主机名与IP对应情况&…

随着人工智能与机器学习的广泛应用,Java 如何有效地与深度学习框架进行集成,以实现更智能的应用开发?

Java作为一种广泛使用的编程语言&#xff0c;在人工智能和机器学习领域也有着一定的应用。Java可以通过与深度学习框架的集成来实现更智能的应用开发&#xff0c;以下是一些方法&#xff1a; 使用Java的深度学习框架&#xff1a;Java有一些针对深度学习的框架&#xff0c;如DL4…

SpringBoot 实现视频分段播放(通过进度条来加载视频)

需求&#xff1a;现在我本地电脑中有一个文件夹&#xff0c;文件夹中都是视频&#xff0c;需要实现视频播放的功能。 问题&#xff1a;如果通过类似 SpringBoot static 文件夹的方式来实现&#xff0c;客户端要下载好完整的视频之后才可以播放&#xff0c;并且服务端也会占用大…

秋招突击——7/5——设计模式知识点补充——适配器模式、代理模式和装饰器模式

文章目录 引言正文适配器模式学习篮球翻译适配器 面试题 代理模式学习面试题 装饰器模式学习装饰模式总结 面试题 总结 引言 为了一雪前耻&#xff0c;之前腾讯面试的极其差&#xff0c;设计模式一点都不会&#xff0c;这里找了一点设计模式的面试题&#xff0c;就针对几个常考…

计算机图形学入门24:材质与外观

1.前言 想要得到一个漂亮准确的场景渲染效果&#xff0c;不只需要物理正确的全局照明算法&#xff0c;也要了解现实中各种物体的表面外观和在图形学中的模拟方式。而物体的外观和材质其实就是同一个意思&#xff0c;不同的材质在光照下就会表现出不同的外观&#xff0c;所以外观…

代理模式的实现

1. 引言 1.1 背景 代理模式&#xff08;Proxy Pattern&#xff09;是一种常用的设计模式&#xff0c;它允许通过一个代理对象来控制对另一个对象的访问。在面向对象编程的框架中&#xff0c;代理模式被广泛应用&#xff0c;尤其在Spring框架的AOP&#xff08;面向切面编程&am…

C++中的设计模式

要搞清楚设计模式&#xff0c;首先得要了解UML中的类的一些关系模型。 一.UML图中与类的层次关系 UML关系&#xff1a;继承关系&#xff08;泛化关系&#xff09;&#xff1b;组合关系&#xff1b;聚合关系&#xff1b;关联关系&#xff1b;依赖关系&#xff1b; 以上关系强度…

vue中实现button按钮的重复点击指令

// 注册一个全局自定义指令 v-debounce Vue.directive(debounce, {// 当被绑定的元素插入到 DOM 中时...inserted: function (el, binding) {let timer;el.addEventListener(click, () > {clearTimeout(timer);timer setTimeout(() > {binding.value(); // 调用传给指令…

IO、零拷贝、多路复用、connection、池化

目录 一、IO 模型 二、什么是网络IO 三、什么是零拷贝 四、多路复用 五、java程序、mysql JDBC connection关系 六、connection怎么操作事务 七 、java里面的池化技术 八、线程池7个核心参数 九、线程的状态 一、IO 模型 BIO &#xff1a;同步阻塞io&#xff0c;单线程 内存上下…

Springboot学习之用EasyExcel4导入导出数据(基于MyBatisPlus)

一、POM依赖 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><m…

AI Earth应用—— 在线使用sentinel数据VV和VH波段进行水体提取分析(昆明抚仙湖、滇池为例)

AI Earth 本文的主要目的就是对水体进行提取,这里,具体的操作步骤很简单基本上是通过,首页的数据检索,选择需要研究的区域,然后选择工具箱种的水体提取分析即可,剩下的就交给阿里云去处理,结果如下: 这是我所选取的一景影像: 详情 卫星: Sentinel-1 级别: 1 …

A35 STM32_HAL库函数 之PCD通用驱动 -- A -- 所有函数的介绍及使用

A35 STM32_HAL库函数 之PCD通用驱动 -- A -- 所有函数的介绍及使用 1 该驱动函数预览1.1 HAL_PCD_Init1.2 HAL_PCD_DeInit1.3 HAL_PCD_MspInit1.4 HAL_PCD_MspDeInit1.5 HAL_PCD_Start1.6 HAL_PCD_Stop1.7 HAL_PCD_IRQHandler1.8 HAL_PCD_DataOutStageCallback1.9 HAL_PCD_Data…

基于机器学习(支持向量机,孤立森林,鲁棒协方差与层次聚类)的机械振动信号异常检测算法(MATLAB 2021B)

机械设备异常检测方法流程一般如下所示。 首先利用传感器采集机械运行过程中的状态信息&#xff0c;包括&#xff0c;振动、声音、压力、温度等。然后采用合适的信号处理技术对采集到机械信号进行分析处理&#xff0c;提取能够准确反映机械运行状态的特征。最后采用合理的异常决…

C++ 什么是虚函数?什么是纯虚函数,以及区别?(通俗易懂)

&#x1f4da; 当谈到虚函数时&#xff0c;通常是指在面向对象编程中的一种机制&#xff0c;它允许在派生类中重写基类的函数&#xff0c;并且能够通过基类指针或引用调用派生类中的函数。 目录 前言 &#x1f525; 虚函数 &#x1f525; 纯虚函数 &#x1f525; 两者区别…

洛谷 P1032 [NOIP2002 提高组] 字串变换

P1032 [NOIP2002 提高组] 字串变换 - 洛谷 | 计算机科学教育新生态 题目来源 洛谷 题目内容 [NOIP2002 提高组] 字串变换 题目背景 本题不保证存在靠谱的多项式复杂度的做法。测试数据非常的水&#xff0c;各种做法都可以通过&#xff0c;不代表算法正确。因此本题题目和…

mac|idea导入通义灵码插件

官方教程&#xff1a;通义灵码下载安装指南_智能编码助手_AI编程_云效(Apsara Devops)-阿里云帮助中心 下载插件&#xff1a; ⇩ TONGYI Lingma - JetBrains 结果如下&#xff1a; 选择apply、ok&#xff0c;会出现弹窗&#xff0c;点击登录 可以实现&#xff1a;生成单元测…

《C++20设计模式》代理模式

文章目录 一、前言二、实现1、UML类图2、实现 一、前言 这代理模式和装饰器模式很像啊。都是套一层类。&#x1f630; 主要就是功能差别 装饰器&#xff1a; 为了强化原有类的功能。代理模式&#xff1a; 不改变原有功能&#xff0c;只是强化原有类的潜在行为。 我觉的书上有…

【基于R语言群体遗传学】-8-代际及时间推移对于变异的影响

上一篇博客&#xff0c;我们学习了在非选择下&#xff0c;以二项分布模拟遗传漂变的过程&#xff1a;【基于R语言群体遗传学】-7-遗传变异&#xff08;genetic variation&#xff09;-CSDN博客 那么我们之前有在代际之间去模拟&#xff0c;那么我们就想知道&#xff0c;遗传变…