From 135ba492fbd500ec555baadab7ff1db3d537acf3 Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Wed, 10 Apr 2019 13:07:33 -0700 Subject: [PATCH] Detach dependency on RRV for Instance Manager Summary: Remove dependency on ReactRootView in ReactInstanceManager by creating a rough interface (ReactRoot) so that either a Surface or ReactRootView can be attached to ReactInstanceManager. Reviewed By: ejanzer, mdvacca Differential Revision: D14158890 fbshipit-source-id: b7ab4654b1e0ef8343230a3c15023653a7f23a4b --- .../src/main/java/com/facebook/react/BUCK | 1 + .../facebook/react/ReactInstanceManager.java | 101 +++++++++--------- .../com/facebook/react/ReactRootView.java | 31 ++++-- .../main/java/com/facebook/react/surface/BUCK | 20 ++++ .../facebook/react/surface/ReactStage.java | 33 ++++++ .../facebook/react/uimanager/ReactRoot.java | 50 +++++++++ 6 files changed, 180 insertions(+), 56 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/surface/BUCK create mode 100644 ReactAndroid/src/main/java/com/facebook/react/surface/ReactStage.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactRoot.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/BUCK b/ReactAndroid/src/main/java/com/facebook/react/BUCK index 04b76cf81..d44eb9eff 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/BUCK @@ -35,6 +35,7 @@ rn_android_library( react_native_target("java/com/facebook/react/modules/deviceinfo:deviceinfo"), react_native_target("java/com/facebook/react/modules/systeminfo:systeminfo"), react_native_target("java/com/facebook/react/modules/toast:toast"), + react_native_target("java/com/facebook/react/surface:surface"), react_native_target("java/com/facebook/react/uimanager:uimanager"), react_native_target("java/com/facebook/react/module/annotations:annotations"), react_native_target("java/com/facebook/react/views/imagehelper:imagehelper"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 69c42c75f..022eb1cf5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -84,7 +84,9 @@ import com.facebook.react.modules.core.ReactChoreographer; import com.facebook.react.modules.debug.interfaces.DeveloperSettings; import com.facebook.react.modules.fabric.ReactFabric; import com.facebook.react.packagerconnection.RequestHandler; +import com.facebook.react.surface.ReactStage; import com.facebook.react.uimanager.DisplayMetricsHolder; +import com.facebook.react.uimanager.ReactRoot; import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIManagerHelper; import com.facebook.react.uimanager.ViewManager; @@ -99,7 +101,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; /** @@ -135,8 +136,8 @@ public class ReactInstanceManager { void onReactContextInitialized(ReactContext context); } - private final Set mAttachedRootViews = Collections.synchronizedSet( - new HashSet()); + private final Set mAttachedReactRoots = Collections.synchronizedSet( + new HashSet()); private volatile LifecycleState mLifecycleState; @@ -710,45 +711,49 @@ public class ReactInstanceManager { mDevSupportManager.showDevOptionsDialog(); } + private void clearReactRoot(ReactRoot reactRoot) { + reactRoot.getRootViewGroup().removeAllViews(); + reactRoot.getRootViewGroup().setId(View.NO_ID); + } + /** - * Attach given {@param rootView} to a catalyst instance manager and start JS application using + * Attach given {@param reactRoot} to a catalyst instance manager and start JS application using * JS module provided by {@link ReactRootView#getJSModuleName}. If the react context is currently * being (re)-created, or if react context has not been created yet, the JS application associated - * with the provided root view will be started asynchronously, i.e this method won't block. - * This view will then be tracked by this manager and in case of catalyst instance restart it will + * with the provided reactRoot reactRoot will be started asynchronously, i.e this method won't block. + * This reactRoot will then be tracked by this manager and in case of catalyst instance restart it will * be re-attached. */ @ThreadConfined(UI) - public void attachRootView(ReactRootView rootView) { + public void attachRootView(ReactRoot reactRoot) { UiThreadUtil.assertOnUiThread(); - mAttachedRootViews.add(rootView); + mAttachedReactRoots.add(reactRoot); - // Reset view content as it's going to be populated by the application content from JS. - rootView.removeAllViews(); - rootView.setId(View.NO_ID); + // Reset reactRoot content as it's going to be populated by the application content from JS. + clearReactRoot(reactRoot); // If react context is being created in the background, JS application will be started - // automatically when creation completes, as root view is part of the attached root view list. + // automatically when creation completes, as reactRoot reactRoot is part of the attached reactRoot reactRoot list. ReactContext currentContext = getCurrentReactContext(); if (mCreateReactContextThread == null && currentContext != null) { - attachRootViewToInstance(rootView); + attachRootViewToInstance(reactRoot); } } /** - * Detach given {@param rootView} from current catalyst instance. It's safe to call this method - * multiple times on the same {@param rootView} - in that case view will be detached with the + * Detach given {@param reactRoot} from current catalyst instance. It's safe to call this method + * multiple times on the same {@param reactRoot} - in that case view will be detached with the * first call. */ @ThreadConfined(UI) - public void detachRootView(ReactRootView rootView) { + public void detachRootView(ReactRoot reactRoot) { UiThreadUtil.assertOnUiThread(); - synchronized (mAttachedRootViews) { - if (mAttachedRootViews.contains(rootView)) { + synchronized (mAttachedReactRoots) { + if (mAttachedReactRoots.contains(reactRoot)) { ReactContext currentContext = getCurrentReactContext(); - mAttachedRootViews.remove(rootView); + mAttachedReactRoots.remove(reactRoot); if (currentContext != null && currentContext.hasActiveCatalystInstance()) { - detachViewFromInstance(rootView, currentContext.getCatalystInstance()); + detachViewFromInstance(reactRoot, currentContext.getCatalystInstance()); } } } @@ -908,7 +913,7 @@ public class ReactInstanceManager { private void runCreateReactContextOnNewThread(final ReactContextInitParams initParams) { Log.d(ReactConstants.TAG, "ReactInstanceManager.runCreateReactContextOnNewThread()"); UiThreadUtil.assertOnUiThread(); - synchronized (mAttachedRootViews) { + synchronized (mAttachedReactRoots) { synchronized (mReactContextLock) { if (mCurrentReactContext != null) { tearDownReactContext(mCurrentReactContext); @@ -985,7 +990,7 @@ public class ReactInstanceManager { ReactMarker.logMarker(PRE_SETUP_REACT_CONTEXT_END); ReactMarker.logMarker(SETUP_REACT_CONTEXT_START); Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "setupReactContext"); - synchronized (mAttachedRootViews) { + synchronized (mAttachedReactRoots) { synchronized (mReactContextLock) { mCurrentReactContext = Assertions.assertNotNull(reactContext); } @@ -999,8 +1004,8 @@ public class ReactInstanceManager { moveReactContextToCurrentLifecycleState(); ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_START); - for (ReactRootView rootView : mAttachedRootViews) { - attachRootViewToInstance(rootView); + for (ReactRoot reactRoot : mAttachedReactRoots) { + attachRootViewToInstance(reactRoot); } ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_END); } @@ -1038,47 +1043,46 @@ public class ReactInstanceManager { }); } - private void attachRootViewToInstance(final ReactRootView rootView) { + private void attachRootViewToInstance(final ReactRoot reactRoot) { Log.d(ReactConstants.TAG, "ReactInstanceManager.attachRootViewToInstance()"); Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachRootViewToInstance"); - UIManager uiManagerModule = UIManagerHelper.getUIManager(mCurrentReactContext, rootView.getUIManagerType()); + UIManager uiManagerModule = UIManagerHelper.getUIManager(mCurrentReactContext, reactRoot.getUIManagerType()); - @Nullable Bundle initialProperties = rootView.getAppProperties(); + @Nullable Bundle initialProperties = reactRoot.getAppProperties(); final int rootTag = uiManagerModule.addRootView( - rootView.getView(), + reactRoot.getRootViewGroup(), initialProperties == null ? new WritableNativeMap() : Arguments.fromBundle(initialProperties), - rootView.getInitialUITemplate()); - rootView.setRootViewTag(rootTag); - rootView.runApplication(); + reactRoot.getInitialUITemplate()); + reactRoot.setRootViewTag(rootTag); + reactRoot.runApplication(); Systrace.beginAsyncSection( TRACE_TAG_REACT_JAVA_BRIDGE, "pre_rootView.onAttachedToReactInstance", rootTag); - UiThreadUtil.runOnUiThread(new Runnable() { - @Override - public void run() { - Systrace.endAsyncSection( - TRACE_TAG_REACT_JAVA_BRIDGE, - "pre_rootView.onAttachedToReactInstance", - rootTag); - rootView.onAttachedToReactInstance(); - } - }); + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + Systrace.endAsyncSection( + TRACE_TAG_REACT_JAVA_BRIDGE, "pre_rootView.onAttachedToReactInstance", rootTag); + reactRoot.onStage(ReactStage.ON_ATTACH_TO_INSTANCE); + } + }); Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); } private void detachViewFromInstance( - ReactRootView rootView, + ReactRoot reactRoot, CatalystInstance catalystInstance) { Log.d(ReactConstants.TAG, "ReactInstanceManager.detachViewFromInstance()"); UiThreadUtil.assertOnUiThread(); - if (rootView.getUIManagerType() == FABRIC) { + if (reactRoot.getUIManagerType() == FABRIC) { catalystInstance.getJSModule(ReactFabric.class) - .unmountComponentAtNode(rootView.getRootViewTag()); + .unmountComponentAtNode(reactRoot.getRootViewTag()); } else { catalystInstance.getJSModule(AppRegistry.class) - .unmountApplicationComponentAtRootTag(rootView.getRootViewTag()); + .unmountApplicationComponentAtRootTag(reactRoot.getRootViewTag()); } } @@ -1090,10 +1094,9 @@ public class ReactInstanceManager { reactContext.onHostPause(); } - synchronized (mAttachedRootViews) { - for (ReactRootView rootView : mAttachedRootViews) { - rootView.removeAllViews(); - rootView.setId(View.NO_ID); + synchronized (mAttachedReactRoots) { + for (ReactRoot reactRoot : mAttachedReactRoots) { + clearReactRoot(reactRoot); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 2cde3cbc2..324af3df5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -41,11 +41,13 @@ import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.modules.appregistry.AppRegistry; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.deviceinfo.DeviceInfoModule; +import com.facebook.react.surface.ReactStage; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.JSTouchDispatcher; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.RootView; +import com.facebook.react.uimanager.ReactRoot; import com.facebook.react.uimanager.UIManagerHelper; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.common.UIManagerType; @@ -65,7 +67,7 @@ import javax.annotation.Nullable; * subsequent touch events related to that gesture (in case when JS code wants to handle that * gesture). */ -public class ReactRootView extends FrameLayout implements RootView { +public class ReactRootView extends FrameLayout implements RootView, ReactRoot { /** * Listener interface for react root view events @@ -124,11 +126,6 @@ public class ReactRootView extends FrameLayout implements RootView { setClipChildren(false); } - public View getView() { - // TODO add mUseSurface to return surface here - return this; - } - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mUseSurface) { @@ -347,6 +344,11 @@ public class ReactRootView extends FrameLayout implements RootView { } } + @Override + public ViewGroup getRootViewGroup() { + return this; + } + /** * {@see #startReactApplication(ReactInstanceManager, String, android.os.Bundle)} */ @@ -433,6 +435,17 @@ public class ReactRootView extends FrameLayout implements RootView { mShouldLogContentAppeared = false; } + @Override + public void onStage(int stage) { + switch(stage) { + case ReactStage.ON_ATTACH_TO_INSTANCE: + onAttachedToReactInstance(); + break; + default: + break; + } + } + public void onAttachedToReactInstance() { // Create the touch dispatcher here instead of having it always available, to make sure // that all touch events are only passed to JS after React/JS side is ready to consume @@ -452,10 +465,12 @@ public class ReactRootView extends FrameLayout implements RootView { return Assertions.assertNotNull(mJSModuleName); } + @Override public @Nullable Bundle getAppProperties() { return mAppProperties; } + @Override public @Nullable String getInitialUITemplate() { return mInitialUITemplate; } @@ -472,7 +487,8 @@ public class ReactRootView extends FrameLayout implements RootView { * Calls into JS to start the React application. Can be called multiple times with the * same rootTag, which will re-render the application from the root. */ - /* package */ void runApplication() { + @Override + public void runApplication() { Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "ReactRootView.runApplication"); try { if (mReactInstanceManager == null || !mIsAttachedToInstance) { @@ -581,6 +597,7 @@ public class ReactRootView extends FrameLayout implements RootView { mUIManagerType = isFabric ? FABRIC : DEFAULT; } + @Override public @UIManagerType int getUIManagerType() { return mUIManagerType; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/surface/BUCK b/ReactAndroid/src/main/java/com/facebook/react/surface/BUCK new file mode 100644 index 000000000..7682d5220 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/surface/BUCK @@ -0,0 +1,20 @@ +load("@fbsource//tools/build_defs/oss:rn_defs.bzl", "YOGA_TARGET", "react_native_dep", "react_native_target", "rn_android_library") + +rn_android_library( + name = "surface", + srcs = glob(["*.java"]), + is_androidx = True, + visibility = [ + "PUBLIC", + ], + deps = [ + YOGA_TARGET, + react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), + react_native_dep("third-party/android/support-annotations:android-support-annotations"), + react_native_dep("third-party/java/infer-annotations:infer-annotations"), + react_native_dep("third-party/java/jsr-305:jsr-305"), + react_native_target("java/com/facebook/react/bridge:bridge"), + react_native_target("java/com/facebook/react/common:common"), + react_native_target("java/com/facebook/react/module/annotations:annotations"), + ], +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/surface/ReactStage.java b/ReactAndroid/src/main/java/com/facebook/react/surface/ReactStage.java new file mode 100644 index 000000000..637aa8c87 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/surface/ReactStage.java @@ -0,0 +1,33 @@ +package com.facebook.react.surface; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import androidx.annotation.IntDef; +import java.lang.annotation.Retention; + +/** + * The stage of the Surface + */ +@Retention(SOURCE) +@IntDef({ + ReactStage.SURFACE_DID_INITIALIZE, + ReactStage.BRIDGE_DID_LOAD, + ReactStage.MODULE_DID_LOAD, + ReactStage.SURFACE_DID_RUN, + ReactStage.SURFACE_DID_INITIAL_RENDERING, + ReactStage.SURFACE_DID_INITIAL_LAYOUT, + ReactStage.SURFACE_DID_INITIAL_MOUNTING, + ReactStage.SURFACE_DID_STOP +}) +public @interface ReactStage { + int SURFACE_DID_INITIALIZE = 0; + int BRIDGE_DID_LOAD = 1; + int MODULE_DID_LOAD = 2; + int SURFACE_DID_RUN = 3; + int SURFACE_DID_INITIAL_RENDERING = 4; + int SURFACE_DID_INITIAL_LAYOUT = 5; + int SURFACE_DID_INITIAL_MOUNTING = 6; + int SURFACE_DID_STOP = 7; + + int ON_ATTACH_TO_INSTANCE = 101; +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactRoot.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactRoot.java new file mode 100644 index 000000000..c56216ded --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactRoot.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * 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.uimanager; + +import android.os.Bundle; +import android.view.ViewGroup; +import com.facebook.react.uimanager.common.UIManagerType; +import javax.annotation.Nullable; + + +/** + * Interface for the root native view of a React native application + */ +public interface ReactRoot { + + /** + * Return cached launch properties for app + */ + @Nullable Bundle getAppProperties(); + @Nullable String getInitialUITemplate(); + + /** + * Fabric or Default UI Manager, see {@link UIManagerType} + */ + @UIManagerType int getUIManagerType(); + + int getRootViewTag(); + + void setRootViewTag(int rootViewTag); + + /** + * Calls into JS to start the React application. + */ + void runApplication(); + + /** + * Handler for stages {@link com.facebook.react.surface.ReactStage} + */ + void onStage(int stage); + + /** + * Return native view for root + */ + ViewGroup getRootViewGroup(); +}