Android 性能优化(三):FPS 监控

515 阅读8分钟

什么是FPS

评估流畅度的指标之一就是FPS,FPS指的是每秒的帧数,传统的帧数就是每秒60帧,也就是说每一帧的绘制不能超过1000/60 = 16.67ms ,如果超过这个数值,就意味着可能会卡顿,而现在的手机甚至支持120帧/s 这就一位置每一帧的绘制1000/120 = 8.333ms,也就是说每秒的帧率越高流畅度就越高

要统计帧率,需要获取每一帧的结束事件,我们现在通过Choreographer 注册postFrameCallback方案来实现

我们通过Choreographer 注册postFrameCallback注册一个回调帧数即可在统计帧数

 class MyCallback implements Choreographer.FrameCallback, Runnable {

        @Override
        public void doFrame(long frameTimeNanos) {
            //这里计算帧数
        }

Vsync 和 三缓冲

Vsync 用于 CPU GPU 显示器同步,主要有下面作用

  • 通知切换buffer,显示前一个buffer的数据到屏幕上
  • 通知CPU进行下一帧数据的计算

当我们在APP中发起布局绘制的操作,会由CPU在收到Vscyn信号后进行处理,来生成GPU可以处理的数据,处理完成后交给GPU进行绘制

等下一个Vsycn信号来临时,显示器从缓冲池中取出一个buffer进行显示,同时CPU进行下一帧的计算,为了保证CPU,GPU 显示器并行工作,Android选择三缓冲的策略

在Android 4.1版本之前,Android 采用双缓冲,所以没有Ios流畅,我们先看下双缓冲

image.png

  • 首先屏幕上显示画面A
  • 这时Vsync信号来临B
  • CPU 对B进行处理
  • CPU 处理完之后GPU 继续进行处理
  • 显示器要显示下一帧的时候,GPU B 还没有处理完成,所以继续显示A 第一次卡顿
  • 当下一帧数据来临时,由于俩个Buffer 都被占用,所以CPU没办法提前处理工作,只能等待Buffer释放
  • GPU处理完成之后显示B,buffer释放
  • 这时才开始处理下一个A信号
  • 这又要重复上面步骤,又会导致卡顿

GPU 绘制慢是因为绘制任务过于繁重,但是Buffer不够我们可以通过增加一个Buffer来解决,这就是三缓冲

image.png

  • 首先屏幕上显示画面A
  • 这时Vsync信号来临B
  • CPU 对B进行处理
  • CPU 处理完之后GPU 继续进行处理
  • 显示器要显示下一帧的时候,GPU B 还没有处理完成,所以继续显示A 第一次卡顿
  • 当下一帧数据来临时,CPU提前处理工作
  • GPU处理完成之后显示B
  • 等下一帧来临,可以直接显示,因为之前已经提前处理了

这样处理的结果就是只会卡顿一次,GPU在绘制上一帧数据的时候CPU 就可以处理下一帧,这样流畅度就很大提升

Choreographer

Choreographer是APP界面渲染机制的核心组成,他帮我们封装了Vsync信号请求和分发工作,在我们发起界面绘制请求时,会向Choreographer注册Vsync信号监听,同时请求Vsync信号,这样当收到Vsync信号后开始渲染UI

先上代码

## ViewRootImpl.java

  void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //发送同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

mChoreographer翻译为编舞者,用于接受系统的VSync信号,在下一个帧渲染时控制一些操作,mChoreographerpostCallback方法用于添加回调,这个添加的回调,将在下一帧渲染时执行,这个添加的回调指的是TraversalRunnable类型的mTraversalRunnable,如下:

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

这个方法内部调用了doTraversal

   void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

这个方法又调用了performTraversals,这个方法中更新了Window的视图,并且完成了View的绘制流程,measure,layout,draw,这样就完成了View的更新

继续追一下mChoreographer.postCallback

## Choreographer.java
public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }


 public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }

        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }


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;
            //把Runnable加入数组中
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                //立即执行
                scheduleFrameLocked(now);
            } else {
                //发送异步消息延迟执行
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }
 private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {//// Android 4.1 之后 USE_VSYNCUSE_VSYNC 默认为 true
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                // 如果再主线程,则立即执行,否则就发送异步消息
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {// 未开启 vsync,4.1 之后默认开启
                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);
            }
        }
    }

这里就是判断是否开启USE_VSYNC,如果是主线程则立即执行

 private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

VSYNC其实是为了解决,屏幕刷新率和GPU帧率不一致导致的屏幕撕裂问题,VSYNC机制再Android4.1引入,简单理解为,VSYNC是硬件发送的定时信号,通过Choreographer来监听这个信号,每当信号来临,开始绘制工作

