溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么監(jiān)測(cè)應(yīng)用的FPS

發(fā)布時(shí)間:2021-10-25 16:02:07 來(lái)源:億速云 閱讀:112 作者:iii 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“怎么監(jiān)測(cè)應(yīng)用的FPS ”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“怎么監(jiān)測(cè)應(yīng)用的FPS ”吧!

什么是 FPS ?

即使你不知道 FPS,但你一定聽(tīng)說(shuō)過(guò)這么一句話,在 Android 中,每一幀的繪制時(shí)間不要超過(guò) 16.67ms。那么,這個(gè) 16.67ms 是怎么來(lái)的呢?就是由 FPS 決定的。

FPS,F(xiàn)rame Per Second,每秒顯示的幀數(shù),也叫 幀率。Android 設(shè)備的 FPS 一般是 60,也即每秒要刷新 60 幀,所以留給每一幀的繪制時(shí)間最多只有 1000/60 =  16.67ms 。一旦某一幀的繪制時(shí)間超過(guò)了限制,就會(huì)發(fā)生 掉幀,用戶在連續(xù)兩幀會(huì)看到同樣的畫(huà)面。

監(jiān)測(cè) FPS 在一定程度上可以反應(yīng)應(yīng)用的卡頓情況,原理也很簡(jiǎn)單,但前提是你對(duì)屏幕刷新機(jī)制和繪制流程很熟悉。所以我不會(huì)直接進(jìn)入主題,讓我們先從 View.invalidate() 說(shuō)起。

 

從 View.invalidate() 說(shuō)起

要探究屏幕刷新機(jī)制和 View 繪制流程,View.invalidate() 無(wú)疑是個(gè)好選擇,它會(huì)發(fā)起一次繪制流程。

> View.java

public void invalidate() {
    invalidate(true);
}

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
    boolean fullInvalidate) {
    ......
    final AttachInfo ai = mAttachInfo;
    final ViewParent p = mParent;
    if (p != null && ai != null && l < r && t < b) {
        final Rect damage = ai.mTmpInvalRect;
        damage.set(l, t, r, b);
 // 調(diào)用 ViewGroup.invalidateChild()
        p.invalidateChild(this, damage);
    }
    ......
}
 

這里調(diào)用到 ViewGroup.invalidateChild() 。

> ViewGroup.java

public final void invalidateChild(View child, final Rect dirty) {
    final AttachInfo attachInfo = mAttachInfo;
    ......
    ViewParent parent = this;
    if (attachInfo != null) {
        ......
        do {
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }
            ......
            parent = parent.invalidateChildInParent(location, dirty);
            ......
        } while (parent != null);
    }
}
 

這里有一個(gè)遞歸,不停的調(diào)用父 View 的 invalidateChildInParent() 方法,直到最頂層父 View 為止。這很好理解,僅靠 View 本身是無(wú)法繪制自己的,必須依賴最頂層的父 View 才可以測(cè)量,布局,繪制整個(gè) View 樹(shù)。但是最頂層的父 View 是誰(shuí)呢?是 setContentView() 傳入的布局文件嗎?不是,它解析之后被塞進(jìn)了 DecorView 中。是 DecorView 嗎?也不是,它也是有父親的。

DecorView 的 parent 是誰(shuí)呢?這就得來(lái)到 ActivityThread.handleResume() 方法中。

> ActivityThread.java

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
    ......
    // 1. 回調(diào) onResume()
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ......
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    ViewManager wm = a.getWindowManager();
    // 2. 添加 decorView 到 WindowManager
    wm.addView(decor, l);
    ......
}
 

第二步中實(shí)際調(diào)用的是 WindowManagerImpl.addView() 方法,WindowManagerImpl 中又調(diào)用了 WindowManagerGlobal.addView() 方法。

> WindowManagerGlobal.java

// 參數(shù) view 就是 DecorView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ......
    ViewRootImpl root;
    // 1. 初始化 ViewRootImpl
    root = new ViewRootImpl(view.getContext(), display);

    mViews.add(view);
    mRoots.add(root);
    // 2. 重點(diǎn)在這
    root.setView(view, wparams, panelParentView);
    ......
}
 

跟進(jìn) ViewRootImpl.setView() 方法。

> ViewRootImpl.java

// 參數(shù) view 就是 DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;

            // 1. 發(fā)起首次繪制
            requestLayout();

            // 2. Binder 調(diào)用 Session.addToDisplay(),將 window 添加到屏幕
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

            // 3. 重點(diǎn)在這,注意 view 是 DecorView,this 是 ViewRootImpl 本身
            view.assignParent(this);
        }
    }
}
 

