问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

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

创作时间:
作者:
@小白创作中心

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

引用
CSDN
1.
https://blog.csdn.net/u010542873/article/details/140231109

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

设计初衷

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

设计原理

参考了网上现有的 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 layer
    void addListener(const sp<gui::IFpsListener>& listener, int32_t taskId);
    // Deregisters an Fps listener
    void 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 layer
    void 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了。

设计实现

源码下载

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

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图层渲染显示的帧率

设计分析

  • 自己实现 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上绘制数字,不细讲。

再探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 tiny
                for (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;
}

拓展

是否可以设置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 源码解析,请结合完整源码阅读!

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号