mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Moving timing logic out of TimingModule and into a new class
Summary: Decoupling the logic for managing timers from the native module interface in TimingModule. I'm doing this so we can more easily share this logic with bridgeless RN, which won't use a native module for timers but instead will define the bindings with JSI. This diff just moves things around and doesn't change any of the behavior. Reviewed By: PeteTheHeat Differential Revision: D17282187 fbshipit-source-id: ef54254dd0c7e2f6e294e9ae5a7d4b010f98de2e
This commit is contained in:
committed by
Facebook Github Bot
parent
e9b50fa4ee
commit
4d774bdc0d
@@ -0,0 +1,349 @@
|
||||
package com.facebook.react.modules.core;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.common.SystemClock;
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager;
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskContext;
|
||||
import java.util.Comparator;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* This class is the native implementation for JS timer execution on Android. It schedules JS timers
|
||||
* to be invoked on frame boundaries using {@link ReactChoreographer}.
|
||||
*
|
||||
* <p>This is used by the NativeModule {@link TimingModule}.
|
||||
*/
|
||||
public class JavaTimerManager {
|
||||
|
||||
// These timing constants should be kept in sync with the ones in `JSTimers.js`.
|
||||
// The minimum time in milliseconds left in the frame to call idle callbacks.
|
||||
private static final float IDLE_CALLBACK_FRAME_DEADLINE_MS = 1.f;
|
||||
// The total duration of a frame in milliseconds, this assumes that devices run at 60 fps.
|
||||
// TODO: Lower frame duration on devices that are too slow to run consistently
|
||||
// at 60 fps.
|
||||
private static final float FRAME_DURATION_MS = 1000.f / 60.f;
|
||||
|
||||
private static class Timer {
|
||||
private final int mCallbackID;
|
||||
private final boolean mRepeat;
|
||||
private final int mInterval;
|
||||
private long mTargetTime;
|
||||
|
||||
private Timer(int callbackID, long initialTargetTime, int duration, boolean repeat) {
|
||||
mCallbackID = callbackID;
|
||||
mTargetTime = initialTargetTime;
|
||||
mInterval = duration;
|
||||
mRepeat = repeat;
|
||||
}
|
||||
}
|
||||
|
||||
private class TimerFrameCallback extends ChoreographerCompat.FrameCallback {
|
||||
|
||||
// Temporary map for constructing the individual arrays of timers to call
|
||||
private @Nullable WritableArray mTimersToCall = null;
|
||||
|
||||
/** Calls all timers that have expired since the last time this frame callback was called. */
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
if (isPaused.get() && !isRunningTasks.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long frameTimeMillis = frameTimeNanos / 1000000;
|
||||
synchronized (mTimerGuard) {
|
||||
while (!mTimers.isEmpty() && mTimers.peek().mTargetTime < frameTimeMillis) {
|
||||
Timer timer = mTimers.poll();
|
||||
if (mTimersToCall == null) {
|
||||
mTimersToCall = Arguments.createArray();
|
||||
}
|
||||
mTimersToCall.pushInt(timer.mCallbackID);
|
||||
if (timer.mRepeat) {
|
||||
timer.mTargetTime = frameTimeMillis + timer.mInterval;
|
||||
mTimers.add(timer);
|
||||
} else {
|
||||
mTimerIdsToTimers.remove(timer.mCallbackID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mTimersToCall != null) {
|
||||
mReactApplicationContext.getJSModule(JSTimers.class).callTimers(mTimersToCall);
|
||||
mTimersToCall = null;
|
||||
}
|
||||
|
||||
mReactChoreographer.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, this);
|
||||
}
|
||||
}
|
||||
|
||||
private class IdleFrameCallback extends ChoreographerCompat.FrameCallback {
|
||||
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
if (isPaused.get() && !isRunningTasks.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the JS thread is busy for multiple frames we cancel any other pending runnable.
|
||||
if (mCurrentIdleCallbackRunnable != null) {
|
||||
mCurrentIdleCallbackRunnable.cancel();
|
||||
}
|
||||
|
||||
mCurrentIdleCallbackRunnable = new IdleCallbackRunnable(frameTimeNanos);
|
||||
mReactApplicationContext.runOnJSQueueThread(mCurrentIdleCallbackRunnable);
|
||||
|
||||
mReactChoreographer.postFrameCallback(ReactChoreographer.CallbackType.IDLE_EVENT, this);
|
||||
}
|
||||
}
|
||||
|
||||
private class IdleCallbackRunnable implements Runnable {
|
||||
private volatile boolean mCancelled = false;
|
||||
private final long mFrameStartTime;
|
||||
|
||||
public IdleCallbackRunnable(long frameStartTime) {
|
||||
mFrameStartTime = frameStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
long frameTimeMillis = mFrameStartTime / 1000000;
|
||||
long timeSinceBoot = SystemClock.uptimeMillis();
|
||||
long frameTimeElapsed = timeSinceBoot - frameTimeMillis;
|
||||
long time = SystemClock.currentTimeMillis();
|
||||
long absoluteFrameStartTime = time - frameTimeElapsed;
|
||||
|
||||
if (FRAME_DURATION_MS - (float) frameTimeElapsed < IDLE_CALLBACK_FRAME_DEADLINE_MS) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean sendIdleEvents;
|
||||
synchronized (mIdleCallbackGuard) {
|
||||
sendIdleEvents = mSendIdleEvents;
|
||||
}
|
||||
|
||||
if (sendIdleEvents) {
|
||||
mReactApplicationContext
|
||||
.getJSModule(JSTimers.class)
|
||||
.callIdleCallbacks(absoluteFrameStartTime);
|
||||
}
|
||||
|
||||
mCurrentIdleCallbackRunnable = null;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
mCancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private final ReactApplicationContext mReactApplicationContext;
|
||||
private final ReactChoreographer mReactChoreographer;
|
||||
private final DevSupportManager mDevSupportManager;
|
||||
private final Object mTimerGuard = new Object();
|
||||
private final Object mIdleCallbackGuard = new Object();
|
||||
private final PriorityQueue<Timer> mTimers;
|
||||
private final SparseArray<Timer> mTimerIdsToTimers;
|
||||
private final AtomicBoolean isPaused = new AtomicBoolean(true);
|
||||
private final AtomicBoolean isRunningTasks = new AtomicBoolean(false);
|
||||
private final TimerFrameCallback mTimerFrameCallback = new TimerFrameCallback();
|
||||
private final IdleFrameCallback mIdleFrameCallback = new IdleFrameCallback();
|
||||
private @Nullable IdleCallbackRunnable mCurrentIdleCallbackRunnable;
|
||||
private boolean mFrameCallbackPosted = false;
|
||||
private boolean mFrameIdleCallbackPosted = false;
|
||||
private boolean mSendIdleEvents = false;
|
||||
|
||||
public JavaTimerManager(
|
||||
ReactApplicationContext reactContext,
|
||||
ReactChoreographer reactChoreographer,
|
||||
DevSupportManager devSupportManager) {
|
||||
mReactApplicationContext = reactContext;
|
||||
mReactChoreographer = reactChoreographer;
|
||||
mDevSupportManager = devSupportManager;
|
||||
|
||||
// We store timers sorted by finish time.
|
||||
mTimers =
|
||||
new PriorityQueue<Timer>(
|
||||
11, // Default capacity: for some reason they don't expose a (Comparator) constructor
|
||||
new Comparator<Timer>() {
|
||||
@Override
|
||||
public int compare(Timer lhs, Timer rhs) {
|
||||
long diff = lhs.mTargetTime - rhs.mTargetTime;
|
||||
if (diff == 0) {
|
||||
return 0;
|
||||
} else if (diff < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
mTimerIdsToTimers = new SparseArray<>();
|
||||
}
|
||||
|
||||
public void onHostPause() {
|
||||
isPaused.set(true);
|
||||
clearFrameCallback();
|
||||
maybeIdleCallback();
|
||||
}
|
||||
|
||||
public void onHostDestroy() {
|
||||
clearFrameCallback();
|
||||
maybeIdleCallback();
|
||||
}
|
||||
|
||||
public void onHostResume() {
|
||||
isPaused.set(false);
|
||||
// TODO(5195192) Investigate possible problems related to restarting all tasks at the same
|
||||
// moment
|
||||
setChoreographerCallback();
|
||||
maybeSetChoreographerIdleCallback();
|
||||
}
|
||||
|
||||
public void onHeadlessJsTaskStart(int taskId) {
|
||||
if (!isRunningTasks.getAndSet(true)) {
|
||||
setChoreographerCallback();
|
||||
maybeSetChoreographerIdleCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public void onHeadlessJsTaskFinish(int taskId) {
|
||||
HeadlessJsTaskContext headlessJsTaskContext =
|
||||
HeadlessJsTaskContext.getInstance(mReactApplicationContext);
|
||||
if (!headlessJsTaskContext.hasActiveTasks()) {
|
||||
isRunningTasks.set(false);
|
||||
clearFrameCallback();
|
||||
maybeIdleCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public void onInstanceDestroy() {
|
||||
clearFrameCallback();
|
||||
clearChoreographerIdleCallback();
|
||||
}
|
||||
|
||||
private void maybeSetChoreographerIdleCallback() {
|
||||
synchronized (mIdleCallbackGuard) {
|
||||
if (mSendIdleEvents) {
|
||||
setChoreographerIdleCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeIdleCallback() {
|
||||
if (isPaused.get() && !isRunningTasks.get()) {
|
||||
clearFrameCallback();
|
||||
}
|
||||
}
|
||||
|
||||
private void setChoreographerCallback() {
|
||||
if (!mFrameCallbackPosted) {
|
||||
mReactChoreographer.postFrameCallback(
|
||||
ReactChoreographer.CallbackType.TIMERS_EVENTS, mTimerFrameCallback);
|
||||
mFrameCallbackPosted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearFrameCallback() {
|
||||
HeadlessJsTaskContext headlessJsTaskContext =
|
||||
HeadlessJsTaskContext.getInstance(mReactApplicationContext);
|
||||
if (mFrameCallbackPosted && isPaused.get() && !headlessJsTaskContext.hasActiveTasks()) {
|
||||
mReactChoreographer.removeFrameCallback(
|
||||
ReactChoreographer.CallbackType.TIMERS_EVENTS, mTimerFrameCallback);
|
||||
mFrameCallbackPosted = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void setChoreographerIdleCallback() {
|
||||
if (!mFrameIdleCallbackPosted) {
|
||||
mReactChoreographer.postFrameCallback(
|
||||
ReactChoreographer.CallbackType.IDLE_EVENT, mIdleFrameCallback);
|
||||
mFrameIdleCallbackPosted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearChoreographerIdleCallback() {
|
||||
if (mFrameIdleCallbackPosted) {
|
||||
mReactChoreographer.removeFrameCallback(
|
||||
ReactChoreographer.CallbackType.IDLE_EVENT, mIdleFrameCallback);
|
||||
mFrameIdleCallbackPosted = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void createTimer(
|
||||
final int callbackID,
|
||||
final int duration,
|
||||
final double jsSchedulingTime,
|
||||
final boolean repeat) {
|
||||
long deviceTime = SystemClock.currentTimeMillis();
|
||||
long remoteTime = (long) jsSchedulingTime;
|
||||
|
||||
// If the times on the server and device have drifted throw an exception to warn the developer
|
||||
// that things might not work or results may not be accurate. This is required only for
|
||||
// developer builds.
|
||||
if (mDevSupportManager.getDevSupportEnabled()) {
|
||||
long driftTime = Math.abs(remoteTime - deviceTime);
|
||||
if (driftTime > 60000) {
|
||||
mReactApplicationContext
|
||||
.getJSModule(JSTimers.class)
|
||||
.emitTimeDriftWarning(
|
||||
"Debugger and device times have drifted by more than 60s. Please correct this by "
|
||||
+ "running adb shell \"date `date +%m%d%H%M%Y.%S`\" on your debugger machine.");
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust for the amount of time it took for native to receive the timer registration call
|
||||
long adjustedDuration = Math.max(0, remoteTime - deviceTime + duration);
|
||||
if (duration == 0 && !repeat) {
|
||||
WritableArray timerToCall = Arguments.createArray();
|
||||
timerToCall.pushInt(callbackID);
|
||||
mReactApplicationContext.getJSModule(JSTimers.class).callTimers(timerToCall);
|
||||
return;
|
||||
}
|
||||
|
||||
long initialTargetTime = SystemClock.nanoTime() / 1000000 + adjustedDuration;
|
||||
Timer timer = new Timer(callbackID, initialTargetTime, duration, repeat);
|
||||
synchronized (mTimerGuard) {
|
||||
mTimers.add(timer);
|
||||
mTimerIdsToTimers.put(callbackID, timer);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteTimer(int timerId) {
|
||||
synchronized (mTimerGuard) {
|
||||
Timer timer = mTimerIdsToTimers.get(timerId);
|
||||
if (timer == null) {
|
||||
return;
|
||||
}
|
||||
mTimerIdsToTimers.remove(timerId);
|
||||
mTimers.remove(timer);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSendIdleEvents(final boolean sendIdleEvents) {
|
||||
synchronized (mIdleCallbackGuard) {
|
||||
mSendIdleEvents = sendIdleEvents;
|
||||
}
|
||||
|
||||
UiThreadUtil.runOnUiThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mIdleCallbackGuard) {
|
||||
if (sendIdleEvents) {
|
||||
setChoreographerIdleCallback();
|
||||
} else {
|
||||
clearChoreographerIdleCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,23 +6,14 @@
|
||||
*/
|
||||
package com.facebook.react.modules.core;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.common.SystemClock;
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager;
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskContext;
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskEventListener;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import java.util.Comparator;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** Native module for JS timer execution. Timers fire on frame boundaries. */
|
||||
@ReactModule(name = TimingModule.NAME)
|
||||
@@ -31,167 +22,12 @@ public final class TimingModule extends ReactContextBaseJavaModule
|
||||
|
||||
public static final String NAME = "Timing";
|
||||
|
||||
// These timing constants should be kept in sync with the ones in `JSTimers.js`.
|
||||
// The minimum time in milliseconds left in the frame to call idle callbacks.
|
||||
private static final float IDLE_CALLBACK_FRAME_DEADLINE_MS = 1.f;
|
||||
// The total duration of a frame in milliseconds, this assumes that devices run at 60 fps.
|
||||
// TODO: Lower frame duration on devices that are too slow to run consistently
|
||||
// at 60 fps.
|
||||
private static final float FRAME_DURATION_MS = 1000.f / 60.f;
|
||||
|
||||
private final DevSupportManager mDevSupportManager;
|
||||
|
||||
private static class Timer {
|
||||
private final int mCallbackID;
|
||||
private final boolean mRepeat;
|
||||
private final int mInterval;
|
||||
private long mTargetTime;
|
||||
|
||||
private Timer(int callbackID, long initialTargetTime, int duration, boolean repeat) {
|
||||
mCallbackID = callbackID;
|
||||
mTargetTime = initialTargetTime;
|
||||
mInterval = duration;
|
||||
mRepeat = repeat;
|
||||
}
|
||||
}
|
||||
|
||||
private class TimerFrameCallback extends ChoreographerCompat.FrameCallback {
|
||||
|
||||
// Temporary map for constructing the individual arrays of timers to call
|
||||
private @Nullable WritableArray mTimersToCall = null;
|
||||
|
||||
/** Calls all timers that have expired since the last time this frame callback was called. */
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
if (isPaused.get() && !isRunningTasks.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long frameTimeMillis = frameTimeNanos / 1000000;
|
||||
synchronized (mTimerGuard) {
|
||||
while (!mTimers.isEmpty() && mTimers.peek().mTargetTime < frameTimeMillis) {
|
||||
Timer timer = mTimers.poll();
|
||||
if (mTimersToCall == null) {
|
||||
mTimersToCall = Arguments.createArray();
|
||||
}
|
||||
mTimersToCall.pushInt(timer.mCallbackID);
|
||||
if (timer.mRepeat) {
|
||||
timer.mTargetTime = frameTimeMillis + timer.mInterval;
|
||||
mTimers.add(timer);
|
||||
} else {
|
||||
mTimerIdsToTimers.remove(timer.mCallbackID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mTimersToCall != null) {
|
||||
getReactApplicationContext().getJSModule(JSTimers.class).callTimers(mTimersToCall);
|
||||
mTimersToCall = null;
|
||||
}
|
||||
|
||||
mReactChoreographer.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, this);
|
||||
}
|
||||
}
|
||||
|
||||
private class IdleFrameCallback extends ChoreographerCompat.FrameCallback {
|
||||
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
if (isPaused.get() && !isRunningTasks.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the JS thread is busy for multiple frames we cancel any other pending runnable.
|
||||
if (mCurrentIdleCallbackRunnable != null) {
|
||||
mCurrentIdleCallbackRunnable.cancel();
|
||||
}
|
||||
|
||||
mCurrentIdleCallbackRunnable = new IdleCallbackRunnable(frameTimeNanos);
|
||||
getReactApplicationContext().runOnJSQueueThread(mCurrentIdleCallbackRunnable);
|
||||
|
||||
mReactChoreographer.postFrameCallback(ReactChoreographer.CallbackType.IDLE_EVENT, this);
|
||||
}
|
||||
}
|
||||
|
||||
private class IdleCallbackRunnable implements Runnable {
|
||||
private volatile boolean mCancelled = false;
|
||||
private final long mFrameStartTime;
|
||||
|
||||
public IdleCallbackRunnable(long frameStartTime) {
|
||||
mFrameStartTime = frameStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
long frameTimeMillis = mFrameStartTime / 1000000;
|
||||
long timeSinceBoot = SystemClock.uptimeMillis();
|
||||
long frameTimeElapsed = timeSinceBoot - frameTimeMillis;
|
||||
long time = SystemClock.currentTimeMillis();
|
||||
long absoluteFrameStartTime = time - frameTimeElapsed;
|
||||
|
||||
if (FRAME_DURATION_MS - (float) frameTimeElapsed < IDLE_CALLBACK_FRAME_DEADLINE_MS) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean sendIdleEvents;
|
||||
synchronized (mIdleCallbackGuard) {
|
||||
sendIdleEvents = mSendIdleEvents;
|
||||
}
|
||||
|
||||
if (sendIdleEvents) {
|
||||
getReactApplicationContext()
|
||||
.getJSModule(JSTimers.class)
|
||||
.callIdleCallbacks(absoluteFrameStartTime);
|
||||
}
|
||||
|
||||
mCurrentIdleCallbackRunnable = null;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
mCancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private final Object mTimerGuard = new Object();
|
||||
private final Object mIdleCallbackGuard = new Object();
|
||||
private final PriorityQueue<Timer> mTimers;
|
||||
private final SparseArray<Timer> mTimerIdsToTimers;
|
||||
private final AtomicBoolean isPaused = new AtomicBoolean(true);
|
||||
private final AtomicBoolean isRunningTasks = new AtomicBoolean(false);
|
||||
private final TimerFrameCallback mTimerFrameCallback = new TimerFrameCallback();
|
||||
private final IdleFrameCallback mIdleFrameCallback = new IdleFrameCallback();
|
||||
private final ReactChoreographer mReactChoreographer;
|
||||
private @Nullable IdleCallbackRunnable mCurrentIdleCallbackRunnable;
|
||||
private boolean mFrameCallbackPosted = false;
|
||||
private boolean mFrameIdleCallbackPosted = false;
|
||||
private boolean mSendIdleEvents = false;
|
||||
private final JavaTimerManager mJavaTimerManager;
|
||||
|
||||
public TimingModule(ReactApplicationContext reactContext, DevSupportManager devSupportManager) {
|
||||
super(reactContext);
|
||||
mDevSupportManager = devSupportManager;
|
||||
// We store timers sorted by finish time.
|
||||
mTimers =
|
||||
new PriorityQueue<Timer>(
|
||||
11, // Default capacity: for some reason they don't expose a (Comparator) constructor
|
||||
new Comparator<Timer>() {
|
||||
@Override
|
||||
public int compare(Timer lhs, Timer rhs) {
|
||||
long diff = lhs.mTargetTime - rhs.mTargetTime;
|
||||
if (diff == 0) {
|
||||
return 0;
|
||||
} else if (diff < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
mTimerIdsToTimers = new SparseArray<>();
|
||||
mReactChoreographer = ReactChoreographer.getInstance();
|
||||
mJavaTimerManager =
|
||||
new JavaTimerManager(reactContext, ReactChoreographer.getInstance(), devSupportManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -202,104 +38,6 @@ public final class TimingModule extends ReactContextBaseJavaModule
|
||||
headlessJsTaskContext.addTaskEventListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
isPaused.set(true);
|
||||
clearFrameCallback();
|
||||
maybeIdleCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
clearFrameCallback();
|
||||
maybeIdleCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
isPaused.set(false);
|
||||
// TODO(5195192) Investigate possible problems related to restarting all tasks at the same
|
||||
// moment
|
||||
setChoreographerCallback();
|
||||
maybeSetChoreographerIdleCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeadlessJsTaskStart(int taskId) {
|
||||
if (!isRunningTasks.getAndSet(true)) {
|
||||
setChoreographerCallback();
|
||||
maybeSetChoreographerIdleCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeadlessJsTaskFinish(int taskId) {
|
||||
HeadlessJsTaskContext headlessJsTaskContext =
|
||||
HeadlessJsTaskContext.getInstance(getReactApplicationContext());
|
||||
if (!headlessJsTaskContext.hasActiveTasks()) {
|
||||
isRunningTasks.set(false);
|
||||
clearFrameCallback();
|
||||
maybeIdleCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCatalystInstanceDestroy() {
|
||||
clearFrameCallback();
|
||||
clearChoreographerIdleCallback();
|
||||
HeadlessJsTaskContext headlessJsTaskContext =
|
||||
HeadlessJsTaskContext.getInstance(getReactApplicationContext());
|
||||
headlessJsTaskContext.removeTaskEventListener(this);
|
||||
}
|
||||
|
||||
private void maybeSetChoreographerIdleCallback() {
|
||||
synchronized (mIdleCallbackGuard) {
|
||||
if (mSendIdleEvents) {
|
||||
setChoreographerIdleCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeIdleCallback() {
|
||||
if (isPaused.get() && !isRunningTasks.get()) {
|
||||
clearFrameCallback();
|
||||
}
|
||||
}
|
||||
|
||||
private void setChoreographerCallback() {
|
||||
if (!mFrameCallbackPosted) {
|
||||
mReactChoreographer.postFrameCallback(
|
||||
ReactChoreographer.CallbackType.TIMERS_EVENTS, mTimerFrameCallback);
|
||||
mFrameCallbackPosted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearFrameCallback() {
|
||||
HeadlessJsTaskContext headlessJsTaskContext =
|
||||
HeadlessJsTaskContext.getInstance(getReactApplicationContext());
|
||||
if (mFrameCallbackPosted && isPaused.get() && !headlessJsTaskContext.hasActiveTasks()) {
|
||||
mReactChoreographer.removeFrameCallback(
|
||||
ReactChoreographer.CallbackType.TIMERS_EVENTS, mTimerFrameCallback);
|
||||
mFrameCallbackPosted = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void setChoreographerIdleCallback() {
|
||||
if (!mFrameIdleCallbackPosted) {
|
||||
mReactChoreographer.postFrameCallback(
|
||||
ReactChoreographer.CallbackType.IDLE_EVENT, mIdleFrameCallback);
|
||||
mFrameIdleCallbackPosted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearChoreographerIdleCallback() {
|
||||
if (mFrameIdleCallbackPosted) {
|
||||
mReactChoreographer.removeFrameCallback(
|
||||
ReactChoreographer.CallbackType.IDLE_EVENT, mIdleFrameCallback);
|
||||
mFrameIdleCallbackPosted = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
@@ -311,70 +49,49 @@ public final class TimingModule extends ReactContextBaseJavaModule
|
||||
final int duration,
|
||||
final double jsSchedulingTime,
|
||||
final boolean repeat) {
|
||||
long deviceTime = SystemClock.currentTimeMillis();
|
||||
long remoteTime = (long) jsSchedulingTime;
|
||||
|
||||
// If the times on the server and device have drifted throw an exception to warn the developer
|
||||
// that things might not work or results may not be accurate. This is required only for
|
||||
// developer builds.
|
||||
if (mDevSupportManager.getDevSupportEnabled()) {
|
||||
long driftTime = Math.abs(remoteTime - deviceTime);
|
||||
if (driftTime > 60000) {
|
||||
getReactApplicationContext()
|
||||
.getJSModule(JSTimers.class)
|
||||
.emitTimeDriftWarning(
|
||||
"Debugger and device times have drifted by more than 60s. Please correct this by "
|
||||
+ "running adb shell \"date `date +%m%d%H%M%Y.%S`\" on your debugger machine.");
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust for the amount of time it took for native to receive the timer registration call
|
||||
long adjustedDuration = Math.max(0, remoteTime - deviceTime + duration);
|
||||
if (duration == 0 && !repeat) {
|
||||
WritableArray timerToCall = Arguments.createArray();
|
||||
timerToCall.pushInt(callbackID);
|
||||
getReactApplicationContext().getJSModule(JSTimers.class).callTimers(timerToCall);
|
||||
return;
|
||||
}
|
||||
|
||||
long initialTargetTime = SystemClock.nanoTime() / 1000000 + adjustedDuration;
|
||||
Timer timer = new Timer(callbackID, initialTargetTime, duration, repeat);
|
||||
synchronized (mTimerGuard) {
|
||||
mTimers.add(timer);
|
||||
mTimerIdsToTimers.put(callbackID, timer);
|
||||
}
|
||||
mJavaTimerManager.createTimer(callbackID, duration, jsSchedulingTime, repeat);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void deleteTimer(int timerId) {
|
||||
synchronized (mTimerGuard) {
|
||||
Timer timer = mTimerIdsToTimers.get(timerId);
|
||||
if (timer == null) {
|
||||
return;
|
||||
}
|
||||
mTimerIdsToTimers.remove(timerId);
|
||||
mTimers.remove(timer);
|
||||
}
|
||||
mJavaTimerManager.deleteTimer(timerId);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setSendIdleEvents(final boolean sendIdleEvents) {
|
||||
synchronized (mIdleCallbackGuard) {
|
||||
mSendIdleEvents = sendIdleEvents;
|
||||
}
|
||||
mJavaTimerManager.setSendIdleEvents(sendIdleEvents);
|
||||
}
|
||||
|
||||
UiThreadUtil.runOnUiThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mIdleCallbackGuard) {
|
||||
if (sendIdleEvents) {
|
||||
setChoreographerIdleCallback();
|
||||
} else {
|
||||
clearChoreographerIdleCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
mJavaTimerManager.onHostResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
mJavaTimerManager.onHostPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
mJavaTimerManager.onHostDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeadlessJsTaskStart(int taskId) {
|
||||
mJavaTimerManager.onHeadlessJsTaskStart(taskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeadlessJsTaskFinish(int taskId) {
|
||||
mJavaTimerManager.onHeadlessJsTaskFinish(taskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCatalystInstanceDestroy() {
|
||||
HeadlessJsTaskContext headlessJsTaskContext =
|
||||
HeadlessJsTaskContext.getInstance(getReactApplicationContext());
|
||||
headlessJsTaskContext.removeTaskEventListener(this);
|
||||
mJavaTimerManager.onInstanceDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user