跟進(jìn) View.assignParent() 方法。

> View.java

// 參數(shù) parent 是 ViewRootImpl
void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but"
                + " it already has a parent");
    }
}
 

還記得我們跟了這么久在干嘛嗎?為了探究 View 的刷新流程,我們跟著 View.invalidate() 方法一路追到 ViewGroup.invalidateChild() ,其中遞歸調(diào)用 parent 的 invalidateChildInParent() 方法。所以我們?cè)?給 DecorView 找爸爸 ?,F(xiàn)在很清晰了,DecorView 的爸爸就是 ViewRootImpl ,所以最終調(diào)用的就是 ViewRootImpl.invalidateChildInParent() 方法。

> ViewRootImpl.java

public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    // 1. 線程檢查
    checkThread();

    if (dirty == null) {
        // 2. 調(diào)用 scheduleTraversals()
        invalidate();
        return null;
    } else if (dirty.isEmpty() && !mIsAnimating) {
        return null;
    }
    ......
    // 3. 調(diào)用 scheduleTraversals()
    invalidateRectOnScreen(dirty);

    return null;
}
 

無(wú)論是注釋 2 處的 invalite() 還是注釋 3 處的 invalidateRectOnScreen() ,最終都會(huì)調(diào)用到 scheduleTraversals() 方法。

scheduleTraversals() 在 View 繪制流程中是個(gè)極其重要的方法,我不得不單獨(dú)開(kāi)一節(jié)來(lái)聊聊它。

 

承上啟下的 “編舞者”

上一節(jié)中,我們從 View.invalidate() 方法開(kāi)始追蹤,一直跟到 ViewRootImpl.scheduleTraversals() 方法。

> ViewRootImpl.java

void scheduleTraversals() {
    // 1. 防止重復(fù)調(diào)用
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
  // 2. 發(fā)送同步屏障,保證優(yōu)先處理異步消息
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
  // 3. 最終會(huì)執(zhí)行 mTraversalRunnable 這個(gè)任務(wù)
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ......
    }
}
 
  1. mTraversalScheduled 是個(gè)布爾值,防止重復(fù)調(diào)用,在一次 vsync 信號(hào)期間多次調(diào)用是沒(méi)有意義的
  2. 利用 Handler 的同步屏障機(jī)制,優(yōu)先處理異步消息
  3. Choreographer 登場(chǎng)

到這里,鼎鼎大名的 編舞者 —— Choreographer [?k??ri?ɑ?ɡr?f?r] 就該出場(chǎng)了(為了避免面試中出現(xiàn)不會(huì)讀單詞的尷尬,掌握一下發(fā)音還是必須的)。

通過(guò) mChoreographer 發(fā)送了一個(gè)任務(wù) mTraversalRunnable ,最終會(huì)在某個(gè)時(shí)刻被執(zhí)行。在看源碼之前,先拋出來(lái)幾個(gè)問(wèn)題:

  1. mChoreographer 是在什么時(shí)候初始化的?
  2. mTraversalRunnable 是個(gè)什么鬼?
  3. mChoreographer 是如何發(fā)送任務(wù)以及任務(wù)是如何被調(diào)度執(zhí)行的?

圍繞這三個(gè)問(wèn)題,我們?cè)倩氐皆创a中。

先來(lái)看第一個(gè)問(wèn)題,這就得回到上一節(jié)介紹過(guò)的 WindowManagerGlobal.addView() 方法。

> WindowManagerGlobal.java

// 參數(shù) view 就是 DecorView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ......
    ViewRootImpl root;
    // 1. 初始化 ViewRootImpl
    root = new ViewRootImpl(view.getContext(), display);

    mViews.add(view);
    mRoots.add(root);
    
    root.setView(view, wparams, panelParentView);
    ......
}
 

注釋 1 處 新建了 ViewRootImpl 對(duì)象,跟進(jìn) ViewRootImpl 的構(gòu)造函數(shù)。

> ViewRootImpl.java

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    // 1. IWindowSession 代理對(duì)象,與 WMS 進(jìn)行 Binder 通信
    mWindowSession = WindowManagerGlobal.getWindowSession();
    ......
    mThread = Thread.currentThread();
    ......
    // IWindow Binder 對(duì)象
    mWindow = new W(this);
    ......
    // 2. 初始化 mAttachInfo
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);
    ......
    // 3. 初始化 Choreographer,通過(guò) Threadlocal 存儲(chǔ)
    mChoreographer = Choreographer.getInstance();
    ......
}
 

