mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-01-12 22:50:10 +08:00
Summary: Before D14297477, the pre-allocation of views was ONLY necessary when react was running in the JS Thread, this is because the batch of mount items used to contain mount items for creation of views. After D14297477, views are only created during pre-allocation, that means that pre-allocation of views need to be trated the same way independently the thread where it is running. Reviewed By: shergin Differential Revision: D14714933 fbshipit-source-id: 7bd19cd33b624a5b0daaafabb476bb06707eea17
489 lines
17 KiB
Java
489 lines
17 KiB
Java
/**
|
|
* Copyright (c) 2014-present, Facebook, Inc.
|
|
*
|
|
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
|
* directory of this source tree.
|
|
*/
|
|
package com.facebook.react.fabric;
|
|
|
|
import static com.facebook.infer.annotation.ThreadConfined.UI;
|
|
import static com.facebook.react.fabric.mounting.LayoutMetricsConversions.getMaxSize;
|
|
import static com.facebook.react.fabric.mounting.LayoutMetricsConversions.getMinSize;
|
|
import static com.facebook.react.fabric.mounting.LayoutMetricsConversions.getYogaMeasureMode;
|
|
import static com.facebook.react.fabric.mounting.LayoutMetricsConversions.getYogaSize;
|
|
import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.os.SystemClock;
|
|
import androidx.annotation.GuardedBy;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.UiThread;
|
|
import android.view.View;
|
|
import com.facebook.common.logging.FLog;
|
|
import com.facebook.debug.holder.PrinterHolder;
|
|
import com.facebook.debug.tags.ReactDebugOverlayTags;
|
|
import com.facebook.infer.annotation.ThreadConfined;
|
|
import com.facebook.proguard.annotations.DoNotStrip;
|
|
import com.facebook.react.bridge.LifecycleEventListener;
|
|
import com.facebook.react.bridge.NativeMap;
|
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
import com.facebook.react.bridge.ReactContext;
|
|
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.fabric.jsi.Binding;
|
|
import com.facebook.react.fabric.jsi.EventBeatManager;
|
|
import com.facebook.react.fabric.jsi.EventEmitterWrapper;
|
|
import com.facebook.react.fabric.jsi.FabricSoLoader;
|
|
import com.facebook.react.fabric.mounting.MountingManager;
|
|
import com.facebook.react.fabric.mounting.mountitems.BatchMountItem;
|
|
import com.facebook.react.fabric.mounting.mountitems.DeleteMountItem;
|
|
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem;
|
|
import com.facebook.react.fabric.mounting.mountitems.InsertMountItem;
|
|
import com.facebook.react.fabric.mounting.mountitems.MountItem;
|
|
import com.facebook.react.fabric.mounting.mountitems.PreAllocateViewMountItem;
|
|
import com.facebook.react.fabric.mounting.mountitems.RemoveMountItem;
|
|
import com.facebook.react.fabric.mounting.mountitems.UpdateEventEmitterMountItem;
|
|
import com.facebook.react.fabric.mounting.mountitems.UpdateLayoutMountItem;
|
|
import com.facebook.react.fabric.mounting.mountitems.UpdateLocalDataMountItem;
|
|
import com.facebook.react.fabric.mounting.mountitems.UpdatePropsMountItem;
|
|
import com.facebook.react.modules.core.ReactChoreographer;
|
|
import com.facebook.react.uimanager.ReactRootViewTagGenerator;
|
|
import com.facebook.react.uimanager.ThemedReactContext;
|
|
import com.facebook.react.uimanager.ViewManagerPropertyUpdater;
|
|
import com.facebook.react.uimanager.ViewManagerRegistry;
|
|
import com.facebook.react.uimanager.events.EventDispatcher;
|
|
import com.facebook.systrace.Systrace;
|
|
import java.util.ArrayDeque;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
@SuppressLint("MissingNativeLoadLibrary")
|
|
public class FabricUIManager implements UIManager, LifecycleEventListener {
|
|
|
|
public static final String TAG = FabricUIManager.class.getSimpleName();
|
|
public static final boolean DEBUG =
|
|
PrinterHolder.getPrinter().shouldDisplayLogMessage(ReactDebugOverlayTags.FABRIC_UI_MANAGER);
|
|
|
|
private static final Map<String, String> sComponentNames = new HashMap<>();
|
|
private static final int FRAME_TIME_MS = 16;
|
|
private static final int MAX_TIME_IN_FRAME_FOR_NON_BATCHED_OPERATIONS_MS = 8;
|
|
private static final int PRE_MOUNT_ITEMS_INITIAL_SIZE_ARRAY = 250;
|
|
|
|
static {
|
|
FabricSoLoader.staticInit();
|
|
|
|
// TODO T31905686: unify component names between JS - Android - iOS - C++
|
|
sComponentNames.put("View", "RCTView");
|
|
sComponentNames.put("Image", "RCTImageView");
|
|
sComponentNames.put("ScrollView", "RCTScrollView");
|
|
sComponentNames.put("Slider", "RCTSlider");
|
|
sComponentNames.put("Paragraph", "RCTText");
|
|
sComponentNames.put("Text", "RCText");
|
|
sComponentNames.put("RawText", "RCTRawText");
|
|
sComponentNames.put("ActivityIndicatorView", "AndroidProgressBar");
|
|
sComponentNames.put("ShimmeringView", "RKShimmeringView");
|
|
sComponentNames.put("TemplateView", "RCTTemplateView");
|
|
}
|
|
|
|
private Binding mBinding;
|
|
private final ReactApplicationContext mReactApplicationContext;
|
|
private final MountingManager mMountingManager;
|
|
private final EventDispatcher mEventDispatcher;
|
|
private final ConcurrentHashMap<Integer, ThemedReactContext> mReactContextForRootTag =
|
|
new ConcurrentHashMap<>();
|
|
private final EventBeatManager mEventBeatManager;
|
|
private final Object mMountItemsLock = new Object();
|
|
private final Object mPreMountItemsLock = new Object();
|
|
|
|
@GuardedBy("mMountItemsLock")
|
|
private List<MountItem> mMountItems = new ArrayList<>();
|
|
|
|
@GuardedBy("mPreMountItemsLock")
|
|
private ArrayDeque<MountItem> mPreMountItems =
|
|
new ArrayDeque<>(PRE_MOUNT_ITEMS_INITIAL_SIZE_ARRAY);
|
|
|
|
@ThreadConfined(UI)
|
|
private final DispatchUIFrameCallback mDispatchUIFrameCallback;
|
|
|
|
@ThreadConfined(UI)
|
|
private boolean mIsMountingEnabled = true;
|
|
|
|
private long mRunStartTime = 0l;
|
|
private long mBatchedExecutionTime = 0l;
|
|
private long mDispatchViewUpdatesTime = 0l;
|
|
private long mCommitStartTime = 0l;
|
|
private long mLayoutTime = 0l;
|
|
private long mFinishTransactionTime = 0l;
|
|
private long mFinishTransactionCPPTime = 0l;
|
|
|
|
public FabricUIManager(
|
|
ReactApplicationContext reactContext,
|
|
ViewManagerRegistry viewManagerRegistry,
|
|
EventDispatcher eventDispatcher,
|
|
EventBeatManager eventBeatManager) {
|
|
mDispatchUIFrameCallback = new DispatchUIFrameCallback(reactContext);
|
|
mReactApplicationContext = reactContext;
|
|
mMountingManager = new MountingManager(viewManagerRegistry);
|
|
mEventDispatcher = eventDispatcher;
|
|
mEventBeatManager = eventBeatManager;
|
|
mReactApplicationContext.addLifecycleEventListener(this);
|
|
}
|
|
|
|
@Override
|
|
public <T extends View> int addRootView(
|
|
final T rootView, final WritableMap initialProps, final @Nullable String initialUITemplate) {
|
|
final int rootTag = ReactRootViewTagGenerator.getNextRootViewTag();
|
|
ThemedReactContext reactContext =
|
|
new ThemedReactContext(mReactApplicationContext, rootView.getContext());
|
|
mMountingManager.addRootView(rootTag, rootView);
|
|
mReactContextForRootTag.put(rootTag, reactContext);
|
|
mBinding.startSurface(rootTag, (NativeMap) initialProps);
|
|
if (initialUITemplate != null) {
|
|
mBinding.renderTemplateToSurface(rootTag, initialUITemplate);
|
|
}
|
|
return rootTag;
|
|
}
|
|
|
|
/** Method called when an event has been dispatched on the C++ side. */
|
|
@DoNotStrip
|
|
public void onRequestEventBeat() {
|
|
mEventDispatcher.dispatchAllEvents();
|
|
}
|
|
|
|
@Override
|
|
public void removeRootView(int reactRootTag) {
|
|
// TODO T31905686: integrate with the unmounting of Fabric React Renderer.
|
|
mMountingManager.removeRootView(reactRootTag);
|
|
mReactContextForRootTag.remove(reactRootTag);
|
|
}
|
|
|
|
@Override
|
|
public void initialize() {
|
|
mEventDispatcher.registerEventEmitter(FABRIC, new FabricEventEmitter(this));
|
|
mEventDispatcher.addBatchEventDispatchedListener(mEventBeatManager);
|
|
}
|
|
|
|
@Override
|
|
public void onCatalystInstanceDestroy() {
|
|
mEventDispatcher.removeBatchEventDispatchedListener(mEventBeatManager);
|
|
mEventDispatcher.unregisterEventEmitter(FABRIC);
|
|
mBinding.unregister();
|
|
ViewManagerPropertyUpdater.clear();
|
|
}
|
|
|
|
@DoNotStrip
|
|
private void preallocateView(
|
|
int rootTag,
|
|
int reactTag,
|
|
final String componentName,
|
|
ReadableMap props,
|
|
boolean isLayoutable) {
|
|
ThemedReactContext context = mReactContextForRootTag.get(rootTag);
|
|
String component = sComponentNames.get(componentName);
|
|
synchronized (mPreMountItemsLock) {
|
|
mPreMountItems.add(
|
|
new PreAllocateViewMountItem(
|
|
context,
|
|
rootTag,
|
|
reactTag,
|
|
component != null ? component : componentName,
|
|
props,
|
|
isLayoutable));
|
|
}
|
|
}
|
|
|
|
@DoNotStrip
|
|
@SuppressWarnings("unused")
|
|
private MountItem removeMountItem(int reactTag, int parentReactTag, int index) {
|
|
return new RemoveMountItem(reactTag, parentReactTag, index);
|
|
}
|
|
|
|
@DoNotStrip
|
|
@SuppressWarnings("unused")
|
|
private MountItem insertMountItem(int reactTag, int parentReactTag, int index) {
|
|
return new InsertMountItem(reactTag, parentReactTag, index);
|
|
}
|
|
|
|
@DoNotStrip
|
|
@SuppressWarnings("unused")
|
|
private MountItem deleteMountItem(int reactTag) {
|
|
return new DeleteMountItem(reactTag);
|
|
}
|
|
|
|
@DoNotStrip
|
|
@SuppressWarnings("unused")
|
|
private MountItem updateLayoutMountItem(int reactTag, int x, int y, int width, int height) {
|
|
return new UpdateLayoutMountItem(reactTag, x, y, width, height);
|
|
}
|
|
|
|
@DoNotStrip
|
|
@SuppressWarnings("unused")
|
|
private MountItem updatePropsMountItem(int reactTag, ReadableMap map) {
|
|
return new UpdatePropsMountItem(reactTag, map);
|
|
}
|
|
|
|
@DoNotStrip
|
|
@SuppressWarnings("unused")
|
|
private MountItem updateLocalDataMountItem(int reactTag, ReadableMap newLocalData) {
|
|
return new UpdateLocalDataMountItem(reactTag, newLocalData);
|
|
}
|
|
|
|
@DoNotStrip
|
|
@SuppressWarnings("unused")
|
|
private MountItem updateEventEmitterMountItem(int reactTag, Object eventEmitter) {
|
|
return new UpdateEventEmitterMountItem(reactTag, (EventEmitterWrapper) eventEmitter);
|
|
}
|
|
|
|
@DoNotStrip
|
|
@SuppressWarnings("unused")
|
|
private MountItem createBatchMountItem(MountItem[] items, int size) {
|
|
return new BatchMountItem(items, size);
|
|
}
|
|
|
|
@DoNotStrip
|
|
@SuppressWarnings("unused")
|
|
private long measure(
|
|
String componentName,
|
|
ReadableMap localData,
|
|
ReadableMap props,
|
|
int minWidth,
|
|
int maxWidth,
|
|
int minHeight,
|
|
int maxHeight) {
|
|
|
|
return mMountingManager.measure(
|
|
mReactApplicationContext,
|
|
componentName,
|
|
localData,
|
|
props,
|
|
getYogaSize(minWidth, maxWidth),
|
|
getYogaMeasureMode(minWidth, maxWidth),
|
|
getYogaSize(minHeight, maxHeight),
|
|
getYogaMeasureMode(minHeight, maxHeight));
|
|
}
|
|
|
|
@Override
|
|
public void synchronouslyUpdateViewOnUIThread(int reactTag, ReadableMap props) {
|
|
long time = SystemClock.uptimeMillis();
|
|
scheduleMountItems(updatePropsMountItem(reactTag, props), time, 0, time, time);
|
|
}
|
|
|
|
/**
|
|
* This method enqueues UI operations directly to the UI thread. This might change in the future
|
|
* to enforce execution order using {@link ReactChoreographer#CallbackType}.
|
|
*/
|
|
@DoNotStrip
|
|
@SuppressWarnings("unused")
|
|
private void scheduleMountItems(
|
|
final MountItem mountItems,
|
|
long commitStartTime,
|
|
long layoutTime,
|
|
long finishTransactionStartTime,
|
|
long finishTransactionEndTime) {
|
|
|
|
// TODO T31905686: support multithreading
|
|
mCommitStartTime = commitStartTime;
|
|
mLayoutTime = layoutTime;
|
|
mFinishTransactionCPPTime = finishTransactionEndTime - finishTransactionStartTime;
|
|
mFinishTransactionTime = SystemClock.uptimeMillis() - finishTransactionStartTime;
|
|
mDispatchViewUpdatesTime = SystemClock.uptimeMillis();
|
|
synchronized (mMountItemsLock) {
|
|
mMountItems.add(mountItems);
|
|
}
|
|
|
|
if (UiThreadUtil.isOnUiThread()) {
|
|
dispatchMountItems();
|
|
}
|
|
}
|
|
|
|
@UiThread
|
|
private void dispatchMountItems() {
|
|
mRunStartTime = SystemClock.uptimeMillis();
|
|
|
|
List<MountItem> mountItemsToDispatch;
|
|
synchronized (mMountItemsLock) {
|
|
if (mMountItems.isEmpty()) {
|
|
return;
|
|
}
|
|
mountItemsToDispatch = mMountItems;
|
|
mMountItems = new ArrayList<>();
|
|
}
|
|
|
|
// If there are MountItems to dispatch, we make sure all the "pre mount items" are executed
|
|
ArrayDeque<MountItem> mPreMountItemsToDispatch = null;
|
|
synchronized (mPreMountItemsLock) {
|
|
if (!mPreMountItems.isEmpty()) {
|
|
mPreMountItemsToDispatch = mPreMountItems;
|
|
mPreMountItems = new ArrayDeque<>(PRE_MOUNT_ITEMS_INITIAL_SIZE_ARRAY);
|
|
}
|
|
}
|
|
if (mPreMountItemsToDispatch != null) {
|
|
Systrace.beginSection(
|
|
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
|
"FabricUIManager::mountViews preMountItems to execute: "
|
|
+ mPreMountItemsToDispatch.size());
|
|
|
|
while (!mPreMountItemsToDispatch.isEmpty()) {
|
|
mPreMountItemsToDispatch.pollFirst().execute(mMountingManager);
|
|
}
|
|
|
|
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
|
}
|
|
|
|
Systrace.beginSection(
|
|
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
|
"FabricUIManager::mountViews mountItems to execute: " + mountItemsToDispatch.size());
|
|
|
|
long batchedExecutionStartTime = SystemClock.uptimeMillis();
|
|
for (MountItem mountItem : mountItemsToDispatch) {
|
|
mountItem.execute(mMountingManager);
|
|
}
|
|
mBatchedExecutionTime = SystemClock.uptimeMillis() - batchedExecutionStartTime;
|
|
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
|
}
|
|
|
|
@UiThread
|
|
private void dispatchPreMountItems(long frameTimeNanos) {
|
|
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "FabricUIManager::premountViews");
|
|
|
|
while (true) {
|
|
long timeLeftInFrame = FRAME_TIME_MS - ((System.nanoTime() - frameTimeNanos) / 1000000);
|
|
if (timeLeftInFrame < MAX_TIME_IN_FRAME_FOR_NON_BATCHED_OPERATIONS_MS) {
|
|
break;
|
|
}
|
|
|
|
MountItem preMountItemsToDispatch;
|
|
synchronized (mPreMountItemsLock) {
|
|
if (mPreMountItems.isEmpty()) {
|
|
break;
|
|
}
|
|
preMountItemsToDispatch = mPreMountItems.pollFirst();
|
|
}
|
|
|
|
preMountItemsToDispatch.execute(mMountingManager);
|
|
}
|
|
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
|
}
|
|
|
|
public void setBinding(Binding binding) {
|
|
mBinding = binding;
|
|
}
|
|
|
|
/**
|
|
* Updates the layout metrics of the root view based on the Measure specs received by parameters.
|
|
*/
|
|
@Override
|
|
public void updateRootLayoutSpecs(
|
|
final int rootTag, final int widthMeasureSpec, final int heightMeasureSpec) {
|
|
|
|
mBinding.setConstraints(
|
|
rootTag,
|
|
getMinSize(widthMeasureSpec),
|
|
getMaxSize(widthMeasureSpec),
|
|
getMinSize(heightMeasureSpec),
|
|
getMaxSize(heightMeasureSpec));
|
|
}
|
|
|
|
public void receiveEvent(int reactTag, String eventName, @Nullable WritableMap params) {
|
|
EventEmitterWrapper eventEmitter = mMountingManager.getEventEmitter(reactTag);
|
|
if (eventEmitter == null) {
|
|
// This can happen if the view has disappeared from the screen (because of async events)
|
|
FLog.d(TAG, "Unable to invoke event: " + eventName + " for reactTag: " + reactTag);
|
|
return;
|
|
}
|
|
|
|
eventEmitter.invoke(eventName, params);
|
|
}
|
|
|
|
@Override
|
|
public void onHostResume() {
|
|
ReactChoreographer.getInstance()
|
|
.postFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback);
|
|
}
|
|
|
|
@Override
|
|
public void onHostPause() {
|
|
ReactChoreographer.getInstance()
|
|
.removeFrameCallback(ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback);
|
|
}
|
|
|
|
@Override
|
|
public void onHostDestroy() {}
|
|
|
|
@Override
|
|
public void dispatchCommand(
|
|
final int reactTag, final int commandId, final ReadableArray commandArgs) {
|
|
synchronized (mMountItemsLock) {
|
|
mMountItems.add(new DispatchCommandMountItem(reactTag, commandId, commandArgs));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setJSResponder(int reactTag, boolean blockNativeResponder) {
|
|
// do nothing for now.
|
|
}
|
|
|
|
@Override
|
|
public void clearJSResponder() {
|
|
// do nothing for now.
|
|
}
|
|
|
|
@Override
|
|
public void profileNextBatch() {
|
|
// TODO T31905686: Remove this method and add support for multi-threading performance counters
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Long> getPerformanceCounters() {
|
|
HashMap<String, Long> performanceCounters = new HashMap<>();
|
|
performanceCounters.put("CommitStartTime", mCommitStartTime);
|
|
performanceCounters.put("LayoutTime", mLayoutTime);
|
|
performanceCounters.put("DispatchViewUpdatesTime", mDispatchViewUpdatesTime);
|
|
performanceCounters.put("RunStartTime", mRunStartTime);
|
|
performanceCounters.put("BatchedExecutionTime", mBatchedExecutionTime);
|
|
performanceCounters.put("FinishFabricTransactionTime", mFinishTransactionTime);
|
|
performanceCounters.put("FinishFabricTransactionCPPTime", mFinishTransactionCPPTime);
|
|
return performanceCounters;
|
|
}
|
|
|
|
private class DispatchUIFrameCallback extends GuardedFrameCallback {
|
|
|
|
private DispatchUIFrameCallback(ReactContext reactContext) {
|
|
super(reactContext);
|
|
}
|
|
|
|
@Override
|
|
public void doFrameGuarded(long frameTimeNanos) {
|
|
if (!mIsMountingEnabled) {
|
|
FLog.w(
|
|
ReactConstants.TAG,
|
|
"Not flushing pending UI operations because of previously thrown Exception");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
|
|
dispatchPreMountItems(frameTimeNanos);
|
|
|
|
dispatchMountItems();
|
|
|
|
} catch (Exception ex) {
|
|
FLog.i(ReactConstants.TAG, "Exception thrown when executing UIFrameGuarded", ex);
|
|
mIsMountingEnabled = false;
|
|
throw ex;
|
|
} finally {
|
|
ReactChoreographer.getInstance()
|
|
.postFrameCallback(
|
|
ReactChoreographer.CallbackType.DISPATCH_UI, mDispatchUIFrameCallback);
|
|
}
|
|
}
|
|
}
|
|
}
|