mDisplayEventReceiver 类型是FrameDisplayEventReceiver其父类为DisplayEventReceiver,scheduleVsync方法为父类的方法

## DisplayEventReceiver.java

 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 {
            //注册Vsync信号
            nativeScheduleVsync(mReceiverPtr);
        }
    }

//有 vsync 信号时,由 native 调用此方法
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
        onVsync(timestampNanos, physicalDisplayId, frame);
    }

这里是调用了native方法nativeScheduleVsync注册信号,当有Vsync信号时回调dispatchVsync方法,然后调用onVsync方法,这个方法是由子类FrameDisplayEventReceiver实现


    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, long physicalDisplayId, int frame) {
           
            long now = System.nanoTime();
            if (timestampNanos > now) {
                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                        + " ms in the future!  Check that graphics HAL is generating vsync "
                        + "timestamps using the correct timebase.");
                timestampNanos = now;
            }

            if (mHavePendingVsync) {
                Log.w(TAG, "Already have a pending vsync event.  There should only be "
                        + "one at a time.");
            } else {
                mHavePendingVsync = true;
            }

            mTimestampNanos = timestampNanos;
            mFrame = frame;
            //这里传入this,回掉自身的run方法
            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);
        }
    }

这里sendMessageAtTime方法的参数timestampNanos / TimeUtils.NANOS_PER_MS,timestampNanos表示vysnc信号时间戳,单位纳秒,这里转换成毫秒,此时Vysnc已经发生,timestampNanos是比当前时间小的,这样消息就可以塞到MessageQueue的前面,此处的this,表示回调自身的run方法,最后调用doFrame

    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }
            //
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            final long jitterNanos = startNanos - frameTimeNanos;
            //startNanos当前时间,frameTimeNanos表示Vysnc信号时间,相减得到主线程耗时时间
            if (jitterNanos >= mFrameIntervalNanos) {
                // mFrameIntervalNanos表示一帧时间
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    //掉帧抄超过30帧打印log
                    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;
            }

            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();
                    return;
                }
            }

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            //开始回调callback
            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_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();
            //根据callbackType找到callbacks对象
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;

        ...
          
        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));
                }
                //执行callback的run方法
                c.run(frameTimeNanos);
            }
        } 
        ...
    }

根据callbackType找到callbacks对象,然后执行run方法,callbackType有四种 CALLBACK_INPUTCALLBACK_ANIMATIONCALLBACK_TRAVERSALCALLBACK_COMMIT

private static final class CallbackRecord {
   
        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                //注意这里我们自定义的就是FrameCallback,回调这里
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                //到这里就执行了Runnable的run方法
                ((Runnable)action).run();
            }
        }
    }

到这里就执行了Runnablerun方法

如何监测应用的 FPS?

其实这里就应用到了一个

> Choreographer.java

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

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    ......
    // 这里的类型是 CALLBACK_ANIMATION
    postCallbackDelayedInternal(CALLBACK_ANIMATION,
            callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

public void postFrameCallback(FrameCallback callback) { public void postFrameCallback(FrameCallback callback) { 这里可以看到这个方法和上面的postCallback一样,差别就是callbackType,这个的callTypeCALLBACK_ANIMATION,也就是说每次Vysnc回调都会调用我们自定义的FrameCallbackdoFrame方法,我们可以再这个方法中计算fps

实战


public class Fps {

    private boolean isFpsOpen;
    private ArrayList<Integer> listeners;
    private int count;
    private Handler handler;
    private long FPS_INTERVAL_TIME = 1000L;
    private FpsCallback mfpsCallback;
    private MyCallback fpsRunnable;


    public Fps() {
        handler = new Handler(Looper.getMainLooper());
    }


    public void startMonitor(FpsCallback fpsCallback) {
        // 防止重复开启
        if (!isFpsOpen) {
            isFpsOpen = true;
            this.mfpsCallback = fpsCallback;
            fpsRunnable = new MyCallback();
            handler.postDelayed(fpsRunnable, FPS_INTERVAL_TIME);
            Choreographer.getInstance().postFrameCallback(fpsRunnable);
        }
    }

    public void stopMonitor() {
        count = 0;
        handler.removeCallbacks(fpsRunnable);
        Choreographer.getInstance().removeFrameCallback(fpsRunnable);
        this.mfpsCallback = null;
        isFpsOpen = false;
    }


    class MyCallback implements Choreographer.FrameCallback, Runnable {

        @Override
        public void doFrame(long frameTimeNanos) {
            count++;
            Choreographer.getInstance().postFrameCallback(this);
        }

        @Override
        public void run() {
            mfpsCallback.fps(count);
            count = 0;
            handler.postDelayed(this, FPS_INTERVAL_TIME);
        }
    }

    public interface FpsCallback {
        void fps(int fps);
    }
}