ViewRootImpl 的構(gòu)造函數(shù)中,注釋 3 處初始化了 mChoreographer,調(diào)用的是 Choreographer.getInstance() 方法。

> Choreographer.java

public static Choreographer getInstance() {
    return sThreadInstance.get();
}
 

sThreadInstance 是一個(gè) ThreadLocal<Choreographer> 對(duì)象。

> Choreographer.java

private static final ThreadLocal<Choreographer> sThreadInstance =
        new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
        Looper looper = Looper.myLooper();
        if (looper == null) {
            throw new IllegalStateException("The current thread must have a looper!");
        }
        // 新建 Choreographer 對(duì)象
        Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
        if (looper == Looper.getMainLooper()) {
            mMainInstance = choreographer;
        }
        return choreographer;
    }
};
 

所以 mChoreographer 保存在 ThreadLocal 中的線程私有對(duì)象。它的構(gòu)造函數(shù)中需要傳入當(dāng)前線程(這里就是主線程)的 Looper 對(duì)象。

這里再插一個(gè)題外話,主線程 Looper 是在什么時(shí)候創(chuàng)建的? 回顧一下應(yīng)用進(jìn)程的創(chuàng)建流程:

  • 調(diào)用 Process.start() 創(chuàng)建應(yīng)用進(jìn)程

  • ZygoteProcess 負(fù)責(zé)和 Zygote 進(jìn)程建立 socket 連接,并將創(chuàng)建進(jìn)程需要的參數(shù)發(fā)送給 Zygote 的 socket 服務(wù)端

  • Zygote 服務(wù)端接收到參數(shù)之后調(diào)用 ZygoteConnection.processOneCommand() 處理參數(shù),并 fork 進(jìn)程

  • 最后通過(guò) findStaticMain() 找到 ActivityThread 類的 main() 方法并執(zhí)行,子進(jìn)程就啟動(dòng)了

ActivityThread 并不是一個(gè)線程,但它是運(yùn)行在主線程上的,主線程 Looper 就是在它的 main() 方法中執(zhí)行的。

> ActivityThread.java

public static void main(String[] args) {
    ......
    // 創(chuàng)建主線程 Looper
    Looper.prepareMainLooper(); 
    ......
    // 創(chuàng)建 ActivityThread ,并 attach(false)
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    ......
    // 開(kāi)啟主線程消息循環(huán)
    Looper.loop();
}
 

Looper 也是存儲(chǔ)在 ThreadLocal 中的。

再回到 Choreographer,我們來(lái)看一下它的構(gòu)造函數(shù)。

> Choreographer.java

private Choreographer(Looper looper, int vsyncSource) {
    mLooper = looper;
    // 處理事件
    mHandler = new FrameHandler(looper);
    // USE_VSYNC 在 Android 4.1 之后默認(rèn)為 true,
    // FrameDisplayEventReceiver 是個(gè) vsync 事件接收器 
    mDisplayEventReceiver = USE_VSYNC
            ? new FrameDisplayEventReceiver(looper, vsyncSource)
            : null;
    mLastFrameTimeNanos = Long.MIN_VALUE;

    // 一幀的時(shí)間,60pfs 的話就是 16.7ms
    mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
    // 回調(diào)隊(duì)列
    mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
        mCallbackQueues[i] = new CallbackQueue();
    }
}
 

這里出現(xiàn)了幾個(gè)新面孔,FrameHandler、FrameDisplayEventReceiverCallbackQueue,這里暫且不表,先混個(gè)臉熟,后面會(huì)一一說(shuō)到。

介紹完 Choreographer 是如何初始化的,再回到 Choreographer 發(fā)送任務(wù)那塊。

mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
 

我們看看 mTraversalRunnable 是什么東西。

> ViewRootImpl.java

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

沒(méi)什么特別的,它就是一個(gè) Runnable 對(duì)象,run() 方法中會(huì)執(zhí)行 doTraversal() 方法。

> ViewRootImpl.java

void doTraversal() {
    if (mTraversalScheduled) {
        // 1. mTraversalScheduled 置為 false
        mTraversalScheduled = false;
 // 2. 移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

 // 3. 開(kāi)始布局,測(cè)量,繪制流程
        performTraversals();
        ......
    }
 

再對(duì)比一下最開(kāi)始發(fā)起繪制的 scheduleTraversals() 方法:

> ViewRootImpl.java

void scheduleTraversals() {
    // 1. mTraversalScheduled 置為 true,防止重復(fù)調(diào)用
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
 // 2. 發(fā)送同步屏障,保證優(yōu)先處理異步消息
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
 // 3. 最終會(huì)執(zhí)行 mTraversalRunnable 這個(gè)任務(wù)
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ......
    }
}
 

