性能检测-FPS

简介

要进行 FPS 检测,自然要先理解 FPS 的概念。

FPS 即 Frames per second。(此处省略 1000 字,请自行百度)

在上方的概念中,60 帧每秒以及 16.67 毫秒这两个关键数据代表什么含义呢?又是怎么得来的呢?请阅读UI卡顿优化这篇文档中的「神奇的16ms」一节。

好了,假装 FPS 的基本概念我们都已理解了,下面我们看看如何用代码实现 FPS 检测吧!

实现

方案选择

adb shell dumpsys gfxinfo 【packagename】

执行以下命令,我们可以获取到下方信息:

FPS

从上图可以看到,此方案使用起来非常方便,但是遗憾的是必须是 Android M 版本以上才支持,而且需要拖动屏幕产生的数据才比较准确,对于我来说不太合适,只能 pass

adb shell dumpsys SurfaceFlinger --latency 【packagename】/【Activity的路径】

执行以下命令,我们可以获取到下方信息:

FPS

第一行是设备的刷新周期 refresh-period,单位是纳秒,换算成毫秒就是 16666666\1000\1000≈16.66ms;这条命令的含义是获取当前 layer(窗口、图层)的最近 128 帧的信息(仅保存 128 帧),所以上面我的截图不够全面,实际上总共有128行(刨去第一行刷新率,需要按127帧来换算),三列数据的解释:

  • 第一列:开始绘制图像的瞬时时间。
  • 第二列:VSYNC信令将软件SF帧传递给硬件HW之前的垂直同步时间。
  • 第三列:SF将帧传递给HW的瞬时时间,及完成绘制的瞬时时间。

如果是单 Activity 应用,此方案使用起来非常方便,但是遗憾的是单 Activity 的应用何其之少,此方案对于我这懒人来说不太合适,只能 pass

Choreographer

使用 Choreographer 进行帧率的监控。
做应用层的 Android 开发,看到 Choreographer 可能会有以下疑问吧:

  • Choreographer 是啥?说实话,我也不知道。
  • 你不知道,怎么知道有它的存在的?查看大佬的源码(https://github.com/didi/DoraemonKit)获悉。

仔细查看了一波源码,发现使用真简单,正好适合我这懒人!

有兴趣看源码的可查看原理一节,若无想法可直接拉至文末,查看代码实现

原理

Choreographer 主要作用是协调动画,输入和绘制的时间,它从显示子系统接收定时脉冲(例如垂直同步),然后安排渲染下一个 frame 的一部分工作。

FrameCallback 是和 Choreographer 交互,在下一个 frame 被渲染时触发的接口类。我们主要从自定义 FrameCallback 作为切入口,尝试窥探一下 Choreographer 的实现原理!

源码查看地址:https://www.androidos.net.cn/android/8.0.0_r4/xref

关键变量

构造函数
private Choreographer(Looper looper, int vsyncSource) {
    mLooper = looper;
    //创建 Handle 对象,用于处理消息
    mHandler = new FrameHandler(looper);
    //创建接收 VSYNC 信号的对象
    mDisplayEventReceiver = USE_VSYNC
            ? new FrameDisplayEventReceiver(looper, vsyncSource)
            : null;
    //初始化上一次 frame 渲染的时间点
    mLastFrameTimeNanos = Long.MIN_VALUE;
    //帧率,也就是渲染一帧的时间,getRefreshRate 是刷新率,大部分手机是 60
    mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
    //创建回调队列
    mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
        mCallbackQueues[i] = new CallbackQueue();
    }
}
FrameHandler
private final class FrameHandler extends Handler {
    public FrameHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DO_FRAME:
                //渲染下一个 frame
                doFrame(System.nanoTime(), 0);
                break;
            case MSG_DO_SCHEDULE_VSYNC:
                //请求 VXYNC 信号
                doScheduleVsync();
                break;
            case MSG_DO_SCHEDULE_CALLBACK:
                //处理 callback
                doScheduleCallback(msg.arg1);
                break;
        }
    }
}
FrameDisplayEventReceiver

FrameDisplayEventReceiver 是 DisplayEventReceiver 的子类,我们先来看下 DisplayEventReceiver 类吧。是接收VSYNC信息的java层实现。

public abstract class DisplayEventReceiver {

    //此处省略部分参数

    //此处省略初始化等部分无关代码

