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,一经查实,立即删除!

相关文章

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

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

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…

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 …

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

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

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

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

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;遗传变…

KVM虚机调整磁盘大小(注:需重启虚拟机)

1、将磁盘大小由15G调整为25G [rootkvm ~]# virsh domblklist kvm-client #显示虚拟机硬盘列表 [rootkvm ~]# qemu-img resize /var/lib/libvirt/images/tesk-disk.qcow2 10G #扩容 [rootkvm ~]# qemu-img info /var/lib/libvirt/images/test-disk.qcow2 #查看信息 注&…

奥威BI方案:多行业、多场景,只打高端局

奥威BI方案&#xff0c;确实以其卓越的性能和广泛的应用领域&#xff0c;在高端数据分析市场中占据了一席之地。以下是对奥威BI方案的详细解析。 奥威BI方案是一款针对多行业、多场景的全面数据分析解决方案&#xff0c;它结合了大数据、云计算等先进技术&#xff0c;为企业提…

看互联网大厂如何落地AI-Agent(3)

vivo一站式AI智能体构建平台的演进实践 引言 在AI技术的浪潮中&#xff0c;vivo互联网产品平台架构团队负责人张硕分享了vivo在构建一站式AI智能体平台方面的演进实践和深刻洞见。 背景与挑战 vivo面临的挑战包括创造商业价值、降低学习成本、合规性、以及LLM&#xff08;大…

hnust 1816: 算法10-9:简单选择排序

hnust 1816: 算法10-9&#xff1a;简单选择排序 题目描述 选择排序的基本思想是&#xff1a;每一趟比较过程中&#xff0c;在n-i1(i1,2,…,n-1)个记录中选取关键字最小的记录作为有序序列中的第i个记录。 在多种选择排序中&#xff0c;最常用且形式最为简单的是简单选择排序。…

收银系统源码-收银台副屏广告

1. 功能描述 门店广告&#xff1a;双屏收银机&#xff0c;副屏广告&#xff0c;主屏和副屏同步&#xff0c;总部可统一控制广告位&#xff0c;也可以给门店开放权限&#xff0c;门店独立上传广告位&#xff1b; 2.适用场景 新店开业、门店周年庆、节假日门店活动宣传&#x…

【HICE】DNS反向解析

反向解析&#xff1a;IP ----> 主机名 1.更改主配置文件 2.:更改反向的信息 3.重启服务 4.测试解析是否成功

论文辅导 | 基于多尺度分解的LSTM⁃ARIMA锂电池寿命预测

辅导文章 模型描述 锂电池剩余使用寿命&#xff08;Remaining useful life&#xff0c;RUL&#xff09;预测是锂电池研究的一个重要方向&#xff0c;通过对RUL的准确预测&#xff0c;可以更好地管理和维护电池&#xff0c;延长电池使用寿命。为了能够准确预测锂电池的RUL&…

待研究课题记录

最近了解到两个新的有趣的节点&#xff0c;但是对于实际效果不是很确定&#xff0c;所以这里记录下&#xff0c;后续慢慢研究&#xff1a; 扰动注意力引导 Perturbed Attention Guidance GitHub - KU-CVLAB/Perturbed-Attention-Guidance: Official implementation of "…