仔細(xì)分別看一下上面兩個(gè)方法的注釋 1、2、 3,還是很清晰的。mTraversalRunnable 被執(zhí)行后最終會(huì)調(diào)用 performTraversals() 方法,來(lái)完成整個(gè) View 的測(cè)量,布局和繪制流程。

分析到這里,就差最后一步了,mTraversalRunnable 是如何被調(diào)度執(zhí)行的? 我們?cè)倩氐?Choreographer.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) {
    ......
    postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}

// 傳入的參數(shù)依次是 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null,0
private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    ......
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        // 1. 將 mTraversalRunnable 塞入隊(duì)列
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) { // 立即執(zhí)行
            // 2. 由于 delayMillis 是 0,所以會(huì)執(zhí)行到這里
            scheduleFrameLocked(now);
        } else { // 延遲執(zhí)行
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}
 

首先根據(jù) callbackType(這里是 CALLBACK_TRAVERSAL) 將稍后要執(zhí)行的 mTraversalRunnable 放入相應(yīng)隊(duì)列中,其中的具體邏輯就不看了。

然后由于 delayMillis 是 0,所以 dueTime 和 now 是相等的,所以直接執(zhí)行 scheduleFrameLocked(now) 方法。如果 delayMillis 不為 0 的話,會(huì)通過(guò) FrameHandler 發(fā)送一個(gè)延時(shí)消息,最后執(zhí)行的仍然是 scheduleFrameLocked(now) 方法。

> Choreographer.java

private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {
        mFrameScheduled = true;
        if (USE_VSYNC) { // Android 4.1 之后 USE_VSYNCUSE_VSYNC 默認(rèn)為 true
               
            // 如果是當(dāng)前線程,直接申請(qǐng) vsync,否則通過(guò) handler 通信
            if (isRunningOnLooperThreadLocked()) {
                scheduleVsyncLocked();
            } else {
                // 發(fā)送異步消息
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
        } else { // 未開(kāi)啟 vsync,4.1 之后默認(rèn)開(kāi)啟
            final long nextFrameTime = Math.max(
                    mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
            Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, nextFrameTime);
        }
    }
}
 

開(kāi)頭到現(xiàn)在,已經(jīng)好幾次提到了 VSYNC,官方也有一個(gè)視頻介紹 Android Performance Patterns: Understanding VSYNC[14] ,大家可以看一看。簡(jiǎn)而言之,VSYNC 是為了解決屏幕刷新率和 GPU 幀率不一致導(dǎo)致的 “屏幕撕裂” 問(wèn)題。VSYNC 在 PC 端是很久以來(lái)就存在的技術(shù),但在 4.1 之后,Google 才將其引入到 Android 顯示系統(tǒng)中,以解決飽受詬病的 UI 顯示不流暢問(wèn)題。

再說(shuō)的簡(jiǎn)單點(diǎn),可以把 VSYNC 看成一個(gè)由硬件發(fā)出的定時(shí)信號(hào),通過(guò) Choreographer 監(jiān)聽(tīng)這個(gè)信號(hào)。每當(dāng)信號(hào)來(lái)臨時(shí),統(tǒng)一開(kāi)始繪制工作。這就是 scheduleVsyncLocked() 方法的工作內(nèi)容。

> Choreographer.java

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

mDisplayEventReceiverFrameDisplayEventReceiver 對(duì)象,但它并沒(méi)有 scheduleVsync() 方法,而是直接調(diào)用的父類方法。FrameDisplayEventReceiver 的父類是 DisplayEventReceiver 。

> DisplayEventReceiver.java

public abstract class DisplayEventReceiver {

    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 {
         // 注冊(cè)監(jiān)聽(tīng) vsync 信號(hào),會(huì)回調(diào) dispatchVsync() 方法
            nativeScheduleVsync(mReceiverPtr);
        }
    }

    // 有 vsync 信號(hào)時(shí),由 native 調(diào)用此方法
    private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
        // timestampNanos 是 vsync 回調(diào)的時(shí)間
        onVsync(timestampNanos, builtInDisplayId, frame);
    }

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

