mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
NativeAnimatedDriver: synchronize animation lifecycle closer to Fabric or Paper lifecycle
Summary: Switch between "Fabric" and "Non-Fabric" modes based on which types of native Views are being attached to animations. Don't allow non-Fabric to drive Fabric animations and vice-versa. Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D21985411 fbshipit-source-id: fb9bef1e38375b384430b4e0275e7b6d62eda7a4
This commit is contained in:
committed by
Facebook GitHub Bot
parent
3346ac7f96
commit
472cf3f4ad
+80
-29
@@ -10,16 +10,19 @@ package com.facebook.react.animated;
|
||||
import android.util.SparseArray;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.JSApplicationCausedNativeException;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.UIManager;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.IllegalViewOperationException;
|
||||
import com.facebook.react.uimanager.UIManagerHelper;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
@@ -48,6 +51,9 @@ import java.util.Queue;
|
||||
*/
|
||||
/*package*/ class NativeAnimatedNodesManager implements EventDispatcherListener {
|
||||
|
||||
private static final String TAG = "NativeAnimatedNodesManager";
|
||||
private static final int MAX_INCONSISTENT_FRAMES = 64;
|
||||
|
||||
private final SparseArray<AnimatedNode> mAnimatedNodes = new SparseArray<>();
|
||||
private final SparseArray<AnimationDriver> mActiveAnimations = new SparseArray<>();
|
||||
private final SparseArray<AnimatedNode> mUpdatedNodes = new SparseArray<>();
|
||||
@@ -55,14 +61,19 @@ import java.util.Queue;
|
||||
// there will be only one driver per mapping so all code code should be optimized around that.
|
||||
private final Map<String, List<EventAnimationDriver>> mEventDrivers = new HashMap<>();
|
||||
private final UIManagerModule.CustomEventNamesResolver mCustomEventNamesResolver;
|
||||
private final UIManager mUIManager;
|
||||
private final ReactApplicationContext mReactApplicationContext;
|
||||
private int mAnimatedGraphBFSColor = 0;
|
||||
private int mNumInconsistentFrames = 0;
|
||||
// Used to avoid allocating a new array on every frame in `runUpdates` and `onEventDispatch`.
|
||||
private final List<AnimatedNode> mRunUpdateNodeList = new LinkedList<>();
|
||||
|
||||
public NativeAnimatedNodesManager(UIManagerModule uiManager) {
|
||||
mUIManager = uiManager;
|
||||
mUIManager.<EventDispatcher>getEventDispatcher().addListener(this);
|
||||
public NativeAnimatedNodesManager(ReactApplicationContext reactApplicationContext) {
|
||||
mReactApplicationContext = reactApplicationContext;
|
||||
|
||||
UIManagerModule uiManager =
|
||||
Assertions.assertNotNull(reactApplicationContext.getNativeModule(UIManagerModule.class));
|
||||
|
||||
uiManager.<EventDispatcher>getEventDispatcher().addListener(this);
|
||||
// TODO T64216139 Remove dependency of UIManagerModule when the Constants are not in Native
|
||||
// anymore
|
||||
mCustomEventNamesResolver = uiManager.getDirectEventNamesResolver();
|
||||
@@ -89,7 +100,7 @@ import java.util.Queue;
|
||||
} else if ("value".equals(type)) {
|
||||
node = new ValueAnimatedNode(config);
|
||||
} else if ("props".equals(type)) {
|
||||
node = new PropsAnimatedNode(config, this, mUIManager);
|
||||
node = new PropsAnimatedNode(config, this);
|
||||
} else if ("interpolation".equals(type)) {
|
||||
node = new InterpolationAnimatedNode(config);
|
||||
} else if ("addition".equals(type)) {
|
||||
@@ -301,8 +312,21 @@ import java.util.Queue;
|
||||
+ "of type "
|
||||
+ PropsAnimatedNode.class.getName());
|
||||
}
|
||||
if (mReactApplicationContext == null) {
|
||||
throw new IllegalStateException(
|
||||
"Animated node could not be connected, no ReactApplicationContext: " + viewTag);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
UIManager uiManager =
|
||||
UIManagerHelper.getUIManagerForReactTag(mReactApplicationContext, viewTag);
|
||||
if (uiManager == null) {
|
||||
throw new IllegalStateException(
|
||||
"Animated node could not be connected to UIManager: " + viewTag);
|
||||
}
|
||||
|
||||
PropsAnimatedNode propsAnimatedNode = (PropsAnimatedNode) node;
|
||||
propsAnimatedNode.connectToView(viewTag);
|
||||
propsAnimatedNode.connectToView(viewTag, uiManager);
|
||||
mUpdatedNodes.put(animatedNodeTag, node);
|
||||
}
|
||||
|
||||
@@ -543,26 +567,43 @@ import java.util.Queue;
|
||||
}
|
||||
|
||||
// Run main "update" loop
|
||||
boolean errorsCaught = false;
|
||||
while (!nodesQueue.isEmpty()) {
|
||||
AnimatedNode nextNode = nodesQueue.poll();
|
||||
nextNode.update();
|
||||
if (nextNode instanceof PropsAnimatedNode) {
|
||||
// Send property updates to native view manager
|
||||
try {
|
||||
try {
|
||||
nextNode.update();
|
||||
if (nextNode instanceof PropsAnimatedNode) {
|
||||
// Send property updates to native view manager
|
||||
((PropsAnimatedNode) nextNode).updateView();
|
||||
} catch (IllegalViewOperationException e) {
|
||||
// An exception is thrown if the view hasn't been created yet. This can happen because
|
||||
// views are
|
||||
// created in batches. If this particular view didn't make it into a batch yet, the view
|
||||
// won't
|
||||
// exist and an exception will be thrown when attempting to start an animation on it.
|
||||
//
|
||||
// Eat the exception rather than crashing. The impact is that we may drop one or more
|
||||
// frames of the
|
||||
// animation.
|
||||
}
|
||||
} catch (IllegalViewOperationException e) {
|
||||
// An exception is thrown if the view hasn't been created yet. This can happen because
|
||||
// views are
|
||||
// created in batches. If this particular view didn't make it into a batch yet, the view
|
||||
// won't
|
||||
// exist and an exception will be thrown when attempting to start an animation on it.
|
||||
//
|
||||
// Eat the exception rather than crashing. The impact is that we may drop one or more
|
||||
// frames of the
|
||||
// animation.
|
||||
FLog.e(TAG, "Native animation workaround, frame lost as result of race condition", e);
|
||||
} catch (JSApplicationCausedNativeException e) {
|
||||
// In Fabric there can be race conditions between the JS thread setting up or tearing down
|
||||
// animated nodes, and Fabric executing them on the UI thread, leading to temporary
|
||||
// inconsistent
|
||||
// states. We require that the inconsistency last for N frames before throwing these
|
||||
// exceptions.
|
||||
if (!errorsCaught) {
|
||||
errorsCaught = true;
|
||||
mNumInconsistentFrames++;
|
||||
}
|
||||
if (mNumInconsistentFrames > MAX_INCONSISTENT_FRAMES) {
|
||||
throw new IllegalStateException(e);
|
||||
} else {
|
||||
FLog.e(
|
||||
ReactConstants.TAG,
|
||||
"Native animation workaround, frame lost as result of race condition",
|
||||
TAG,
|
||||
"Swallowing exception due to potential race between JS and UI threads: inconsistent frame counter: "
|
||||
+ mNumInconsistentFrames,
|
||||
e);
|
||||
}
|
||||
}
|
||||
@@ -586,13 +627,23 @@ import java.util.Queue;
|
||||
// Verify that we've visited *all* active nodes. Throw otherwise as this would mean there is a
|
||||
// cycle in animated node graph. We also take advantage of the fact that all active nodes are
|
||||
// visited in the step above so that all the nodes properties `mActiveIncomingNodes` are set to
|
||||
// zero
|
||||
// zero.
|
||||
// In Fabric there can be race conditions between the JS thread setting up or tearing down
|
||||
// animated nodes, and Fabric executing them on the UI thread, leading to temporary inconsistent
|
||||
// states. We require that the inconsistency last for 64 frames before throwing this exception.
|
||||
if (activeNodesCount != updatedNodesCount) {
|
||||
throw new IllegalStateException(
|
||||
"Looks like animated nodes graph has cycles, there are "
|
||||
+ activeNodesCount
|
||||
+ " but toposort visited only "
|
||||
+ updatedNodesCount);
|
||||
if (!errorsCaught) {
|
||||
mNumInconsistentFrames++;
|
||||
}
|
||||
if (mNumInconsistentFrames > MAX_INCONSISTENT_FRAMES) {
|
||||
throw new IllegalStateException(
|
||||
"Looks like animated nodes graph has cycles, there are "
|
||||
+ activeNodesCount
|
||||
+ " but toposort visited only "
|
||||
+ updatedNodesCount);
|
||||
}
|
||||
} else if (!errorsCaught) {
|
||||
mNumInconsistentFrames = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user