    /**
     * Called when a vertical sync pulse is received.
     * The recipient should render a frame and then call {@link #scheduleVsync}
     * to schedule the next vertical sync pulse.
     *
     * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
     * timebase.
     * @param builtInDisplayId The surface flinger built-in display id such as
     * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
     * @param frame The frame number.  Increases by one for each vertical sync interval.
     */
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
    }

    /**
     * Schedules a single vertical sync pulse to be delivered when the next
     * display frame begins.
     */
    public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
            nativeScheduleVsync(mReceiverPtr);
        }
    }

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
        onVsync(timestampNanos, builtInDisplayId, frame);
    }
}
  • VSYNC 信息一般由硬件中断产生,SurfaceFlinger 处理。
  • scheduleVsync 方法用于请求 VSNYC 信号。
  • Native 方法接收到 VSYNC 信息处理后会调用 java 层 dispatchVsync 方法,最终调用到 FrameDisplayEventReceiver 的 onVsync 方法,详细的请继续查看。
CallbackQueue

CallbackQueue 是个单链表实现,每种类型的 callback(CallbackRecord) 按照设置的执行时间(dueTime)顺序排序分别保存在其各自 CallbackQueue。在 Choreographer 中有四种类型 callback:

  • CALLBACK_INPUT:Input callback。
  • CALLBACK_ANIMATION:Animation callback。 在 traversals 之前。
  • CALLBACK_TRAVERSAL:Traversal callback。 处理 layout 和 draw。
  • CALLBACK_COMMIT:Commit callback。 在 traversals 完成后执行。
private final class CallbackQueue {
    private CallbackRecord mHead;

    public boolean hasDueCallbacksLocked(long now) {
        return mHead != null && mHead.dueTime <= now;
    }

    //根据当前时间得到 callback
    public CallbackRecord extractDueCallbacksLocked(long now) {
        //此处省略具体实现
    }

    //根据时间添加 callback
    public void addCallbackLocked(long dueTime, Object action, Object token) {
        //此处省略具体实现
    }

    //移除 callback
    public void removeCallbacksLocked(Object action, Object token) {
        //此处省略具体实现
    }
}

流程分析

大致分析完 Choreographer 关键的几个成员变量后,我们再回到 postFrameCallback 方法。

public void postFrameCallback(FrameCallback callback) {
    postFrameCallbackDelayed(callback, 0);
}

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    if (callback == null) {
        throw new IllegalArgumentException("callback must not be null");
    }

    postCallbackDelayedInternal(CALLBACK_ANIMATION,
                callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
    if (DEBUG_FRAMES) {
        Log.d(TAG, "PostCallback: type=" + callbackType
                + ", action=" + action + ", token=" + token
                + ", delayMillis=" + delayMillis);
    }

    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        //添加 CALLBACK_ANIMATION 类型的 callback 到回调队列
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            //设定的执行时间在当前时间之后,发送 MSG_DO_SCHEDULE_CALLBACK,由 FrameHanlder 执行 doScheduleCallback
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}
doScheduleCallback
void doScheduleCallback(int callbackType) {
    synchronized (mLock) {
        if (!mFrameScheduled) {
            final long now = SystemClock.uptimeMillis();
            if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
                scheduleFrameLocked(now);
            }
        }
    }
}
scheduleFrameLocked
private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {
        mFrameScheduled = true;
        if (USE_VSYNC) {
            if (DEBUG_FRAMES) {
                Log.d(TAG, "Scheduling next frame on vsync.");
            }

            // If running on the Looper thread, then schedule the vsync immediately,
            // otherwise post a message to schedule the vsync from the UI thread
            // as soon as possible.
            if (isRunningOnLooperThreadLocked()) {
                //翻译:若当前线程是UI线程,执行 scheduleVsyncLocked 请求 VSYNC 信号
                scheduleVsyncLocked();
            } else {
                //翻译:非UI线程,发送 MSG_DO_SCHEDULE_VSYNC 消息到主线程
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
        } else {
            final long nextFrameTime = Math.max(
                    mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
            if (DEBUG_FRAMES) {
                Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
            }
            Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, nextFrameTime);
        }
    }
}
scheduleVsyncLocked
private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();
}

调用 DisplayEventReceiver#scheduleVsync,收到 Vsync 信息后,调用 FrameDisplayEventReceiver#onVsync。

