/** * 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; import static com.facebook.infer.annotation.ThreadConfined.UI; import static com.facebook.react.bridge.ReactMarkerConstants.ATTACH_MEASURED_ROOT_VIEWS_END; import static com.facebook.react.bridge.ReactMarkerConstants.ATTACH_MEASURED_ROOT_VIEWS_START; import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_NATIVE_MODULE_REGISTRY_END; import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_NATIVE_MODULE_REGISTRY_START; import static com.facebook.react.bridge.ReactMarkerConstants.CHANGE_THREAD_PRIORITY; import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_CATALYST_INSTANCE_END; import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_CATALYST_INSTANCE_START; import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_REACT_CONTEXT_START; import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_VIEW_MANAGERS_END; import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_VIEW_MANAGERS_START; import static com.facebook.react.bridge.ReactMarkerConstants.PRE_SETUP_REACT_CONTEXT_END; import static com.facebook.react.bridge.ReactMarkerConstants.PRE_SETUP_REACT_CONTEXT_START; import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_END; import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_START; import static com.facebook.react.bridge.ReactMarkerConstants.REACT_CONTEXT_THREAD_END; import static com.facebook.react.bridge.ReactMarkerConstants.REACT_CONTEXT_THREAD_START; import static com.facebook.react.bridge.ReactMarkerConstants.SETUP_REACT_CONTEXT_END; import static com.facebook.react.bridge.ReactMarkerConstants.SETUP_REACT_CONTEXT_START; import static com.facebook.react.bridge.ReactMarkerConstants.VM_INIT; import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_APPS; import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE; import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JS_VM_CALLS; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.Process; import android.util.Log; import android.view.View; import androidx.annotation.Nullable; import androidx.core.view.ViewCompat; import com.facebook.common.logging.FLog; import com.facebook.debug.holder.PrinterHolder; import com.facebook.debug.tags.ReactDebugOverlayTags; import com.facebook.infer.annotation.Assertions; import com.facebook.infer.annotation.ThreadConfined; import com.facebook.infer.annotation.ThreadSafe; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.CatalystInstanceImpl; import com.facebook.react.bridge.JSBundleLoader; import com.facebook.react.bridge.JSIModulePackage; import com.facebook.react.bridge.JSIModuleType; import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.JavaScriptExecutor; import com.facebook.react.bridge.JavaScriptExecutorFactory; import com.facebook.react.bridge.NativeModuleCallExceptionHandler; import com.facebook.react.bridge.NativeModuleRegistry; import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; import com.facebook.react.bridge.ProxyJavaScriptExecutor; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactMarker; import com.facebook.react.bridge.ReactMarkerConstants; import com.facebook.react.bridge.UIManager; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec; import com.facebook.react.common.LifecycleState; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.devsupport.DevSupportManagerFactory; import com.facebook.react.devsupport.ReactInstanceManagerDevHelper; import com.facebook.react.devsupport.RedBoxHandler; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.devsupport.interfaces.PackagerStatusCallback; import com.facebook.react.modules.appearance.AppearanceModule; import com.facebook.react.modules.appregistry.AppRegistry; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.DeviceEventManagerModule; 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; import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper; import com.facebook.soloader.SoLoader; import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * This class is managing instances of {@link CatalystInstance}. It exposes a way to configure * catalyst instance using {@link ReactPackage} and keeps track of the lifecycle of that instance. * It also sets up connection between the instance and developers support functionality of the * framework. * *
An instance of this manager is required to start JS application in {@link ReactRootView} (see * {@link ReactRootView#startReactApplication} for more info). * *
The lifecycle of the instance of {@link ReactInstanceManager} should be bound to the activity * that owns the {@link ReactRootView} that is used to render react application using this instance * manager (see {@link ReactRootView#startReactApplication}). It's required to pass owning * activity's lifecycle events to the instance manager (see {@link #onHostPause}, {@link * #onHostDestroy} and {@link #onHostResume}). * *
To instantiate an instance of this class use {@link #builder}.
*/
@ThreadSafe
public class ReactInstanceManager {
private static final String TAG = ReactInstanceManager.class.getSimpleName();
/** Listener interface for react instance events. */
public interface ReactInstanceEventListener {
/**
* Called when the react context is initialized (all modules registered). Always called on the
* UI thread.
*/
void onReactContextInitialized(ReactContext context);
}
private final Set Called from UI thread.
*/
@ThreadConfined(UI)
public void createReactContextInBackground() {
Log.d(ReactConstants.TAG, "ReactInstanceManager.createReactContextInBackground()");
UiThreadUtil
.assertOnUiThread(); // Assert before setting mHasStartedCreatingInitialContext = true
if (!mHasStartedCreatingInitialContext) {
mHasStartedCreatingInitialContext = true;
recreateReactContextInBackgroundInner();
}
}
/**
* Recreate the react application and context. This should be called if configuration has changed
* or the developer has requested the app to be reloaded. It should only be called after an
* initial call to createReactContextInBackground.
*
* Called from UI thread.
*/
@ThreadConfined(UI)
public void recreateReactContextInBackground() {
Assertions.assertCondition(
mHasStartedCreatingInitialContext,
"recreateReactContextInBackground should only be called after the initial "
+ "createReactContextInBackground call.");
recreateReactContextInBackgroundInner();
}
@ThreadConfined(UI)
private void recreateReactContextInBackgroundInner() {
Log.d(ReactConstants.TAG, "ReactInstanceManager.recreateReactContextInBackgroundInner()");
PrinterHolder.getPrinter()
.logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: recreateReactContextInBackground");
UiThreadUtil.assertOnUiThread();
if (mUseDeveloperSupport && mJSMainModulePath != null) {
final DeveloperSettings devSettings = mDevSupportManager.getDevSettings();
if (!Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
if (mBundleLoader == null) {
mDevSupportManager.handleReloadJS();
} else {
mDevSupportManager.isPackagerRunning(
new PackagerStatusCallback() {
@Override
public void onPackagerStatusFetched(final boolean packagerIsRunning) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (packagerIsRunning) {
mDevSupportManager.handleReloadJS();
} else if (mDevSupportManager.hasUpToDateJSBundleInCache()
&& !devSettings.isRemoteJSDebugEnabled()) {
// If there is a up-to-date bundle downloaded from server,
// with remote JS debugging disabled, always use that.
onJSBundleLoadedFromServer();
} else {
// If dev server is down, disable the remote JS debugging.
devSettings.setRemoteJSDebugEnabled(false);
recreateReactContextInBackgroundFromBundleLoader();
}
}
});
}
});
}
return;
}
}
recreateReactContextInBackgroundFromBundleLoader();
}
@ThreadConfined(UI)
private void recreateReactContextInBackgroundFromBundleLoader() {
Log.d(
ReactConstants.TAG,
"ReactInstanceManager.recreateReactContextInBackgroundFromBundleLoader()");
PrinterHolder.getPrinter()
.logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: load from BundleLoader");
recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);
}
/**
* @return whether createReactContextInBackground has been called. Will return false after
* onDestroy until a new initial context has been created.
*/
public boolean hasStartedCreatingInitialContext() {
return mHasStartedCreatingInitialContext;
}
/**
* This method will give JS the opportunity to consume the back button event. If JS does not
* consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS.
*/
public void onBackPressed() {
UiThreadUtil.assertOnUiThread();
ReactContext reactContext = mCurrentReactContext;
if (reactContext == null) {
// Invoke without round trip to JS.
FLog.w(ReactConstants.TAG, "Instance detached from instance manager");
invokeDefaultOnBackPressed();
} else {
DeviceEventManagerModule deviceEventManagerModule =
reactContext.getNativeModule(DeviceEventManagerModule.class);
deviceEventManagerModule.emitHardwareBackPressed();
}
}
private void invokeDefaultOnBackPressed() {
UiThreadUtil.assertOnUiThread();
if (mDefaultBackButtonImpl != null) {
mDefaultBackButtonImpl.invokeDefaultOnBackPressed();
}
}
/** This method will give JS the opportunity to receive intents via Linking. */
@ThreadConfined(UI)
public void onNewIntent(Intent intent) {
UiThreadUtil.assertOnUiThread();
ReactContext currentContext = getCurrentReactContext();
if (currentContext == null) {
FLog.w(ReactConstants.TAG, "Instance detached from instance manager");
} else {
String action = intent.getAction();
Uri uri = intent.getData();
if (uri != null
&& (Intent.ACTION_VIEW.equals(action)
|| NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action))) {
DeviceEventManagerModule deviceEventManagerModule =
currentContext.getNativeModule(DeviceEventManagerModule.class);
deviceEventManagerModule.emitNewIntentReceived(uri);
}
currentContext.onNewIntent(mCurrentActivity, intent);
}
}
private void toggleElementInspector() {
ReactContext currentContext = getCurrentReactContext();
if (currentContext != null) {
currentContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("toggleElementInspector", null);
}
}
/**
* Call this from {@link Activity#onPause()}. This notifies any listening modules so they can do
* any necessary cleanup.
*
* @deprecated Use {@link #onHostPause(Activity)} instead.
*/
@ThreadConfined(UI)
public void onHostPause() {
UiThreadUtil.assertOnUiThread();
mDefaultBackButtonImpl = null;
if (mUseDeveloperSupport) {
mDevSupportManager.setDevSupportEnabled(false);
}
moveToBeforeResumeLifecycleState();
}
/**
* Call this from {@link Activity#onPause()}. This notifies any listening modules so they can do
* any necessary cleanup. The passed Activity is the current Activity being paused. This will
* always be the foreground activity that would be returned by {@link
* ReactContext#getCurrentActivity()}.
*
* @param activity the activity being paused
*/
@ThreadConfined(UI)
public void onHostPause(Activity activity) {
Assertions.assertNotNull(mCurrentActivity);
Assertions.assertCondition(
activity == mCurrentActivity,
"Pausing an activity that is not the current activity, this is incorrect! "
+ "Current activity: "
+ mCurrentActivity.getClass().getSimpleName()
+ " "
+ "Paused activity: "
+ activity.getClass().getSimpleName());
onHostPause();
}
/**
* Use this method when the activity resumes to enable invoking the back button directly from JS.
*
* This method retains an instance to provided mDefaultBackButtonImpl. Thus it's important to
* pass from the activity instance that owns this particular instance of {@link
* ReactInstanceManager}, so that once this instance receive {@link #onHostDestroy} event it will
* clear the reference to that defaultBackButtonImpl.
*
* @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} from an Activity that owns
* this instance of {@link ReactInstanceManager}.
*/
@ThreadConfined(UI)
public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
UiThreadUtil.assertOnUiThread();
mDefaultBackButtonImpl = defaultBackButtonImpl;
onHostResume(activity);
}
/** Use this method when the activity resumes. */
@ThreadConfined(UI)
public void onHostResume(Activity activity) {
UiThreadUtil.assertOnUiThread();
mCurrentActivity = activity;
if (mUseDeveloperSupport) {
// Resume can be called from one of two different states:
// a) when activity was paused
// b) when activity has just been created
// In case of (a) the activity is attached to window and it is ok to add new views to it or
// open dialogs. In case of (b) there is often a slight delay before such a thing happens.
// As dev support manager can add views or open dialogs immediately after it gets enabled
// (e.g. in the case when JS bundle is being fetched in background) we only want to enable
// it once we know for sure the current activity is attached.
// We check if activity is attached to window by checking if decor view is attached
final View decorView = mCurrentActivity.getWindow().getDecorView();
if (!ViewCompat.isAttachedToWindow(decorView)) {
decorView.addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
// we can drop listener now that we know the view is attached
decorView.removeOnAttachStateChangeListener(this);
mDevSupportManager.setDevSupportEnabled(true);
}
@Override
public void onViewDetachedFromWindow(View v) {
// do nothing
}
});
} else {
// activity is attached to window, we can enable dev support immediately
mDevSupportManager.setDevSupportEnabled(true);
}
}
moveToResumedLifecycleState(false);
}
/**
* Call this from {@link Activity#onDestroy()}. This notifies any listening modules so they can do
* any necessary cleanup.
*
* @deprecated use {@link #onHostDestroy(Activity)} instead
*/
@ThreadConfined(UI)
public void onHostDestroy() {
UiThreadUtil.assertOnUiThread();
if (mUseDeveloperSupport) {
mDevSupportManager.setDevSupportEnabled(false);
}
moveToBeforeCreateLifecycleState();
mCurrentActivity = null;
}
/**
* Call this from {@link Activity#onDestroy()}. This notifies any listening modules so they can do
* any necessary cleanup. If the activity being destroyed is not the current activity, no modules
* are notified.
*
* @param activity the activity being destroyed
*/
@ThreadConfined(UI)
public void onHostDestroy(Activity activity) {
if (activity == mCurrentActivity) {
onHostDestroy();
}
}
/** Destroy this React instance and the attached JS context. */
@ThreadConfined(UI)
public void destroy() {
UiThreadUtil.assertOnUiThread();
PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: Destroy");
mHasStartedDestroying = true;
if (mUseDeveloperSupport) {
mDevSupportManager.setDevSupportEnabled(false);
mDevSupportManager.stopInspector();
}
moveToBeforeCreateLifecycleState();
if (mCreateReactContextThread != null) {
mCreateReactContextThread = null;
}
mMemoryPressureRouter.destroy(mApplicationContext);
synchronized (mReactContextLock) {
if (mCurrentReactContext != null) {
mCurrentReactContext.destroy();
mCurrentReactContext = null;
}
}
mHasStartedCreatingInitialContext = false;
mCurrentActivity = null;
ResourceDrawableIdHelper.getInstance().clear();
mHasStartedDestroying = false;
synchronized (mHasStartedDestroying) {
mHasStartedDestroying.notifyAll();
}
}
private synchronized void moveToResumedLifecycleState(boolean force) {
ReactContext currentContext = getCurrentReactContext();
if (currentContext != null) {
// we currently don't have an onCreate callback so we call onResume for both transitions
if (force
|| mLifecycleState == LifecycleState.BEFORE_RESUME
|| mLifecycleState == LifecycleState.BEFORE_CREATE) {
currentContext.onHostResume(mCurrentActivity);
}
}
mLifecycleState = LifecycleState.RESUMED;
}
private synchronized void moveToBeforeResumeLifecycleState() {
ReactContext currentContext = getCurrentReactContext();
if (currentContext != null) {
if (mLifecycleState == LifecycleState.BEFORE_CREATE) {
currentContext.onHostResume(mCurrentActivity);
currentContext.onHostPause();
} else if (mLifecycleState == LifecycleState.RESUMED) {
currentContext.onHostPause();
}
}
mLifecycleState = LifecycleState.BEFORE_RESUME;
}
private synchronized void moveToBeforeCreateLifecycleState() {
ReactContext currentContext = getCurrentReactContext();
if (currentContext != null) {
if (mLifecycleState == LifecycleState.RESUMED) {
currentContext.onHostPause();
mLifecycleState = LifecycleState.BEFORE_RESUME;
}
if (mLifecycleState == LifecycleState.BEFORE_RESUME) {
currentContext.onHostDestroy();
}
}
mLifecycleState = LifecycleState.BEFORE_CREATE;
}
private synchronized void moveReactContextToCurrentLifecycleState() {
if (mLifecycleState == LifecycleState.RESUMED) {
moveToResumedLifecycleState(true);
}
}
@ThreadConfined(UI)
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
ReactContext currentContext = getCurrentReactContext();
if (currentContext != null) {
currentContext.onActivityResult(activity, requestCode, resultCode, data);
}
}
@ThreadConfined(UI)
public void onWindowFocusChange(boolean hasFocus) {
UiThreadUtil.assertOnUiThread();
ReactContext currentContext = getCurrentReactContext();
if (currentContext != null) {
currentContext.onWindowFocusChange(hasFocus);
}
}
/** Call this from {@link Activity#onConfigurationChanged()}. */
@ThreadConfined(UI)
public void onConfigurationChanged(@Nullable Configuration newConfig) {
UiThreadUtil.assertOnUiThread();
ReactContext currentContext = getCurrentReactContext();
if (currentContext != null) {
currentContext.getNativeModule(AppearanceModule.class).onConfigurationChanged();
}
}
@ThreadConfined(UI)
public void showDevOptionsDialog() {
UiThreadUtil.assertOnUiThread();
mDevSupportManager.showDevOptionsDialog();
}
private void clearReactRoot(ReactRoot reactRoot) {
reactRoot.getRootViewGroup().removeAllViews();
reactRoot.getRootViewGroup().setId(View.NO_ID);
}
/**
* 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 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(ReactRoot reactRoot) {
UiThreadUtil.assertOnUiThread();
mAttachedReactRoots.add(reactRoot);
// 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 reactRoot reactRoot is part of the attached
// reactRoot reactRoot list.
ReactContext currentContext = getCurrentReactContext();
if (mCreateReactContextThread == null && currentContext != null) {
attachRootViewToInstance(reactRoot);
}
}
/**
* 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(ReactRoot reactRoot) {
UiThreadUtil.assertOnUiThread();
synchronized (mAttachedReactRoots) {
if (mAttachedReactRoots.contains(reactRoot)) {
ReactContext currentContext = getCurrentReactContext();
mAttachedReactRoots.remove(reactRoot);
if (currentContext != null && currentContext.hasActiveCatalystInstance()) {
detachViewFromInstance(reactRoot, currentContext.getCatalystInstance());
}
}
}
}
/** Uses configured {@link ReactPackage} instances to create all view managers. */
public List