Summary:
Fixes T54997838 by preventing any view mutations during `onMeasure` calls.

There might still be places where this is possible, but this is where I'm seeing all the crashes currently.

See comments in ReactRootView for why views were mutated during onMeasure.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D18518591

fbshipit-source-id: 1406af8a6b0bfcc86f4cc5b451b3967f312dfd85
This commit is contained in:
Joshua Gross 2019-11-14 21:21:31 -08:00 коммит произвёл Facebook Github Bot
Родитель c54f5cf72a
Коммит ce226c1f28
4 изменённых файлов: 67 добавлений и 1 удалений

Просмотреть файл

@ -127,8 +127,11 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setAllowImmediateUIOperationExecution(false);
if (mUseSurface) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setAllowImmediateUIOperationExecution(true);
return;
}
@ -182,6 +185,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
mLastHeight = height;
} finally {
setAllowImmediateUIOperationExecution(true);
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
@ -437,6 +441,38 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
}
}
/**
* In Fabric, it is possible for MountItems to be scheduled during onMeasure calls, specifically:
*
* <p>ReactRootView.onMeasure -> ReactRootView.updateRootLayoutSpecs ->
* FabricUIManager.updateRootLayoutSpecs -> Binding.setConstraints -> (C++) commit new tree ->
* (C++ Android binding) diff tree, schedule mount items -> FabricUIManager.scheduleMountItem
*
* <p>If called on the main thread, `scheduleMountItem` will execute MountItems synchronously,
* causing all ShadowNode updates to be flushed to the view hierarchy, on the main thread, during
* an onMeasure call.
*
* <p>Use this method to disable immediate execution of mount items.
*
* <p>This is a noop outside in pre-Fabric React Native.
*/
private void setAllowImmediateUIOperationExecution(boolean flag) {
final ReactInstanceManager reactInstanceManager = mReactInstanceManager;
if (reactInstanceManager == null) {
return;
}
final ReactContext reactApplicationContext = reactInstanceManager.getCurrentReactContext();
if (reactApplicationContext == null) {
return;
}
UIManagerHelper.getUIManager(reactApplicationContext, getUIManagerType())
.setAllowImmediateUIOperationExecution(flag);
}
/**
* Unmount the react application at this root view, reclaiming any JS memory associated with that
* application. If {@link #startReactApplication} is called, this method must be called before the

Просмотреть файл

@ -62,4 +62,12 @@ public interface UIManager extends JSIModule, PerformanceCounter {
* @param eventType
*/
void sendAccessibilityEvent(int reactTag, int eventType);
/**
* When mounting instructions are scheduled on the UI thread, should they be executed immediately?
* For Fabric. Should noop in pre-Fabric.
*
* @param flag
*/
void setAllowImmediateUIOperationExecution(boolean flag);
}

Просмотреть файл

@ -125,6 +125,13 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
@NonNull
private final DispatchUIFrameCallback mDispatchUIFrameCallback;
/**
* Whether or not to immediately, synchronously execute mountItems when they are scheduled on the
* UI thread.
*/
@ThreadConfined(UI)
private boolean mImmediatelyExecutedMountItemsOnUI = true;
/**
* This is used to keep track of whether or not the FabricUIManager has been destroyed. Once the
* Catalyst instance is being destroyed, we should cease all operation here.
@ -446,6 +453,16 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
}
}
/**
* When mounting instructions are scheduled on the UI thread, should they be executed immediately?
* *
*/
@Override
@ThreadConfined(UI)
public void setAllowImmediateUIOperationExecution(boolean flag) {
mImmediatelyExecutedMountItemsOnUI = flag;
}
/**
* This method enqueues UI operations directly to the UI thread. This might change in the future
* to enforce execution order using {@link ReactChoreographer#CallbackType}.
@ -480,7 +497,7 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
mMountItems.add(mountItem);
}
if (UiThreadUtil.isOnUiThread()) {
if (mImmediatelyExecutedMountItemsOnUI && UiThreadUtil.isOnUiThread()) {
dispatchMountItems();
}

Просмотреть файл

@ -828,6 +828,11 @@ public class UIManagerModule extends ReactContextBaseJavaModule
}
}
@Override
public void setAllowImmediateUIOperationExecution(boolean flag) {
// Noop outside of Fabric, call directly on FabricUIManager
}
/**
* Schedule a block to be executed on the UI thread. Useful if you need to execute view logic
* after all currently queued view updates have completed.