本篇內容主要講解“怎么監測應用的FPS ”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么監測應用的FPS ”吧!
創新互聯建站主要從事網站建設、網站制作、網頁設計、企業做網站、公司建網站等業務。立足成都服務豐城,十年網站建設經驗,價格優惠、服務專業,歡迎來電咨詢建站服務:13518219792
即使你不知道 FPS,但你一定聽說過這么一句話,在 Android 中,每一幀的繪制時間不要超過 16.67ms。那么,這個 16.67ms 是怎么來的呢?就是由 FPS 決定的。
FPS,Frame Per Second,每秒顯示的幀數,也叫 幀率。Android 設備的 FPS 一般是 60,也即每秒要刷新 60 幀,所以留給每一幀的繪制時間最多只有 1000/60 = 16.67ms。一旦某一幀的繪制時間超過了限制,就會發生 掉幀,用戶在連續兩幀會看到同樣的畫面。
監測 FPS 在一定程度上可以反應應用的卡頓情況,原理也很簡單,但前提是你對屏幕刷新機制和繪制流程很熟悉。所以我不會直接進入主題,讓我們先從 View.invalidate()
說起。
要探究屏幕刷新機制和 View 繪制流程,View.invalidate()
無疑是個好選擇,它會發起一次繪制流程。
> 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);
// 調用 ViewGroup.invalidateChild()
p.invalidateChild(this, damage);
}
......
}
這里調用到 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);
}
}
這里有一個遞歸,不停的調用父 View 的 invalidateChildInParent()
方法,直到最頂層父 View 為止。這很好理解,僅靠 View 本身是無法繪制自己的,必須依賴最頂層的父 View 才可以測量,布局,繪制整個 View 樹。但是最頂層的父 View 是誰呢?是 setContentView()
傳入的布局文件嗎?不是,它解析之后被塞進了 DecorView
中。是 DecorView 嗎
?也不是,它也是有父親的。
DecorView 的 parent 是誰呢?這就得來到 ActivityThread.handleResume()
方法中。
> ActivityThread.java
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
......
// 1. 回調 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);
......
}
第二步中實際調用的是 WindowManagerImpl.addView()
方法,WindowManagerImpl
中又調用了 WindowManagerGlobal.addView()
方法。
> WindowManagerGlobal.java
// 參數 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. 重點在這
root.setView(view, wparams, panelParentView);
......
}
跟進 ViewRootImpl.setView()
方法。
> ViewRootImpl.java
// 參數 view 就是 DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// 1. 發起首次繪制
requestLayout();
// 2. Binder 調用 Session.addToDisplay(),將 window 添加到屏幕
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
// 3. 重點在這,注意 view 是 DecorView,this 是 ViewRootImpl 本身
view.assignParent(this);
}
}
}
跟進 View.assignParent()
方法。
> View.java
// 參數 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()
,其中遞歸調用 parent 的 invalidateChildInParent()
方法。所以我們在 給 DecorView 找爸爸。現在很清晰了,DecorView 的爸爸就是 ViewRootImpl,所以最終調用的就是 ViewRootImpl.invalidateChildInParent()
方法。
> ViewRootImpl.java
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
// 1. 線程檢查
checkThread();
if (dirty == null) {
// 2. 調用 scheduleTraversals()
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
......
// 3. 調用 scheduleTraversals()
invalidateRectOnScreen(dirty);
return null;
}
無論是注釋 2 處的 invalite()
還是注釋 3 處的 invalidateRectOnScreen()
,最終都會調用到 scheduleTraversals()
方法。
scheduleTraversals()
在 View 繪制流程中是個極其重要的方法,我不得不單獨開一節來聊聊它。
上一節中,我們從 View.invalidate()
方法開始追蹤,一直跟到 ViewRootImpl.scheduleTraversals()
方法。
> ViewRootImpl.java
void scheduleTraversals() {
// 1. 防止重復調用
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 2. 發送同步屏障,保證優先處理異步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 3. 最終會執行 mTraversalRunnable 這個任務
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
mTraversalScheduled
是個布爾值,防止重復調用,在一次 vsync 信號期間多次調用是沒有意義的到這里,鼎鼎大名的 編舞者 —— Choreographer [?k??ri?ɑ?ɡr?f?r]就該出場了(為了避免面試中出現不會讀單詞的尷尬,掌握一下發音還是必須的)。
通過 mChoreographer
發送了一個任務 mTraversalRunnable
,最終會在某個時刻被執行。在看源碼之前,先拋出來幾個問題:
mChoreographer
是在什么時候初始化的?mTraversalRunnable
是個什么鬼?mChoreographer
是如何發送任務以及任務是如何被調度執行的?圍繞這三個問題,我們再回到源碼中。
先來看第一個問題,這就得回到上一節介紹過的 WindowManagerGlobal.addView()
方法。
> WindowManagerGlobal.java
// 參數 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 對象,跟進 ViewRootImpl 的構造函數。
> ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mContext = context;
// 1. IWindowSession 代理對象,與 WMS 進行 Binder 通信
mWindowSession = WindowManagerGlobal.getWindowSession();
......
mThread = Thread.currentThread();
......
// IWindow Binder 對象
mWindow = new W(this);
......
// 2. 初始化 mAttachInfo
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
......
// 3. 初始化 Choreographer,通過 Threadlocal 存儲
mChoreographer = Choreographer.getInstance();
......
}
在 ViewRootImpl
的構造函數中,注釋 3 處初始化了 mChoreographer
,調用的是 Choreographer.getInstance()
方法。
> Choreographer.java
public static Choreographer getInstance() {
return sThreadInstance.get();
}
sThreadInstance
是一個 ThreadLocal<Choreographer>
對象。
> 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 對象
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
所以 mChoreographer 保存在 ThreadLocal 中的線程私有對象。它的構造函數中需要傳入當前線程(這里就是主線程)的 Looper 對象。
這里再插一個題外話,主線程 Looper 是在什么時候創建的?回顧一下應用進程的創建流程:
調用 Process.start()
創建應用進程
ZygoteProcess
負責和 Zygote
進程建立 socket 連接,并將創建進程需要的參數發送給 Zygote
的 socket 服務端
Zygote
服務端接收到參數之后調用 ZygoteConnection.processOneCommand()
處理參數,并 fork 進程
最后通過 findStaticMain()
找到 ActivityThread
類的 main()
方法并執行,子進程就啟動了
ActivityThread
并不是一個線程,但它是運行在主線程上的,主線程 Looper 就是在它的 main()
方法中執行的。
> ActivityThread.java
public static void main(String[] args) {
......
// 創建主線程 Looper
Looper.prepareMainLooper();
......
// 創建 ActivityThread ,并 attach(false)
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
......
// 開啟主線程消息循環
Looper.loop();
}
Looper 也是存儲在 ThreadLocal 中的。
再回到 Choreographer,我們來看一下它的構造函數。
> Choreographer.java
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// 處理事件
mHandler = new FrameHandler(looper);
// USE_VSYNC 在 Android 4.1 之后默認為 true,
// FrameDisplayEventReceiver 是個 vsync 事件接收器
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
// 一幀的時間,60pfs 的話就是 16.7ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 回調隊列
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
這里出現了幾個新面孔,FrameHandler
、FrameDisplayEventReceiver
、CallbackQueue
,這里暫且不表,先混個臉熟,后面會一一說到。
介紹完 Choreographer 是如何初始化的,再回到 Choreographer 發送任務那塊。
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();
}
}
沒什么特別的,它就是一個 Runnable 對象,run() 方法中會執行 doTraversal()
方法。
> ViewRootImpl.java
void doTraversal() {
if (mTraversalScheduled) {
// 1. mTraversalScheduled 置為 false
mTraversalScheduled = false;
// 2. 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 3. 開始布局,測量,繪制流程
performTraversals();
......
}
再對比一下最開始發起繪制的 scheduleTraversals()
方法:
> ViewRootImpl.java
void scheduleTraversals() {
// 1. mTraversalScheduled 置為 true,防止重復調用
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 2. 發送同步屏障,保證優先處理異步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 3. 最終會執行 mTraversalRunnable 這個任務
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
仔細分別看一下上面兩個方法的注釋 1、2、 3,還是很清晰的。mTraversalRunnable
被執行后最終會調用 performTraversals()
方法,來完成整個 View 的測量,布局和繪制流程。
分析到這里,就差最后一步了,mTraversalRunnable 是如何被調度執行的?我們再回到 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);
}
// 傳入的參數依次是 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 塞入隊列
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) { // 立即執行
// 2. 由于 delayMillis 是 0,所以會執行到這里
scheduleFrameLocked(now);
} else { // 延遲執行
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
首先根據 callbackType(這里是 CALLBACK_TRAVERSAL) 將稍后要執行的 mTraversalRunnable 放入相應隊列中,其中的具體邏輯就不看了。
然后由于 delayMillis 是 0,所以 dueTime 和 now 是相等的,所以直接執行 scheduleFrameLocked(now)
方法。如果 delayMillis 不為 0 的話,會通過 FrameHandler 發送一個延時消息,最后執行的仍然是 scheduleFrameLocked(now)
方法。
> Choreographer.java
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) { // Android 4.1 之后 USE_VSYNCUSE_VSYNC 默認為 true
// 如果是當前線程,直接申請 vsync,否則通過 handler 通信
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);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
開頭到現在,已經好幾次提到了 VSYNC
,官方也有一個視頻介紹 Android Performance Patterns: Understanding VSYNC[14] ,大家可以看一看。簡而言之,VSYNC 是為了解決屏幕刷新率和 GPU 幀率不一致導致的 “屏幕撕裂” 問題。VSYNC 在 PC 端是很久以來就存在的技術,但在 4.1 之后,Google 才將其引入到 Android 顯示系統中,以解決飽受詬病的 UI 顯示不流暢問題。
再說的簡單點,可以把 VSYNC 看成一個由硬件發出的定時信號,通過 Choreographer 監聽這個信號。每當信號來臨時,統一開始繪制工作。這就是 scheduleVsyncLocked()
方法的工作內容。
> Choreographer.java
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
mDisplayEventReceiver
是 FrameDisplayEventReceiver
對象,但它并沒有 scheduleVsync()
方法,而是直接調用的父類方法。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 {
// 注冊監聽 vsync 信號,會回調 dispatchVsync() 方法
nativeScheduleVsync(mReceiverPtr);
}
}
// 有 vsync 信號時,由 native 調用此方法
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
// timestampNanos 是 vsync 回調的時間
onVsync(timestampNanos, builtInDisplayId, frame);
}
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}
}
在 scheduleVsync()
方法中會通過 nativeScheduleVsync()
方法注冊下一次 vsync 信號的監聽,從方法名也能看出來,下面會進入 native 調用,水平有限,就不追進去了。
注冊監聽之后,當下次 vsync 信號來臨時,會通過 jni 回調 java 層的 dispatchVsync()
方法,其中又調用了 onVsync()
方法。父類 DisplayEventReceiver
的 onVsync()
方法是個空實現,我們再回到子類 FrameDisplayEventReceiver
,它是 Choreographer 的內部類。
> Choreographer.java
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private long mTimestampNanos;
private int mFrame;
// vsync 信號監聽回調
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
......
long now = System.nanoTime();
// // timestampNanos 是 vsync 回調的時間,不能比 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,會回調本身的 run() 方法
Message msg = Message.obtain(mHandler, this);
// 這是一個異步消息,保證優先執行
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
doFrame(mTimestampNanos, mFrame);
}
}
在 onVsync()
回調中,向主線程發送了一個異步消息,注意 sendMessageAtTime()
方法參數中的時間是 timestampNanos / TimeUtils
。timestampNanos
是 vsync 信號的時間戳,單位是納秒,所以這里做一個除法轉換為毫秒。代碼執行到這里的時候 vsync 信號已經發生,所以 timestampNanos
是比當前時間小的。這樣這個消息塞進 MessageQueue 的時候就可以直接塞到前面了。另外 callback 是 this,所以當消息被執行時,調用的是自己的 run() 方法,run() 方法中調用的是 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();
// 計算超時時間
// frameTimeNanos 是 vsync 信號回調的時間,startNanos 是當前時間戳
// 相減得到主線程的耗時時間
final long jitterNanos = startNanos - frameTimeNanos;
// mFrameIntervalNanos 是一幀的時間
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
// 掉幀超過 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() 開始執行回調
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
塞進了 mCallbackQueues[]
數組中,下面的 doCallbacks()
方法就要把它取出來執行了。
> Choreographer.java
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 根據 callbackType 找到對應的 CallbackRecord 對象
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) {
// 執行 callBack
c.run(frameTimeNanos);
}
} finally {
......
}
}
根據 callbackType
找到對應的 mCallbackQueues
,然后執行,具體流程就不深入分析了。callbackType
共有四個類型,分別是 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT。
> Choreographer.CallbackRecord
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
到此為止,mTraversalRunnable
得以被執行,View.invalidate()
的整個流程就走通了。總結一下:
從 View.invalidate()
開始,最后會遞歸調用 parent.invalidateChildInParent()
方法。這里最頂層的 parent 是 ViewRootImpl
。ViewRootImpl 是 DecorView 的 parent,這個賦值調用鏈是這樣的 ActivityThread.handleResumeActivity -> WindowManagerImpl.addView() -> WindowManagerGlobal.addView() -> ViewRootImpl.setView() -> View.assignParent()
。
ViewRootImpl.invalidateChildInParent()
最終調用到 scheduleTraversals()
方法,其中建立同步屏障之后,通過 Choreographer.postCallback()
方法提交了任務 mTraversalRunnable
,這個任務就是負責 View 的測量,布局,繪制。
Choreographer.postCallback()
方法通過 DisplayEventReceiver.nativeScheduleVsync()
方法向系統底層注冊了下一次 vsync 信號的監聽。當下一次 vsync 來臨時,系統會回調其 dispatchVsync()
方法,最終回調 FrameDisplayEventReceiver.onVsync()
方法。
FrameDisplayEventReceiver.onVsync()
方法中取出之前提交的 mTraversalRunnable
并執行。這樣就完成了整個繪制流程。
監測當前應用的 FPS 很簡單。每次 vsync 信號回調中,都會執行四種類型的 mCallbackQueues
隊列中的回調任務。而 Choreographer
又對外提供了提交回調任務的方法,這個方法就是 Choreographer.getInstance().postFrameCallback()
。簡單跟進去看一下。
> 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()
流程中調用的 Choreographer.postCallback()
基本一致,僅僅只是 callback 類型不一致,這里是 CALLBACK_ANIMATION
。
我直接給出實現代碼 :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) {
// 防止重復開啟
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
用于統計回調次數Choreographer.getInstance().postFrameCallback(fpsRunnable)
注冊監聽下一次 vsync信號,提交任務,任務回調只做兩件事,一是
count++
,二是繼續注冊監聽下一次 vsync 信號 。count
值并清空。到此,相信大家對“怎么監測應用的FPS ”有了更深的了解,不妨來實際操作一番吧!這里是創新互聯網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
文章標題:怎么監測應用的FPS
當前網址:http://vcdvsql.cn/article32/jhispc.html
成都網站建設公司_創新互聯,為您提供域名注冊、網站營銷、小程序開發、網站改版、商城網站、ChatGPT
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