mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-07 17:37:34 +08:00
Summary: Initializing natives modules on the UI thread blocks the JS thread if the UI thread is busy. Reviewed By: yungsters Differential Revision: D4611211 fbshipit-source-id: cd4fb9cb5e52a478b6692b784cfd9e3bf34c0d34
365 lines
14 KiB
Java
365 lines
14 KiB
Java
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
package com.facebook.react.animated;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import com.facebook.infer.annotation.Assertions;
|
|
import com.facebook.react.bridge.Arguments;
|
|
import com.facebook.react.bridge.Callback;
|
|
import com.facebook.react.bridge.LifecycleEventListener;
|
|
import com.facebook.react.bridge.OnBatchCompleteListener;
|
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
import com.facebook.react.bridge.ReactMethod;
|
|
import com.facebook.react.bridge.ReadableMap;
|
|
import com.facebook.react.bridge.WritableMap;
|
|
import com.facebook.react.module.annotations.ReactModule;
|
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
import com.facebook.react.modules.core.ReactChoreographer;
|
|
import com.facebook.react.uimanager.GuardedFrameCallback;
|
|
import com.facebook.react.uimanager.UIManagerModule;
|
|
|
|
/**
|
|
* Module that exposes interface for creating and managing animated nodes on the "native" side.
|
|
*
|
|
* Animated.js library is based on a concept of a graph where nodes are values or transform
|
|
* operations (such as interpolation, addition, etc) and connection are used to describe how change
|
|
* of the value in one node can affect other nodes.
|
|
*
|
|
* Few examples of the nodes that can be created on the JS side:
|
|
* - Animated.Value is a simplest type of node with a numeric value which can be driven by an
|
|
* animation engine (spring, decay, etc) or by calling setValue on it directly from JS
|
|
* - Animated.add is a type of node that may have two or more input nodes. It outputs the sum of
|
|
* all the input node values
|
|
* - interpolate - is actually a method you can call on any node and it creates a new node that
|
|
* takes the parent node as an input and outputs its interpolated value (e.g. if you have value
|
|
* that can animate from 0 to 1 you can create interpolated node and set output range to be 0 to
|
|
* 100 and when the input node changes the output of interpolated node will multiply the values
|
|
* by 100)
|
|
*
|
|
* You can mix and chain nodes however you like and this way create nodes graph with connections
|
|
* between them.
|
|
*
|
|
* To map animated node values to view properties there is a special type of a node: AnimatedProps.
|
|
* It is created by AnimatedImplementation whenever you render Animated.View and stores a mapping
|
|
* from the view properties to the corresponding animated values (so it's actually also a node with
|
|
* connections to the value nodes).
|
|
*
|
|
* Last "special" elements of the the graph are "animation drivers". Those are objects (represented
|
|
* as a graph nodes too) that based on some criteria updates attached values every frame (we have
|
|
* few types of those, e.g., spring, timing, decay). Animation objects can be "started" and
|
|
* "stopped". Those are like "pulse generators" for the rest of the nodes graph. Those pulses then
|
|
* propagate along the graph to the children nodes up to the special node type: AnimatedProps which
|
|
* then can be used to calculate property update map for a view.
|
|
*
|
|
* This class acts as a proxy between the "native" API that can be called from JS and the main class
|
|
* that coordinates all the action: {@link NativeAnimatedNodesManager}. Since all the methods from
|
|
* {@link NativeAnimatedNodesManager} need to be called from the UI thread, we we create a queue of
|
|
* animated graph operations that is then enqueued to be executed in the UI Thread at the end of the
|
|
* batch of JS->native calls (similarily to how it's handled in {@link UIManagerModule}). This
|
|
* isolates us from the problems that may be caused by concurrent updates of animated graph while UI
|
|
* thread is "executing" the animation loop.
|
|
*/
|
|
@ReactModule(name = "NativeAnimatedModule")
|
|
public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
|
|
OnBatchCompleteListener, LifecycleEventListener {
|
|
|
|
private interface UIThreadOperation {
|
|
void execute(NativeAnimatedNodesManager animatedNodesManager);
|
|
}
|
|
|
|
private final Object mOperationsCopyLock = new Object();
|
|
private @Nullable GuardedFrameCallback mAnimatedFrameCallback;
|
|
private @Nullable ReactChoreographer mReactChoreographer;
|
|
private ArrayList<UIThreadOperation> mOperations = new ArrayList<>();
|
|
private volatile @Nullable ArrayList<UIThreadOperation> mReadyOperations = null;
|
|
|
|
public NativeAnimatedModule(ReactApplicationContext reactContext) {
|
|
super(reactContext);
|
|
}
|
|
|
|
@Override
|
|
public void initialize() {
|
|
getReactApplicationContext().addLifecycleEventListener(this);
|
|
}
|
|
|
|
@Override
|
|
public void onHostResume() {
|
|
if (mReactChoreographer == null) {
|
|
// Safe to acquire choreographer here, as onHostResume() is invoked from UI thread.
|
|
mReactChoreographer = ReactChoreographer.getInstance();
|
|
|
|
ReactApplicationContext reactCtx = getReactApplicationContext();
|
|
UIManagerModule uiManager = reactCtx.getNativeModule(UIManagerModule.class);
|
|
|
|
final NativeAnimatedNodesManager nodesManager = new NativeAnimatedNodesManager(uiManager);
|
|
mAnimatedFrameCallback = new GuardedFrameCallback(reactCtx) {
|
|
@Override
|
|
protected void doFrameGuarded(final long frameTimeNanos) {
|
|
|
|
ArrayList<UIThreadOperation> operations;
|
|
synchronized (mOperationsCopyLock) {
|
|
operations = mReadyOperations;
|
|
mReadyOperations = null;
|
|
}
|
|
|
|
if (operations != null) {
|
|
for (int i = 0, size = operations.size(); i < size; i++) {
|
|
operations.get(i).execute(nodesManager);
|
|
}
|
|
}
|
|
|
|
if (nodesManager.hasActiveAnimations()) {
|
|
nodesManager.runUpdates(frameTimeNanos);
|
|
}
|
|
|
|
// TODO: Would be great to avoid adding this callback in case there are no active animations
|
|
// and no outstanding tasks on the operations queue. Apparently frame callbacks can only
|
|
// be posted from the UI thread and therefore we cannot schedule them directly from
|
|
// @ReactMethod methods
|
|
Assertions.assertNotNull(mReactChoreographer).postFrameCallback(
|
|
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
|
|
mAnimatedFrameCallback);
|
|
}
|
|
};
|
|
}
|
|
enqueueFrameCallback();
|
|
}
|
|
|
|
@Override
|
|
public void onBatchComplete() {
|
|
// Note: The order of executing onBatchComplete handler (especially in terms of onBatchComplete
|
|
// from the UIManagerModule) doesn't matter as we only enqueue operations for the UI thread to
|
|
// be executed from here. Thanks to ReactChoreographer all the operations from here are going
|
|
// to be executed *after* all the operations enqueued by UIManager as the callback type that we
|
|
// use for ReactChoreographer (CallbackType.NATIVE_ANIMATED_MODULE) is run after callbacks that
|
|
// UIManager uses.
|
|
ArrayList<UIThreadOperation> operations = mOperations.isEmpty() ? null : mOperations;
|
|
if (operations != null) {
|
|
mOperations = new ArrayList<>();
|
|
synchronized (mOperationsCopyLock) {
|
|
if (mReadyOperations == null) {
|
|
mReadyOperations = operations;
|
|
} else {
|
|
mReadyOperations.addAll(operations);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onHostPause() {
|
|
clearFrameCallback();
|
|
}
|
|
|
|
@Override
|
|
public void onHostDestroy() {
|
|
// do nothing
|
|
}
|
|
|
|
@Override
|
|
public String getName() {
|
|
return "NativeAnimatedModule";
|
|
}
|
|
|
|
private void clearFrameCallback() {
|
|
Assertions.assertNotNull(mReactChoreographer).removeFrameCallback(
|
|
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
|
|
mAnimatedFrameCallback);
|
|
}
|
|
|
|
private void enqueueFrameCallback() {
|
|
Assertions.assertNotNull(mReactChoreographer).postFrameCallback(
|
|
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
|
|
mAnimatedFrameCallback);
|
|
}
|
|
|
|
@ReactMethod
|
|
public void createAnimatedNode(final int tag, final ReadableMap config) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.createAnimatedNode(tag, config);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void startListeningToAnimatedNodeValue(final int tag) {
|
|
final AnimatedNodeValueListener listener = new AnimatedNodeValueListener() {
|
|
public void onValueUpdate(double value) {
|
|
WritableMap onAnimatedValueData = Arguments.createMap();
|
|
onAnimatedValueData.putInt("tag", tag);
|
|
onAnimatedValueData.putDouble("value", value);
|
|
getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
.emit("onAnimatedValueUpdate", onAnimatedValueData);
|
|
}
|
|
};
|
|
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.startListeningToAnimatedNodeValue(tag, listener);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void stopListeningToAnimatedNodeValue(final int tag) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.stopListeningToAnimatedNodeValue(tag);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void dropAnimatedNode(final int tag) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.dropAnimatedNode(tag);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void setAnimatedNodeValue(final int tag, final double value) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.setAnimatedNodeValue(tag, value);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void setAnimatedNodeOffset(final int tag, final double value) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.setAnimatedNodeOffset(tag, value);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void flattenAnimatedNodeOffset(final int tag) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.flattenAnimatedNodeOffset(tag);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void extractAnimatedNodeOffset(final int tag) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.extractAnimatedNodeOffset(tag);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void startAnimatingNode(
|
|
final int animationId,
|
|
final int animatedNodeTag,
|
|
final ReadableMap animationConfig,
|
|
final Callback endCallback) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.startAnimatingNode(
|
|
animationId,
|
|
animatedNodeTag,
|
|
animationConfig,
|
|
endCallback);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void stopAnimation(final int animationId) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.stopAnimation(animationId);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void connectAnimatedNodes(final int parentNodeTag, final int childNodeTag) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.connectAnimatedNodes(parentNodeTag, childNodeTag);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void disconnectAnimatedNodes(final int parentNodeTag, final int childNodeTag) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.disconnectAnimatedNodes(parentNodeTag, childNodeTag);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void connectAnimatedNodeToView(final int animatedNodeTag, final int viewTag) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.connectAnimatedNodeToView(animatedNodeTag, viewTag);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void disconnectAnimatedNodeFromView(final int animatedNodeTag, final int viewTag) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.disconnectAnimatedNodeFromView(animatedNodeTag, viewTag);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void addAnimatedEventToView(final int viewTag, final String eventName, final ReadableMap eventMapping) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.addAnimatedEventToView(viewTag, eventName, eventMapping);
|
|
}
|
|
});
|
|
}
|
|
|
|
@ReactMethod
|
|
public void removeAnimatedEventFromView(final int viewTag, final String eventName) {
|
|
mOperations.add(new UIThreadOperation() {
|
|
@Override
|
|
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
|
|
animatedNodesManager.removeAnimatedEventFromView(viewTag, eventName);
|
|
}
|
|
});
|
|
}
|
|
}
|