scheduleVsync() 方法中會(huì)通過(guò) nativeScheduleVsync() 方法注冊(cè)下一次 vsync 信號(hào)的監(jiān)聽(tīng),從方法名也能看出來(lái),下面會(huì)進(jìn)入 native 調(diào)用,水平有限,就不追進(jìn)去了。

注冊(cè)監(jiān)聽(tīng)之后,當(dāng)下次 vsync 信號(hào)來(lái)臨時(shí),會(huì)通過(guò) jni 回調(diào) java 層的 dispatchVsync() 方法,其中又調(diào)用了 onVsync() 方法。父類 DisplayEventReceiveronVsync() 方法是個(gè)空實(shí)現(xiàn),我們?cè)倩氐阶宇?FrameDisplayEventReceiver ,它是 Choreographer 的內(nèi)部類。

> Choreographer.java

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
        implements Runnable {
    private long mTimestampNanos;
    private int mFrame;

    // vsync 信號(hào)監(jiān)聽(tīng)回調(diào)
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        ......
        long now = System.nanoTime();
        // // timestampNanos 是 vsync 回調(diào)的時(shí)間,不能比 now 大
        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;
        }
        ......
        mTimestampNanos = timestampNanos;
        mFrame = frame;
        // 這里傳入的是 this,會(huì)回調(diào)本身的 run() 方法
        Message msg = Message.obtain(mHandler, this);
        // 這是一個(gè)異步消息,保證優(yōu)先執(zhí)行
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }

    @Override
    public void run() {
        doFrame(mTimestampNanos, mFrame);
    }
}
 

onVsync() 回調(diào)中,向主線程發(fā)送了一個(gè)異步消息,注意 sendMessageAtTime() 方法參數(shù)中的時(shí)間是 timestampNanos / TimeUtils。timestampNanos 是 vsync 信號(hào)的時(shí)間戳,單位是納秒,所以這里做一個(gè)除法轉(zhuǎn)換為毫秒。代碼執(zhí)行到這里的時(shí)候 vsync 信號(hào)已經(jīng)發(fā)生,所以 timestampNanos 是比當(dāng)前時(shí)間小的。這樣這個(gè)消息塞進(jìn) MessageQueue 的時(shí)候就可以直接塞到前面了。另外 callback 是 this,所以當(dāng)消息被執(zhí)行時(shí),調(diào)用的是自己的 run() 方法,run() 方法中調(diào)用的是 doFrame() 方法。

> Choreographer.java

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        if (!mFrameScheduled) {
            return; // no work to do
        }
        ......

        long intendedFrameTimeNanos = frameTimeNanos;
        startNanos = System.nanoTime();
  // 計(jì)算超時(shí)時(shí)間
  // frameTimeNanos 是 vsync 信號(hào)回調(diào)的時(shí)間,startNanos 是當(dāng)前時(shí)間戳
  // 相減得到主線程的耗時(shí)時(shí)間
        final long jitterNanos = startNanos - frameTimeNanos;
  // mFrameIntervalNanos 是一幀的時(shí)間
  if (jitterNanos >= mFrameIntervalNanos) {
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
   // 掉幀超過(guò) 30 幀,打印 log
            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;
            frameTimeNanos = startNanos - lastFrameOffset;
        }
        ......
    }

    try {
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

  // doCallBacks() 開(kāi)始執(zhí)行回調(diào)
        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();
    }
    ......
}
 

Choreographer.postCallback() 方法中將 mTraversalRunnable 塞進(jìn)了 mCallbackQueues[] 數(shù)組中,下面的 doCallbacks() 方法就要把它取出來(lái)執(zhí)行了。

> Choreographer.java

    void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            final long now = System.nanoTime();
   // 根據(jù) callbackType 找到對(duì)應(yīng)的 CallbackRecord 對(duì)象
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;
            ......
        }
        try {
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                // 執(zhí)行 callBack
                c.run(frameTimeNanos);
            }
        } finally {
           ......
        }
    }
 

根據(jù) callbackType 找到對(duì)應(yīng)的 mCallbackQueues ,然后執(zhí)行,具體流程就不深入分析了。callbackType 共有四個(gè)類型,分別是 CALLBACK_INPUT、CALLBACK_ANIMATIONCALLBACK_TRAVERSAL、CALLBACK_COMMIT

> Choreographer.CallbackRecord

public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }
 

