Queue mount items executed before root view is attached

Summary:
Changelog: [Internal]

After D27016919, some mounting instructions can be executed before the view is attached, which is invalid. This change adds additional queue for such items, which can be later dispatched after view is ready.

The new queue is expected to be empty for usual rendering and used in prerendering flows only. In case of prerendering, it should only hold intermediate items between early start of `SurfaceHandler` and view attach.

Reviewed By: mdvacca

Differential Revision: D27291706

fbshipit-source-id: f383c1d0d7050f271993553b51bf2e387efe1e9e
This commit is contained in:
Andrei Shikov
2021-04-06 11:19:57 -07:00
committed by Facebook GitHub Bot
parent 2c0a0a7c25
commit d97350dcbc
3 changed files with 74 additions and 28 deletions
@@ -183,7 +183,7 @@ public class MountItemDispatcher {
printMountItem(command, "dispatchMountItems: Executing viewCommandMountItem");
}
try {
command.execute(mMountingManager);
executeOrEnqueue(command);
} catch (RetryableMountingLayerException e) {
// If the exception is marked as Retryable, we retry the viewcommand exactly once, after
// the current batch of mount items has finished executing.
@@ -225,7 +225,7 @@ public class MountItemDispatcher {
+ preMountItemsToDispatch.size());
for (PreAllocateViewMountItem preMountItem : preMountItemsToDispatch) {
preMountItem.execute(mMountingManager);
executeOrEnqueue(preMountItem);
}
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
@@ -244,7 +244,7 @@ public class MountItemDispatcher {
}
try {
mountItem.execute(mMountingManager);
executeOrEnqueue(mountItem);
} catch (Throwable e) {
// If there's an exception, we want to log diagnostics in prod and rethrow.
FLog.e(TAG, "dispatchMountItems: caught exception, displaying all MountItems", e);
@@ -288,7 +288,7 @@ public class MountItemDispatcher {
break;
}
preMountItemToDispatch.execute(mMountingManager);
executeOrEnqueue(preMountItemToDispatch);
}
} finally {
mInDispatch = false;
@@ -297,6 +297,16 @@ public class MountItemDispatcher {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
private void executeOrEnqueue(MountItem item) {
if (mMountingManager.isWaitingForViewAttach(item.getSurfaceId())) {
SurfaceMountingManager surfaceMountingManager =
mMountingManager.getSurfaceManager(item.getSurfaceId());
surfaceMountingManager.executeOnViewAttach(item);
} else {
item.execute(mMountingManager);
}
}
@Nullable
private static <E extends MountItem> List<E> drainConcurrentItemQueue(
ConcurrentLinkedQueue<E> queue) {
@@ -26,7 +26,6 @@ import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
import com.facebook.react.fabric.FabricUIManager;
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.touch.JSResponderHandler;
import com.facebook.react.uimanager.RootViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
@@ -40,7 +39,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
/**
* Class responsible for actually dispatching view updates enqueued via {@link
* FabricUIManager#scheduleMountItems(int, MountItem[])} on the UI thread.
* FabricUIManager#scheduleMountItem} on the UI thread.
*/
public class MountingManager {
public static final String TAG = MountingManager.class.getSimpleName();
@@ -53,14 +52,12 @@ public class MountingManager {
private final CopyOnWriteArrayList<Integer> mStoppedSurfaceIds = new CopyOnWriteArrayList<>();
@Nullable private SurfaceMountingManager mMostRecentSurfaceMountingManager;
@Nullable private SurfaceMountingManager mLastQueriedSurfaceMountingManager;
@NonNull private final JSResponderHandler mJSResponderHandler = new JSResponderHandler();
@NonNull private final ViewManagerRegistry mViewManagerRegistry;
@NonNull private final RootViewManager mRootViewManager = new RootViewManager();
private volatile int mStoppedSurfaceCacheLastId = View.NO_ID;
private volatile boolean mStoppedSurfaceCacheLastResult = false;
public MountingManager(@NonNull ViewManagerRegistry viewManagerRegistry) {
mViewManagerRegistry = viewManagerRegistry;
}
@@ -81,7 +78,7 @@ public class MountingManager {
public SurfaceMountingManager startSurface(final int surfaceId) {
SurfaceMountingManager surfaceMountingManager =
new SurfaceMountingManager(
surfaceId, mJSResponderHandler, mViewManagerRegistry, mRootViewManager);
surfaceId, mJSResponderHandler, mViewManagerRegistry, mRootViewManager, this);
// There could technically be a race condition here if addRootView is called twice from
// different threads, though this is (probably) extremely unlikely, and likely an error.
@@ -117,8 +114,6 @@ public class MountingManager {
@AnyThread
public void stopSurface(final int surfaceId) {
mStoppedSurfaceCacheLastId = View.NO_ID;
SurfaceMountingManager surfaceMountingManager = mSurfaceIdToManager.get(surfaceId);
if (surfaceMountingManager != null) {
// Maximum number of stopped surfaces to keep track of
@@ -145,6 +140,15 @@ public class MountingManager {
@Nullable
public SurfaceMountingManager getSurfaceManager(int surfaceId) {
if (mLastQueriedSurfaceMountingManager != null
&& mLastQueriedSurfaceMountingManager.getSurfaceId() == surfaceId) {
return mLastQueriedSurfaceMountingManager;
}
if (mMostRecentSurfaceMountingManager != null
&& mMostRecentSurfaceMountingManager.getSurfaceId() == surfaceId) {
return mMostRecentSurfaceMountingManager;
}
return mSurfaceIdToManager.get(surfaceId);
}
@@ -164,25 +168,11 @@ public class MountingManager {
}
public boolean surfaceIsStopped(int surfaceId) {
if (surfaceId == View.NO_ID) {
return false;
}
if (surfaceId == mStoppedSurfaceCacheLastId) {
return mStoppedSurfaceCacheLastResult;
}
boolean res = surfaceIsStoppedImpl(surfaceId);
mStoppedSurfaceCacheLastResult = res;
mStoppedSurfaceCacheLastId = surfaceId;
return res;
}
private boolean surfaceIsStoppedImpl(int surfaceId) {
if (mStoppedSurfaceIds.contains(surfaceId)) {
return true;
}
SurfaceMountingManager surfaceMountingManager = mSurfaceIdToManager.get(surfaceId);
SurfaceMountingManager surfaceMountingManager = getSurfaceManager(surfaceId);
if (surfaceMountingManager != null && surfaceMountingManager.isStopped()) {
return true;
}
@@ -190,6 +180,19 @@ public class MountingManager {
return false;
}
public boolean isWaitingForViewAttach(int surfaceId) {
SurfaceMountingManager mountingManager = getSurfaceManager(surfaceId);
if (mountingManager == null) {
return false;
}
if (mountingManager.isStopped()) {
return false;
}
return !mountingManager.isRootViewAttached();
}
/**
* Get SurfaceMountingManager associated with a ReactTag. Unfortunately, this requires lookups
* over N maps, where N is the number of active or recently-stopped Surfaces. Each lookup will
@@ -26,6 +26,7 @@ import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.touch.JSResponderHandler;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.ReactRoot;
@@ -39,6 +40,7 @@ import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.ViewManagerRegistry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.annotation.Nullable;
public class SurfaceMountingManager {
@@ -47,15 +49,18 @@ public class SurfaceMountingManager {
private static final boolean SHOW_CHANGED_VIEW_HIERARCHIES = ReactBuildConfig.DEBUG && false;
private volatile boolean mIsStopped = false;
private volatile boolean mRootViewAttached = false;
@Nullable private ThemedReactContext mThemedReactContext;
// These are all non-null, until StopSurface is called
private ConcurrentHashMap<Integer, ViewState> mTagToViewState =
new ConcurrentHashMap<>(); // any thread
private ConcurrentLinkedQueue<MountItem> mOnViewAttachItems = new ConcurrentLinkedQueue<>();
private JSResponderHandler mJSResponderHandler;
private ViewManagerRegistry mViewManagerRegistry;
private RootViewManager mRootViewManager;
private MountingManager mMountingManager;
// This is null *until* StopSurface is called.
private Set<Integer> mTagSetForStoppedSurface;
@@ -67,12 +72,14 @@ public class SurfaceMountingManager {
int surfaceId,
@NonNull JSResponderHandler jsResponderHandler,
@NonNull ViewManagerRegistry viewManagerRegistry,
@NonNull RootViewManager rootViewManager) {
@NonNull RootViewManager rootViewManager,
@NonNull MountingManager mountingManager) {
mSurfaceId = surfaceId;
mJSResponderHandler = jsResponderHandler;
mViewManagerRegistry = viewManagerRegistry;
mRootViewManager = rootViewManager;
mMountingManager = mountingManager;
}
public boolean isStopped() {
@@ -84,6 +91,14 @@ public class SurfaceMountingManager {
addRootView(rootView);
}
public int getSurfaceId() {
return mSurfaceId;
}
public boolean isRootViewAttached() {
return mRootViewAttached;
}
@Nullable
public ThemedReactContext getContext() {
return mThemedReactContext;
@@ -133,6 +148,11 @@ public class SurfaceMountingManager {
return mTagToViewState.containsKey(tag);
}
@AnyThread
public void executeOnViewAttach(MountItem item) {
mOnViewAttachItems.add(item);
}
@AnyThread
private void addRootView(@NonNull final View rootView) {
if (isStopped()) {
@@ -174,6 +194,9 @@ public class SurfaceMountingManager {
if (rootView instanceof ReactRoot) {
((ReactRoot) rootView).setRootViewTag(mSurfaceId);
}
mRootViewAttached = true;
executeViewAttachMountItems();
}
};
@@ -184,6 +207,14 @@ public class SurfaceMountingManager {
}
}
@UiThread
private void executeViewAttachMountItems() {
while (!mOnViewAttachItems.isEmpty()) {
MountItem item = mOnViewAttachItems.poll();
item.execute(mMountingManager);
}
}
/**
* Stop surface and all operations within it. Garbage-collect Views (caller is responsible for
* removing RootView from View layer).
@@ -232,6 +263,8 @@ public class SurfaceMountingManager {
mTagToViewState = null;
mJSResponderHandler = null;
mRootViewManager = null;
mMountingManager = null;
mOnViewAttachItems.clear();
}
};