FrameDisplayEventReceiver#onVsync
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
    private boolean mHavePendingVsync;
    private long mTimestampNanos;
    private int mFrame;

    public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
        super(looper, vsyncSource);
    }

    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {

        //此处省略部分无关代码

        mTimestampNanos = timestampNanos;
        mFrame = frame;
        //该消息的 callback 为当前对象 FrameDisplayEventReceiver,收到消息调用其 run 方法,然后调用 doFrame 方法
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }

    @Override
    public void run() {
        mHavePendingVsync = false;
        doFrame(mTimestampNanos, mFrame);
    }
}
doFrame
void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {

        //此处省略部分无关代码

        // VSYNC 信号到来时间
        long intendedFrameTimeNanos = frameTimeNanos;
        //实际开始执行当前 frame 的时间
        startNanos = System.nanoTime();
        //时间差
        final long jitterNanos = startNanos - frameTimeNanos;
        //时间差大于帧率(也就是刷新率,大部分手机是 60),则认为是跳帧
        if (jitterNanos >= mFrameIntervalNanos) {
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                        + "The application may be doing too much work on its main thread.");
            }
            final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
            if (DEBUG_JANK) {
                Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                        + "which is more than the frame interval of "
                        + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                        + "Skipping " + skippedFrames + " frames and setting frame "
                        + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
            }
            frameTimeNanos = startNanos - lastFrameOffset;
        }

        if (frameTimeNanos < mLastFrameTimeNanos) {
            if (DEBUG_JANK) {
                Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                        + "previously skipped frame.  Waiting for next vsync.");
            }
            scheduleVsyncLocked();
            return;
        }

        //记录当前frame信息
        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
        mFrameScheduled = false;
        //记录上一次frame渲染的时间点
        mLastFrameTimeNanos = frameTimeNanos;
    }

    try {
        //执行CallBack,优先级为:CALLBACK_INPUT > CALLBACK_ANIMATION > CALLBACK_TRAVERSAL > CALLBACK_COMMIT
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }

    //此处省略部分无关代码
}
doCallbacks
void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {

        final long now = System.nanoTime();
        // 根据 callback 的类型,查找 CallbackRecord 对象
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
        mCallbacksRunning = true;

        if (callbackType == Choreographer.CALLBACK_COMMIT) {
            //此处省略部分无关代码
        }
    }
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
            if (DEBUG_FRAMES) {
                Log.d(TAG, "RunCallback: type=" + callbackType
                        + ", action=" + c.action + ", token=" + c.token
                        + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
            }
            //调用 run 方法
            c.run(frameTimeNanos);
        }
    } finally {
        synchronized (mLock) {
            mCallbacksRunning = false;
            //回收 callbacks
            do {
                final CallbackRecord next = callbacks.next;
                recycleCallbackLocked(callbacks);
                callbacks = next;
            } while (callbacks != null);
        }
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
CallbackRecord#run
private static final class CallbackRecord {
    public CallbackRecord next;
    public long dueTime;
    public Object action; // Runnable or FrameCallback
    public Object token;

    public void run(long frameTimeNanos) {
        if (token == FRAME_CALLBACK_TOKEN) {
            //调用 FrameCallback 的 doFrame 方法
            ((FrameCallback)action).doFrame(frameTimeNanos);
        } else {
            ((Runnable)action).run();
        }
    }
}

小结

关于 Choreographer 的整个调用流程及其原理已经分析完成。至于系统某些调用,如 View 的 invalidate,触发 ViewRootImpl#scheduleTraversals,最终调用 Choreographer#postCallback(Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable, null);从方法中可以看出,这只是明确了 Callback 的类型以及回调处理 Runnable 而已,基本流程几乎一样。

既然自定义 FrameCallback 可以在下一个 frame 被渲染的时候会被回调,那我们可以根据这个原理实现应用的帧率监听。

代码实现

class FpsUtilKt private constructor() {
    /**
     * 当前的帧率
     */
    private var mLastFrameRate = 60
    private val mMainHandler = Handler(Looper.getMainLooper())
    private val mRateRunnable = FrameRateRunnable()

    private object Holder {
        val INSTANCE = FpsUtilKt()
    }

    val fps: Unit
        get() {
            //开启定时任务
            mMainHandler.postDelayed(mRateRunnable, FPS_SAMPLING_TIME.toLong())
            Choreographer.getInstance().postFrameCallback(mRateRunnable)
        }

    /**
     * 读取fps的线程
     */
    private inner class FrameRateRunnable : Runnable, Choreographer.FrameCallback {
        private var totalFramesPerSecond = 0
        override fun run() {
            mLastFrameRate = totalFramesPerSecond
            println("FPS:$mLastFrameRate")
            totalFramesPerSecond = 0
            mMainHandler.postDelayed(this, FPS_SAMPLING_TIME.toLong())
        }

        override fun doFrame(frameTimeNanos: Long) {
            totalFramesPerSecond++
            Choreographer.getInstance().postFrameCallback(this)
        }
    }

    companion object {
        /**
         * fps 采集时间
         */
        private const val FPS_SAMPLING_TIME = 1000
        val instance: FpsUtilKt
            get() = Holder.INSTANCE
    }
}

上文若存在问题,欢迎指出!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值