Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to monitor the FPS of the application

2025-01-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article focuses on "how to monitor the FPS of an application". Interested friends may wish to have a look at it. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn how to monitor the FPS of an application.

What is FPS?

Even if you don't know FPS, you must have heard the saying that in Android, the drawing time of each frame should not exceed 16.67ms. So, where did this 16.67ms come from? Is decided by FPS.

FPS,Frame Per Second, the number of frames displayed per second, also known as the frame rate. The FPS of an Android device is typically 60, which means 60 frames per second, so there is only a maximum of 1000 FPS 60 = 16.67ms left for each frame. Once the drawing time of a frame exceeds the limit, the frame will be dropped, and the user will see the same picture in two consecutive frames.

Monitoring FPS can reflect the stutter of the application to some extent, and the principle is also very simple, but only if you are familiar with the screen refresh mechanism and the drawing process. So I'm not going to go straight to the topic, let's start with View.invalidate ().

Start with View.invalidate ()

To explore the screen refresh mechanism and the View drawing process, View.invalidate () is undoubtedly a good choice, it will initiate a drawing process.

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

}

}

There is a recursion that keeps calling the invalidateChildInParent () method of the parent View up to the topmost parent View. It is easy to understand that View alone cannot draw its own, and must rely on the top-level parent View to measure, layout, and draw the entire View tree. But who is the parent View at the top level? Is it a layout file passed in by setContentView ()? No, it is parsed and stuffed into the DecorView. Is that DecorView? No, it has a father, too.

Who is the parent of DecorView? This brings you to the ActivityThread.handleResume () method.

> ActivityThread.java

Public void handleResumeActivity (IBinder token, boolean finalStateRequest, boolean isForward, String reason) {

.

/ / 1. Callback onResume ()

Final ActivityClientRecord r = performResumeActivity (token, finalStateRequest, reason)

.

View decor = r.window.getDecorView ()

Decor.setVisibility (View.INVISIBLE)

ViewManager wm = a.getWindowManager ()

/ / 2. Add decorView to WindowManager

Wm.addView (decor, l)

.

}

In the second step, the WindowManagerImpl.addView () method is actually called, and the WindowManagerGlobal.addView () method is called in WindowManagerImpl.

> WindowManagerGlobal.java

/ / Parameter view is DecorView

Public void addView (View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {

.

ViewRootImpl root

/ / 1. Initialize ViewRootImpl

Root = new ViewRootImpl (view.getContext (), display)

MViews.add (view)

MRoots.add (root)

/ / 2. That's the point.

Root.setView (view, wparams, panelParentView)

.

}

Follow up the ViewRootImpl.setView () method.

> ViewRootImpl.java

/ / Parameter view is DecorView

Public void setView (View view, WindowManager.LayoutParams attrs, View panelParentView) {

Synchronized (this) {

If (mView = = null) {

MView = view

/ / 1. Initiate the first drawing

RequestLayout ()

/ / 2. Binder calls Session.addToDisplay () to add window to the screen

Res = mWindowSession.addToDisplay (mWindow, mSeq, mWindowAttributes

GetHostVisibility (), mDisplay.getDisplayId (), mWinFrame

MAttachInfo.mContentInsets, mAttachInfo.mStableInsets

MAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel)

/ / 3. The point is, notice that view is DecorView,this and ViewRootImpl itself.

View.assignParent (this)

}

}

}

Follow up the View.assignParent () method.

> View.java

/ / Parameter parent is 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")

}

}

Remember what we've been doing for so long? To explore the refresh process of View, we follow the View.invalidate () method all the way to ViewGroup.invalidateChild (), where the invalidateChildInParent () method of parent is called recursively. So we're looking for DecorView's dad. It is now clear that DecorView's father is ViewRootImpl, so the final call is the ViewRootImpl.invalidateChildInParent () method.

> ViewRootImpl.java

Public ViewParent invalidateChildInParent (int [] location, Rect dirty) {

/ / 1. Thread check

CheckThread ()

If (dirty = = null) {

/ / 2. Call scheduleTraversals ()

Invalidate ()

Return null

} else if (dirty.isEmpty () & &! mIsAnimating) {

Return null

}

.

/ / 3. Call scheduleTraversals ()

InvalidateRectOnScreen (dirty)

Return null

}

Whether it's invalite () at comment 2 or invalidateRectOnScreen () at comment 3, you end up calling the scheduleTraversals () method.

ScheduleTraversals () is an extremely important method in the View drawing process, and I have to have a separate section to talk about it.

The "choreographer" that connects the preceding and the following

In the previous section, we started with the View.invalidate () method and followed it all the way to the ViewRootImpl.scheduleTraversals () method.

> ViewRootImpl.java

Void scheduleTraversals () {

/ / 1. Prevent repeated calls

If (! mTraversalScheduled) {

MTraversalScheduled = true

/ / 2. Send a synchronization barrier to ensure priority for asynchronous messages

MTraversalBarrier = mHandler.getLooper (). GetQueue (). PostSyncBarrier ()

/ / 3. Will eventually perform the task of mTraversalRunnable.

MChoreographer.postCallback (

Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)

.

}

}

MTraversalScheduled is a Boolean value that prevents repeated calls. Multiple calls during a vsync signal are meaningless, using Handler's synchronization barrier mechanism to give priority to asynchronous message Choreographer debut.

At this point, it's time for the famous choreographer Choreographer (to avoid the embarrassment of not being able to read words in the interview, mastering the pronunciation is still necessary).

A task mTraversalRunnable is sent through mChoreographer and will eventually be executed at some point. Before looking at the source code, throw out a few questions:

When was mChoreographer initialized? What the heck is mTraversalRunnable? How does mChoreographer send tasks and how are tasks scheduled for execution?

Around these three questions, let's go back to the source code.

Let's take a look at the first question, which goes back to the WindowManagerGlobal.addView () method introduced in the previous section.

> WindowManagerGlobal.java

/ / Parameter view is DecorView

Public void addView (View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {

.

ViewRootImpl root

/ / 1. Initialize ViewRootImpl

Root = new ViewRootImpl (view.getContext (), display)

MViews.add (view)

MRoots.add (root)

Root.setView (view, wparams, panelParentView)

.

}

A new ViewRootImpl object is created at note 1 to follow up the constructor of ViewRootImpl.

> ViewRootImpl.java

Public ViewRootImpl (Context context, Display display) {

MContext = context

/ / 1. IWindowSession proxy object for Binder communication with WMS

MWindowSession = WindowManagerGlobal.getWindowSession ()

.

MThread = Thread.currentThread ()

.

/ / IWindow Binder object

MWindow = new W (this)

.

/ / 2. Initialize mAttachInfo

MAttachInfo = new View.AttachInfo (mWindowSession, mWindow, display, this, mHandler, this

Context)

.

/ / 3. Initialize Choreographer and store through Threadlocal

MChoreographer = Choreographer.getInstance ()

.

}

In the constructor of ViewRootImpl, mChoreographer is initialized at note 3, and the Choreographer.getInstance () method is called.

> Choreographer.java

Public static Choreographer getInstance () {

Return sThreadInstance.get ()

}

SThreadInstance is a ThreadLocal object.

> Choreographer.java

Private static final ThreadLocal sThreadInstance =

New ThreadLocal () {

@ Override

Protected Choreographer initialValue () {

Looper looper = Looper.myLooper ()

If (looper = = null) {

Throw new IllegalStateException ("The current thread must have a looper!")

}

/ / create a new Choreographer object

Choreographer choreographer = new Choreographer (looper, VSYNC_SOURCE_APP)

If (looper = = Looper.getMainLooper ()) {

MMainInstance = choreographer

}

Return choreographer

}

}

So mChoreographer saves the thread private object in ThreadLocal. Its constructor needs to pass in the Looper object of the current thread (in this case, the main thread).

Another digression here, when was the main thread Looper created? Review the process of creating the application process:

Call Process.start () to create an application process

ZygoteProcess is responsible for establishing a socket connection with the Zygote process and sending the parameters needed by the creation process to the socket server of Zygote

After receiving the parameters, the Zygote server calls ZygoteConnection.processOneCommand () to process the parameters and fork the process.

Finally, the main () method of the ActivityThread class is found through findStaticMain () and executed, and the child process is started.

ActivityThread is not a thread, but it runs on the main thread, and the main thread Looper is executed in its main () method.

> ActivityThread.java

Public static void main (String [] args) {

.

/ / create the main thread Looper

Looper.prepareMainLooper ()

.

/ / create ActivityThread and attach (false)

ActivityThread thread = new ActivityThread ()

Thread.attach (false, startSeq)

.

/ / start the main thread message loop

Looper.loop ()

}

Looper is also stored in ThreadLocal.

Going back to Choreographer, let's take a look at its constructor.

> Choreographer.java

Private Choreographer (Looper looper, int vsyncSource) {

MLooper = looper

/ / handling events

MHandler = new FrameHandler (looper)

/ / USE_VSYNC defaults to true after Android 4.1,

/ / FrameDisplayEventReceiver is a vsync event receiver

MDisplayEventReceiver = USE_VSYNC

? New FrameDisplayEventReceiver (looper, vsyncSource)

: null

MLastFrameTimeNanos = Long.MIN_VALUE

/ / the time of a frame. 60pfs is 16.7ms.

MFrameIntervalNanos = (long) (1000000000 / getRefreshRate ())

/ / callback queue

MCallbackQueues = new callback queue [CALLBACK _ LAST + 1]

For (int I = 0; I ViewRootImpl.java

Final TraversalRunnable mTraversalRunnable = new TraversalRunnable ()

Final class TraversalRunnable implements Runnable {

@ Override

Public void run () {

DoTraversal ()

}

}

Nothing special, it's just a Runnable object, and the doTraversal () method is executed in the run () method.

> ViewRootImpl.java

Void doTraversal () {

If (mTraversalScheduled) {

/ / 1. Set mTraversalScheduled to false

MTraversalScheduled = false

/ / 2. Remove the synchronization barrier

MHandler.getLooper (). GetQueue (). RemoveSyncBarrier (mTraversalBarrier)

/ / 3. Start the layout, measurement, and drawing process

PerformTraversals ()

.

}

Compare again with the scheduleTraversals () method that initially initiated the drawing:

> ViewRootImpl.java

Void scheduleTraversals () {

/ / 1. Set mTraversalScheduled to true to prevent repeated calls

If (! mTraversalScheduled) {

MTraversalScheduled = true

/ / 2. Send a synchronization barrier to ensure priority for asynchronous messages

MTraversalBarrier = mHandler.getLooper (). GetQueue (). PostSyncBarrier ()

/ / 3. Will eventually perform the task of mTraversalRunnable.

MChoreographer.postCallback (

Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)

.

}

}

If you take a closer look at the notes 1, 2, and 3 of the above two methods, it is still very clear. After the mTraversalRunnable is executed, the performTraversals () method is eventually called to complete the entire View measurement, layout and drawing process.

At this point in the analysis, the last step is, how is mTraversalRunnable scheduled for execution? Let's go back to the Choreographer.postCallback () method.

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

}

/ / the parameters passed are Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, and null,0 in turn

Private void postCallbackDelayedInternal (int callbackType

Object action, Object token, long delayMillis) {

.

Synchronized (mLock) {

Final long now = SystemClock.uptimeMillis ()

Final long dueTime = now + delayMillis

/ / 1. Cram mTraversalRunnable into the queue

MCallbackQueues [callbackType] .addCallbackLocked (dueTime, action, token)

If (dueTime Choreographer.java

Private void scheduleFrameLocked (long now) {

If (! mFrameScheduled) {

MFrameScheduled = true

If (USE_VSYNC) {/ / Android 4.1After USE_VSYNCUSE_VSYNC defaults to true

/ / if it is the current thread, apply for vsync directly, otherwise communicate through handler

If (isRunningOnLooperThreadLocked ()) {

ScheduleVsyncLocked ()

} else {

/ / send asynchronous messages

Message msg = mHandler.obtainMessage (MSG_DO_SCHEDULE_VSYNC)

Msg.setAsynchronous (true)

MHandler.sendMessageAtFrontOfQueue (msg)

}

} else {/ / when vsync,4.1 is not enabled, it is enabled by default

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)

}

}

}

Since the beginning, VSYNC has been mentioned several times, and there is also a video introduction to Android Performance Patterns: Understanding VSYNC [14], which you can take a look at. In short, VSYNC is to solve the problem of "screen tearing" caused by the inconsistency between the screen refresh rate and the GPU frame rate. VSYNC has existed for a long time on the PC side, but it was introduced into the Android display system by Google after 4.1in order to solve the problem that UI display is not smooth.

To put it simply, VSYNC can be regarded as a timing signal sent by hardware, and the signal is monitored by Choreographer. Whenever the signal comes, the unity begins to draw. This is what the scheduleVsyncLocked () method does.

> Choreographer.java

Private void scheduleVsyncLocked () {

MDisplayEventReceiver.scheduleVsync ()

}

MDisplayEventReceiver is a FrameDisplayEventReceiver object, but it does not have a scheduleVsync () method, but a parent class method that is called directly. The parent class of FrameDisplayEventReceiver is 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 {

/ / registers to listen for vsync signals and calls back the dispatchVsync () method

NativeScheduleVsync (mReceiverPtr)

}

}

/ / this method is called by native when there is a vsync signal

Private void dispatchVsync (long timestampNanos, int builtInDisplayId, int frame) {

/ / timestampNanos is the time of vsync callback

OnVsync (timestampNanos, builtInDisplayId, frame)

}

Public void onVsync (long timestampNanos, int builtInDisplayId, int frame) {

}

}

In the scheduleVsync () method, the next listening of the vsync signal will be registered through the nativeScheduleVsync () method. As can be seen from the method name, the following native call will be entered, and the level is limited, so it will not be pursued.

After registering listening, the next time the vsync signal comes, the dispatchVsync () method of the java layer is called back through jni, where the onVsync () method is called. The onVsync () method of the parent class DisplayEventReceiver is an empty implementation, so let's go back to the subclass FrameDisplayEventReceiver, which is the inner class of Choreographer.

> Choreographer.java

Private final class FrameDisplayEventReceiver extends DisplayEventReceiver

Implements Runnable {

Private long mTimestampNanos

Private int mFrame

/ / vsync signal monitoring callback

@ Override

Public void onVsync (long timestampNanos, int builtInDisplayId, int frame) {

.

Long now = System.nanoTime ()

/ timestampNanos is the time of vsync callback, which cannot be greater than 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

/ / the this passed in here will call back its own run () method

Message msg = Message.obtain (mHandler, this)

/ / this is an asynchronous message, which ensures priority execution.

Msg.setAsynchronous (true)

MHandler.sendMessageAtTime (msg, timestampNanos / TimeUtils.NANOS_PER_MS)

}

@ Override

Public void run () {

DoFrame (mTimestampNanos, mFrame)

}

}

In the onVsync () callback, an asynchronous message is sent to the main thread. Notice that the time in the sendMessageAtTime () method parameter is timestampNanos / TimeUtils. TimestampNanos is the timestamp of the vsync signal in nanoseconds, so here we do a division and convert it to milliseconds. By the time the code is executed here, the vsync signal has already occurred, so the timestampNanos is smaller than the current time. In this way, when the message is stuffed into the MessageQueue, it can be stuffed directly to the front. In addition, callback is this, so when the message is executed, it calls its own run () method, and the run () method calls the doFrame () method.

> 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 ()

/ / calculate the timeout

/ / frameTimeNanos is the time of callback of vsync signal, and startNanos is the current timestamp

/ / subtract the time it takes to get the main thread

Final long jitterNanos = startNanos-frameTimeNanos

/ / mFrameIntervalNanos is the time of one frame

If (jitterNanos > = mFrameIntervalNanos) {

Final long skippedFrames = jitterNanos / mFrameIntervalNanos

/ / drop more than 30 frames and print 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 () starts the callback

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 ()

}

.

}

The mTraversalRunnable is stuffed into the mCallbackQueues [] array in the Choreographer.postCallback () method, and the following doCallbacks () method is about to take it out and execute it.

> Choreographer.java

Void doCallbacks (int callbackType, long frameTimeNanos) {

CallbackRecord callbacks

Synchronized (mLock) {

Final long now = System.nanoTime ()

/ / find the corresponding CallbackRecord object according to callbackType

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) {

/ / execute callBack

C.run (frameTimeNanos)

}

} finally {

.

}

}

Find the corresponding mCallbackQueues according to callbackType, and then execute it, and the specific process will not be analyzed in depth. There are four types of callbackType, which are CALLBACK_INPUT, CALLBACK_ANIMATION, CALLBACK_TRAVERSAL and CALLBACK_COMMIT.

> Choreographer.CallbackRecord

Public void run (long frameTimeNanos) {

If (token = = FRAME_CALLBACK_TOKEN) {

((FrameCallback) action) .doFrame (frameTimeNanos)

} else {

(Runnable) action) .run ()

}

}

At this point, the mTraversalRunnable is executed and the entire process of View.invalidate () goes through. To sum up:

Starting with View.invalidate (), the parent.invalidateChildInParent () method is called recursively. The top parent here is ViewRootImpl. ViewRootImpl is the parent of DecorView, and the chain of assignment calls is ActivityThread.handleResumeActivity-> WindowManagerImpl.addView ()-> WindowManagerGlobal.addView ()-> ViewRootImpl.setView ()-> View.assignParent ().

ViewRootImpl.invalidateChildInParent () finally calls the scheduleTraversals () method, where after setting up the synchronization barrier, the task mTraversalRunnable is submitted through the Choreographer.postCallback () method, which is responsible for measuring, laying out, and drawing the View.

The Choreographer.postCallback () method registers the next monitoring of the vsync signal to the underlying layer of the system through the DisplayEventReceiver.nativeScheduleVsync () method. When the next vsync comes, the system will call back its dispatchVsync () method and finally its FrameDisplayEventReceiver.onVsync () method.

The previously submitted mTraversalRunnable is taken out in the FrameDisplayEventReceiver.onVsync () method and executed. In this way, the whole drawing process is completed.

How to monitor the FPS of the application?

Monitoring the current application of FPS is simple. In each vsync signal callback, callback tasks in four types of mCallbackQueues queues are performed. On the other hand, Choreographer provides a method to submit callback tasks, which is Choreographer.getInstance (). PostFrameCallback (). Simply go in and have a look.

> Choreographer.java

Public void postFrameCallback (FrameCallback callback) {

PostFrameCallbackDelayed (callback, 0)

}

Public void postFrameCallbackDelayed (FrameCallback callback, long delayMillis) {

.

/ / the type here is CALLBACK_ANIMATION

PostCallbackDelayedInternal (CALLBACK_ANIMATION

Callback, FRAME_CALLBACK_TOKEN, delayMillis)

}

It is basically the same as the Choreographer.postCallback () called in the View.invalite () process, except that the callback type is not consistent, in this case CALLBACK_ANIMATION.

I directly give the implementation code: FpsMonitor.kt [15]

Object FpsMonitor {

Private const val FPS_INTERVAL_TIME = 1000L

Private var count = 0

Private var isFpsOpen = false

Privateval fpsRunnable by lazy {FpsRunnable ()}

Privateval mainHandler by lazy {Handler (Looper.getMainLooper ())}

Privateval listeners = arrayListOf Unit > ()

Fun startMonitor (listener: (Int)-> Unit) {

/ / prevent repeated opening

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)

}

}

}

The general logic goes like this:

The declaration variable count is used to count the number of callbacks to monitor the next vsync signal through Choreographer.getInstance (). PostFrameCallback (fpsRunnable) registration, submit the task, the task callback only does two things, one is count++, the other is to continue to register to monitor the next vsync signal. Do a scheduled task through Handler, count the count value every other second and clear it. At this point, I believe you have a deeper understanding of "how to monitor the FPS of the application". You might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report