到此為止,mTraversalRunnable 得以被執(zhí)行,View.invalidate() 的整個(gè)流程就走通了??偨Y(jié)一下:

  1. View.invalidate() 開(kāi)始,最后會(huì)遞歸調(diào)用 parent.invalidateChildInParent() 方法。這里最頂層的 parent 是 ViewRootImpl 。ViewRootImpl 是 DecorView 的 parent,這個(gè)賦值調(diào)用鏈?zhǔn)沁@樣的 ActivityThread.handleResumeActivity -> WindowManagerImpl.addView() -> WindowManagerGlobal.addView() -> ViewRootImpl.setView() -> View.assignParent() 。

  2. ViewRootImpl.invalidateChildInParent() 最終調(diào)用到 scheduleTraversals() 方法,其中建立同步屏障之后,通過(guò) Choreographer.postCallback() 方法提交了任務(wù) mTraversalRunnable,這個(gè)任務(wù)就是負(fù)責(zé) View 的測(cè)量,布局,繪制。

  3. Choreographer.postCallback() 方法通過(guò) DisplayEventReceiver.nativeScheduleVsync() 方法向系統(tǒng)底層注冊(cè)了下一次 vsync 信號(hào)的監(jiān)聽(tīng)。當(dāng)下一次 vsync 來(lái)臨時(shí),系統(tǒng)會(huì)回調(diào)其 dispatchVsync() 方法,最終回調(diào) FrameDisplayEventReceiver.onVsync() 方法。

  4. FrameDisplayEventReceiver.onVsync() 方法中取出之前提交的 mTraversalRunnable 并執(zhí)行。這樣就完成了整個(gè)繪制流程。

 

如何監(jiān)測(cè)應(yīng)用的 FPS?

監(jiān)測(cè)當(dāng)前應(yīng)用的 FPS 很簡(jiǎn)單。每次 vsync 信號(hào)回調(diào)中,都會(huì)執(zhí)行四種類型的 mCallbackQueues 隊(duì)列中的回調(diào)任務(wù)。而 Choreographer 又對(duì)外提供了提交回調(diào)任務(wù)的方法,這個(gè)方法就是 Choreographer.getInstance().postFrameCallback() 。簡(jiǎn)單跟進(jìn)去看一下。

> 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);
}
 

View.invalite() 流程中調(diào)用的 Choreographer.postCallback() 基本一致,僅僅只是 callback 類型不一致,這里是 CALLBACK_ANIMATION 。

我直接給出實(shí)現(xiàn)代碼 :FpsMonitor.kt[15]

object FpsMonitor {

    private const val FPS_INTERVAL_TIME = 1000L
    private var count = 0
    private var isFpsOpen = false
    private val fpsRunnable by lazy { FpsRunnable() }
    private val mainHandler by lazy { Handler(Looper.getMainLooper()) }
    private val listeners = arrayListOf<(Int) -> Unit>()

    fun startMonitor(listener: (Int) -> Unit) {
        // 防止重復(fù)開(kāi)啟
        if (!isFpsOpen) {
            isFpsOpen = true
            listeners.add(listener)
            mainHandler.postDelayed(fpsRunnable, FPS_INTERVAL_TIME)
            Choreographer.getInstance().postFrameCallback(fpsRunnable)
        }
    }

    fun stopMonitor() {
        count = 0
        mainHandler.removeCallbacks(fpsRunnable)
        Choreographer.getInstance().removeFrameCallback(fpsRunnable)
        isFpsOpen = false
    }

    class FpsRunnable : Choreographer.FrameCallback, Runnable {
        override fun doFrame(frameTimeNanos: Long) {
            count++
            Choreographer.getInstance().postFrameCallback(this)
        }

        override fun run() {
            listeners.forEach { it.invoke(count) }
            count = 0
            mainHandler.postDelayed(this, FPS_INTERVAL_TIME)
        }
    }
}
 

大致邏輯是這樣的 :

  • 聲明變量     count 用于統(tǒng)計(jì)回調(diào)次數(shù)
  • 通過(guò)     Choreographer.getInstance().postFrameCallback(fpsRunnable) 注冊(cè)監(jiān)聽(tīng)下一次 vsync信號(hào),提交任務(wù),任務(wù)回調(diào)只做兩件事,一是     count++,二是繼續(xù)注冊(cè)監(jiān)聽(tīng)下一次 vsync 信號(hào) 。
  • 通過(guò) Handler 做個(gè)定時(shí)任務(wù),每隔一秒統(tǒng)計(jì)     count 值并清空。

到此,相信大家對(duì)“怎么監(jiān)測(cè)應(yīng)用的FPS ”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI