77
88package com .facebook .react .modules .core ;
99
10- import androidx . annotation . GuardedBy ;
10+ import android . view . Choreographer ;
1111import androidx .annotation .Nullable ;
1212import com .facebook .common .logging .FLog ;
1313import com .facebook .infer .annotation .Assertions ;
1717
1818/**
1919 * A simple wrapper around Choreographer that allows us to control the order certain callbacks are
20- * executed within a given frame. The main difference is that we enforce this is accessed from the
21- * UI thread: this is because this ordering cannot be guaranteed across multiple threads .
20+ * executed within a given frame. The wrapped Choreographer instance will always be the main thread
21+ * one and the API's are safe to use from any thread .
2222 */
23- public class ReactChoreographer {
23+ public final class ReactChoreographer {
2424
2525 public enum CallbackType {
2626
@@ -67,37 +67,65 @@ public static ReactChoreographer getInstance() {
6767 }
6868
6969 // This needs to be volatile due to double checked locking issue - https://fburl.com/z409owpf
70- private @ Nullable volatile ChoreographerCompat mChoreographer ;
71- private final ReactChoreographerDispatcher mReactChoreographerDispatcher ;
72- private final Object mCallbackQueuesLock = new Object ();
73-
74- @ GuardedBy ("mCallbackQueuesLock" )
75- private final ArrayDeque <ChoreographerCompat .FrameCallback >[] mCallbackQueues ;
70+ private @ Nullable volatile Choreographer mChoreographer ;
71+
72+ private final ArrayDeque <Choreographer .FrameCallback >[] mCallbackQueues ;
73+
74+ private final Choreographer .FrameCallback mFrameCallback =
75+ new Choreographer .FrameCallback () {
76+ @ Override
77+ public void doFrame (long frameTimeNanos ) {
78+ synchronized (mCallbackQueues ) {
79+ // Callbacks run once and are then automatically removed, the callback will
80+ // be posted again from postFrameCallback
81+ mHasPostedCallback = false ;
82+
83+ for (int i = 0 ; i < mCallbackQueues .length ; i ++) {
84+ ArrayDeque <Choreographer .FrameCallback > callbackQueue = mCallbackQueues [i ];
85+ int initialLength = callbackQueue .size ();
86+ for (int callback = 0 ; callback < initialLength ; callback ++) {
87+ Choreographer .FrameCallback frameCallback = callbackQueue .pollFirst ();
88+ if (frameCallback != null ) {
89+ frameCallback .doFrame (frameTimeNanos );
90+ mTotalCallbacks --;
91+ } else {
92+ FLog .e (ReactConstants .TAG , "Tried to execute non-existent frame callback" );
93+ }
94+ }
95+ }
96+ maybeRemoveFrameCallback ();
97+ }
98+ }
99+ };
76100
77101 private int mTotalCallbacks = 0 ;
78102 private boolean mHasPostedCallback = false ;
79103
80104 private ReactChoreographer () {
81- mReactChoreographerDispatcher = new ReactChoreographerDispatcher ();
82105 mCallbackQueues = new ArrayDeque [CallbackType .values ().length ];
83106 for (int i = 0 ; i < mCallbackQueues .length ; i ++) {
84107 mCallbackQueues [i ] = new ArrayDeque <>();
85108 }
86- initializeChoreographer (null );
109+
110+ UiThreadUtil .runOnUiThread (
111+ () -> {
112+ mChoreographer = Choreographer .getInstance ();
113+ });
87114 }
88115
89- public void postFrameCallback (
90- CallbackType type , ChoreographerCompat .FrameCallback frameCallback ) {
91- synchronized (mCallbackQueuesLock ) {
116+ public void postFrameCallback (CallbackType type , Choreographer .FrameCallback frameCallback ) {
117+ synchronized (mCallbackQueues ) {
92118 mCallbackQueues [type .getOrder ()].addLast (frameCallback );
93119 mTotalCallbacks ++;
94120 Assertions .assertCondition (mTotalCallbacks > 0 );
121+
95122 if (!mHasPostedCallback ) {
96123 if (mChoreographer == null ) {
97- initializeChoreographer (
98- new Runnable () {
99- @ Override
100- public void run () {
124+ // Schedule on the main thread, at which point the constructor's async work will have
125+ // completed
126+ UiThreadUtil .runOnUiThread (
127+ () -> {
128+ synchronized (mCallbackQueues ) {
101129 postFrameCallbackOnChoreographer ();
102130 }
103131 });
@@ -110,33 +138,15 @@ public void run() {
110138
111139 /**
112140 * This method writes on mHasPostedCallback and it should be called from another method that has
113- * the lock mCallbackQueuesLock
141+ * the lock mCallbackQueues
114142 */
115143 private void postFrameCallbackOnChoreographer () {
116- mChoreographer .postFrameCallback (mReactChoreographerDispatcher );
144+ mChoreographer .postFrameCallback (mFrameCallback );
117145 mHasPostedCallback = true ;
118146 }
119147
120- public void initializeChoreographer (@ Nullable final Runnable runnable ) {
121- UiThreadUtil .runOnUiThread (
122- new Runnable () {
123- @ Override
124- public void run () {
125- synchronized (ReactChoreographer .class ) {
126- if (mChoreographer == null ) {
127- mChoreographer = ChoreographerCompat .getInstance ();
128- }
129- }
130- if (runnable != null ) {
131- runnable .run ();
132- }
133- }
134- });
135- }
136-
137- public void removeFrameCallback (
138- CallbackType type , ChoreographerCompat .FrameCallback frameCallback ) {
139- synchronized (mCallbackQueuesLock ) {
148+ public void removeFrameCallback (CallbackType type , Choreographer .FrameCallback frameCallback ) {
149+ synchronized (mCallbackQueues ) {
140150 if (mCallbackQueues [type .getOrder ()].removeFirstOccurrence (frameCallback )) {
141151 mTotalCallbacks --;
142152 maybeRemoveFrameCallback ();
@@ -148,39 +158,15 @@ public void removeFrameCallback(
148158
149159 /**
150160 * This method reads and writes on mHasPostedCallback and it should be called from another method
151- * that already has the lock mCallbackQueuesLock .
161+ * that already has the lock on mCallbackQueues .
152162 */
153163 private void maybeRemoveFrameCallback () {
154164 Assertions .assertCondition (mTotalCallbacks >= 0 );
155165 if (mTotalCallbacks == 0 && mHasPostedCallback ) {
156166 if (mChoreographer != null ) {
157- mChoreographer .removeFrameCallback (mReactChoreographerDispatcher );
167+ mChoreographer .removeFrameCallback (mFrameCallback );
158168 }
159169 mHasPostedCallback = false ;
160170 }
161171 }
162-
163- private class ReactChoreographerDispatcher extends ChoreographerCompat .FrameCallback {
164-
165- @ Override
166- public void doFrame (long frameTimeNanos ) {
167- synchronized (mCallbackQueuesLock ) {
168- mHasPostedCallback = false ;
169- for (int i = 0 ; i < mCallbackQueues .length ; i ++) {
170- ArrayDeque <ChoreographerCompat .FrameCallback > callbackQueue = mCallbackQueues [i ];
171- int initialLength = callbackQueue .size ();
172- for (int callback = 0 ; callback < initialLength ; callback ++) {
173- ChoreographerCompat .FrameCallback frameCallback = callbackQueue .pollFirst ();
174- if (frameCallback != null ) {
175- frameCallback .doFrame (frameTimeNanos );
176- mTotalCallbacks --;
177- } else {
178- FLog .e (ReactConstants .TAG , "Tried to execute non-existent frame callback" );
179- }
180- }
181- }
182- maybeRemoveFrameCallback ();
183- }
184- }
185- }
186172}
0 commit comments