Move new bridge java stuff to OSS
Reviewed By: mhorowitz Differential Revision: D3300152 fbshipit-source-id: 9a76b10579bbfc5bde3a5094b99b64c38f4c1da9
This commit is contained in:
parent
5c6bbf7f1f
commit
3b3b46d86e
|
@ -1,22 +1,41 @@
|
|||
include_defs('//ReactAndroid/DEFS')
|
||||
|
||||
XREACT_SRCS = [
|
||||
'XReactInstanceManager.java',
|
||||
'XReactInstanceManagerImpl.java',
|
||||
]
|
||||
|
||||
DEPS = [
|
||||
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/devsupport:devsupport'),
|
||||
react_native_target('java/com/facebook/react/modules/core:core'),
|
||||
react_native_target('java/com/facebook/react/modules/debug:debug'),
|
||||
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/uimanager:uimanager'),
|
||||
react_native_dep('java/com/facebook/systrace:systrace'),
|
||||
react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
|
||||
react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
]
|
||||
|
||||
android_library(
|
||||
name = 'react',
|
||||
srcs = glob(['*.java']),
|
||||
deps = [
|
||||
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/devsupport:devsupport'),
|
||||
react_native_target('java/com/facebook/react/modules/core:core'),
|
||||
react_native_target('java/com/facebook/react/modules/debug:debug'),
|
||||
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/uimanager:uimanager'),
|
||||
react_native_dep('java/com/facebook/systrace:systrace'),
|
||||
react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
|
||||
react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
srcs = glob(['*.java'], excludes=XREACT_SRCS),
|
||||
deps = DEPS,
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
||||
|
||||
android_library(
|
||||
name = 'xreact',
|
||||
srcs = XREACT_SRCS,
|
||||
deps = DEPS + [
|
||||
':react',
|
||||
react_native_target('java/com/facebook/react/cxxbridge:bridge'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.uimanager.UIImplementationProvider;
|
||||
|
||||
public abstract class XReactInstanceManager {
|
||||
/**
|
||||
* Creates a builder that is capable of creating an instance of {@link XReactInstanceManagerImpl}.
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for {@link XReactInstanceManagerImpl}
|
||||
*/
|
||||
public static class Builder extends ReactInstanceManager.Builder {
|
||||
/**
|
||||
* Instantiates a new {@link ReactInstanceManagerImpl}.
|
||||
* Before calling {@code build}, the following must be called:
|
||||
* <ul>
|
||||
* <li> {@link #setApplication}
|
||||
* <li> {@link #setCurrentActivity} if the activity has already resumed
|
||||
* <li> {@link #setDefaultHardwareBackBtnHandler} if the activity has already resumed
|
||||
* <li> {@link #setJSBundleFile} or {@link #setJSMainModuleName}
|
||||
* </ul>
|
||||
*/
|
||||
public ReactInstanceManager build() {
|
||||
Assertions.assertCondition(
|
||||
mUseDeveloperSupport || mJSBundleFile != null,
|
||||
"JS Bundle File has to be provided when dev support is disabled");
|
||||
|
||||
Assertions.assertCondition(
|
||||
mJSMainModuleName != null || mJSBundleFile != null,
|
||||
"Either MainModuleName or JS Bundle File needs to be provided");
|
||||
|
||||
if (mUIImplementationProvider == null) {
|
||||
// create default UIImplementationProvider if the provided one is null.
|
||||
mUIImplementationProvider = new UIImplementationProvider();
|
||||
}
|
||||
|
||||
return new XReactInstanceManagerImpl(
|
||||
Assertions.assertNotNull(
|
||||
mApplication,
|
||||
"Application property has not been set with this builder"),
|
||||
mCurrentActivity,
|
||||
mDefaultHardwareBackBtnHandler,
|
||||
mJSBundleFile,
|
||||
mJSMainModuleName,
|
||||
mPackages,
|
||||
mUseDeveloperSupport,
|
||||
mBridgeIdleDebugListener,
|
||||
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
|
||||
mUIImplementationProvider,
|
||||
mNativeModuleCallExceptionHandler,
|
||||
mJSCConfig);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,865 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.CatalystInstance;
|
||||
import com.facebook.react.bridge.JavaJSExecutor;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.JavaScriptModuleRegistry;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
|
||||
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactMarker;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
|
||||
import com.facebook.react.common.ApplicationHolder;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.cxxbridge.Arguments;
|
||||
import com.facebook.react.cxxbridge.CatalystInstanceImpl;
|
||||
import com.facebook.react.cxxbridge.JSBundleLoader;
|
||||
import com.facebook.react.cxxbridge.JSCJavaScriptExecutor;
|
||||
import com.facebook.react.cxxbridge.JavaScriptExecutor;
|
||||
import com.facebook.react.cxxbridge.NativeModuleRegistry;
|
||||
import com.facebook.react.cxxbridge.ProxyJavaScriptExecutor;
|
||||
import com.facebook.react.cxxbridge.UiThreadUtil;
|
||||
import com.facebook.react.devsupport.DevServerHelper;
|
||||
import com.facebook.react.devsupport.DevSupportManager;
|
||||
import com.facebook.react.devsupport.DevSupportManagerFactory;
|
||||
import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.facebook.react.uimanager.AppRegistry;
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
||||
import com.facebook.react.uimanager.UIImplementationProvider;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.facebook.systrace.Systrace;
|
||||
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_END;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_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.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.PROCESS_PACKAGES_END;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_START;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_END;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_START;
|
||||
|
||||
/**
|
||||
* This class is managing instances of {@link CatalystInstance}. It expose 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 XReactInstanceManagerImpl} 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}.
|
||||
*/
|
||||
/* package */ class XReactInstanceManagerImpl extends ReactInstanceManager {
|
||||
|
||||
/* should only be accessed from main thread (UI thread) */
|
||||
private final List<ReactRootView> mAttachedRootViews = new ArrayList<>();
|
||||
private LifecycleState mLifecycleState;
|
||||
private @Nullable ReactContextInitParams mPendingReactContextInitParams;
|
||||
private @Nullable ReactContextInitAsyncTask mReactContextInitAsyncTask;
|
||||
|
||||
/* accessed from any thread */
|
||||
private @Nullable String mJSBundleFile; /* path to JS bundle on file system */
|
||||
private final @Nullable String mJSMainModuleName; /* path to JS bundle root on packager server */
|
||||
private final List<ReactPackage> mPackages;
|
||||
private final DevSupportManager mDevSupportManager;
|
||||
private final boolean mUseDeveloperSupport;
|
||||
private final @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
|
||||
private @Nullable volatile ReactContext mCurrentReactContext;
|
||||
private final Context mApplicationContext;
|
||||
private @Nullable DefaultHardwareBackBtnHandler mDefaultBackButtonImpl;
|
||||
private String mSourceUrl;
|
||||
private @Nullable Activity mCurrentActivity;
|
||||
private final Collection<ReactInstanceEventListener> mReactInstanceEventListeners =
|
||||
Collections.synchronizedSet(new HashSet<ReactInstanceEventListener>());
|
||||
private volatile boolean mHasStartedCreatingInitialContext = false;
|
||||
private final UIImplementationProvider mUIImplementationProvider;
|
||||
private final MemoryPressureRouter mMemoryPressureRouter;
|
||||
private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
|
||||
private final @Nullable JSCConfig mJSCConfig;
|
||||
|
||||
private final ReactInstanceDevCommandsHandler mDevInterface =
|
||||
new ReactInstanceDevCommandsHandler() {
|
||||
|
||||
@Override
|
||||
public void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) {
|
||||
XReactInstanceManagerImpl.this.onReloadWithJSDebugger(jsExecutorFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onJSBundleLoadedFromServer() {
|
||||
XReactInstanceManagerImpl.this.onJSBundleLoadedFromServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggleElementInspector() {
|
||||
XReactInstanceManagerImpl.this.toggleElementInspector();
|
||||
}
|
||||
};
|
||||
|
||||
private final DefaultHardwareBackBtnHandler mBackBtnHandler =
|
||||
new DefaultHardwareBackBtnHandler() {
|
||||
@Override
|
||||
public void invokeDefaultOnBackPressed() {
|
||||
XReactInstanceManagerImpl.this.invokeDefaultOnBackPressed();
|
||||
}
|
||||
};
|
||||
|
||||
private class ReactContextInitParams {
|
||||
private final JavaScriptExecutor.Factory mJsExecutorFactory;
|
||||
private final JSBundleLoader mJsBundleLoader;
|
||||
|
||||
public ReactContextInitParams(
|
||||
JavaScriptExecutor.Factory jsExecutorFactory,
|
||||
JSBundleLoader jsBundleLoader) {
|
||||
mJsExecutorFactory = Assertions.assertNotNull(jsExecutorFactory);
|
||||
mJsBundleLoader = Assertions.assertNotNull(jsBundleLoader);
|
||||
}
|
||||
|
||||
public JavaScriptExecutor.Factory getJsExecutorFactory() {
|
||||
return mJsExecutorFactory;
|
||||
}
|
||||
|
||||
public JSBundleLoader getJsBundleLoader() {
|
||||
return mJsBundleLoader;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Task class responsible for (re)creating react context in the background. These tasks can only
|
||||
* be executing one at time, see {@link #recreateReactContextInBackground()}.
|
||||
*/
|
||||
private final class ReactContextInitAsyncTask extends
|
||||
AsyncTask<ReactContextInitParams, Void, Result<ReactApplicationContext>> {
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (mCurrentReactContext != null) {
|
||||
tearDownReactContext(mCurrentReactContext);
|
||||
mCurrentReactContext = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
|
||||
Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
|
||||
try {
|
||||
JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create();
|
||||
return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader()));
|
||||
} catch (Exception e) {
|
||||
// Pass exception to onPostExecute() so it can be handled on the main thread
|
||||
return Result.of(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Result<ReactApplicationContext> result) {
|
||||
try {
|
||||
setupReactContext(result.get());
|
||||
} catch (Exception e) {
|
||||
mDevSupportManager.handleException(e);
|
||||
} finally {
|
||||
mReactContextInitAsyncTask = null;
|
||||
}
|
||||
|
||||
// Handle enqueued request to re-initialize react context.
|
||||
if (mPendingReactContextInitParams != null) {
|
||||
recreateReactContextInBackground(
|
||||
mPendingReactContextInitParams.getJsExecutorFactory(),
|
||||
mPendingReactContextInitParams.getJsBundleLoader());
|
||||
mPendingReactContextInitParams = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(Result<ReactApplicationContext> reactApplicationContextResult) {
|
||||
try {
|
||||
mMemoryPressureRouter.destroy(reactApplicationContextResult.get());
|
||||
} catch (Exception e) {
|
||||
FLog.w(ReactConstants.TAG, "Caught exception after cancelling react context init", e);
|
||||
} finally {
|
||||
mReactContextInitAsyncTask = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Result<T> {
|
||||
@Nullable private final T mResult;
|
||||
@Nullable private final Exception mException;
|
||||
|
||||
public static <T, U extends T> Result<T> of(U result) {
|
||||
return new Result<T>(result);
|
||||
}
|
||||
|
||||
public static <T> Result<T> of(Exception exception) {
|
||||
return new Result<>(exception);
|
||||
}
|
||||
|
||||
private Result(T result) {
|
||||
mException = null;
|
||||
mResult = result;
|
||||
}
|
||||
|
||||
private Result(Exception exception) {
|
||||
mException = exception;
|
||||
mResult = null;
|
||||
}
|
||||
|
||||
public T get() throws Exception {
|
||||
if (mException != null) {
|
||||
throw mException;
|
||||
}
|
||||
|
||||
Assertions.assertNotNull(mResult);
|
||||
|
||||
return mResult;
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ XReactInstanceManagerImpl(
|
||||
Context applicationContext,
|
||||
@Nullable Activity currentActivity,
|
||||
@Nullable DefaultHardwareBackBtnHandler defaultHardwareBackBtnHandler,
|
||||
@Nullable String jsBundleFile,
|
||||
@Nullable String jsMainModuleName,
|
||||
List<ReactPackage> packages,
|
||||
boolean useDeveloperSupport,
|
||||
@Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
|
||||
LifecycleState initialLifecycleState,
|
||||
UIImplementationProvider uiImplementationProvider,
|
||||
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
|
||||
@Nullable JSCConfig jscConfig) {
|
||||
initializeSoLoaderIfNecessary(applicationContext);
|
||||
|
||||
// TODO(9577825): remove this
|
||||
ApplicationHolder.setApplication((Application) applicationContext.getApplicationContext());
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(applicationContext);
|
||||
|
||||
mApplicationContext = applicationContext;
|
||||
mCurrentActivity = currentActivity;
|
||||
mDefaultBackButtonImpl = defaultHardwareBackBtnHandler;
|
||||
mJSBundleFile = jsBundleFile;
|
||||
mJSMainModuleName = jsMainModuleName;
|
||||
mPackages = packages;
|
||||
mUseDeveloperSupport = useDeveloperSupport;
|
||||
mDevSupportManager = DevSupportManagerFactory.create(
|
||||
applicationContext,
|
||||
mDevInterface,
|
||||
mJSMainModuleName,
|
||||
useDeveloperSupport);
|
||||
mBridgeIdleDebugListener = bridgeIdleDebugListener;
|
||||
mLifecycleState = initialLifecycleState;
|
||||
mUIImplementationProvider = uiImplementationProvider;
|
||||
mMemoryPressureRouter = new MemoryPressureRouter(applicationContext);
|
||||
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
|
||||
mJSCConfig = jscConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DevSupportManager getDevSupportManager() {
|
||||
return mDevSupportManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemoryPressureRouter getMemoryPressureRouter() {
|
||||
return mMemoryPressureRouter;
|
||||
}
|
||||
|
||||
private static void initializeSoLoaderIfNecessary(Context applicationContext) {
|
||||
// Call SoLoader.initialize here, this is required for apps that does not use exopackage and
|
||||
// does not use SoLoader for loading other native code except from the one used by React Native
|
||||
// This way we don't need to require others to have additional initialization code and to
|
||||
// subclass android.app.Application.
|
||||
|
||||
// Method SoLoader.init is idempotent, so if you wish to use native exopackage, just call
|
||||
// SoLoader.init with appropriate args before initializing XReactInstanceManagerImpl
|
||||
SoLoader.init(applicationContext, /* native exopackage */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger react context initialization asynchronously in a background async task. This enables
|
||||
* applications to pre-load the application JS, and execute global code before
|
||||
* {@link ReactRootView} is available and measured. This should only be called the first time the
|
||||
* application is set up, which is enforced to keep developers from accidentally creating their
|
||||
* application multiple times without realizing it.
|
||||
*
|
||||
* Called from UI thread.
|
||||
*/
|
||||
@Override
|
||||
public void createReactContextInBackground() {
|
||||
Assertions.assertCondition(
|
||||
!mHasStartedCreatingInitialContext,
|
||||
"createReactContextInBackground should only be called when creating the react " +
|
||||
"application for the first time. When reloading JS, e.g. from a new file, explicitly" +
|
||||
"use recreateReactContextInBackground");
|
||||
|
||||
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.
|
||||
*/
|
||||
public void recreateReactContextInBackground() {
|
||||
Assertions.assertCondition(
|
||||
mHasStartedCreatingInitialContext,
|
||||
"recreateReactContextInBackground should only be called after the initial " +
|
||||
"createReactContextInBackground call.");
|
||||
recreateReactContextInBackgroundInner();
|
||||
}
|
||||
|
||||
private void recreateReactContextInBackgroundInner() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
if (mUseDeveloperSupport && mJSMainModuleName != null) {
|
||||
if (mDevSupportManager.hasUpToDateJSBundleInCache()) {
|
||||
// If there is a up-to-date bundle downloaded from server, always use that
|
||||
onJSBundleLoadedFromServer();
|
||||
} else if (mJSBundleFile == null) {
|
||||
mDevSupportManager.handleReloadJS();
|
||||
} else {
|
||||
mDevSupportManager.isPackagerRunning(
|
||||
new DevServerHelper.PackagerStatusCallback() {
|
||||
@Override
|
||||
public void onPackagerStatusFetched(final boolean packagerIsRunning) {
|
||||
UiThreadUtil.runOnUiThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (packagerIsRunning) {
|
||||
mDevSupportManager.handleReloadJS();
|
||||
} else {
|
||||
recreateReactContextInBackgroundFromBundleFile();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
recreateReactContextInBackgroundFromBundleFile();
|
||||
}
|
||||
|
||||
private void recreateReactContextInBackgroundFromBundleFile() {
|
||||
recreateReactContextInBackground(
|
||||
new JSCJavaScriptExecutor.Factory(
|
||||
mJSCConfig == null ? new WritableNativeMap() : mJSCConfig.getConfigMap()),
|
||||
JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
ReactContext reactContext = mCurrentReactContext;
|
||||
if (mCurrentReactContext == null) {
|
||||
// Invoke without round trip to JS.
|
||||
FLog.w(ReactConstants.TAG, "Instance detached from instance manager");
|
||||
invokeDefaultOnBackPressed();
|
||||
} else {
|
||||
DeviceEventManagerModule deviceEventManagerModule =
|
||||
Assertions.assertNotNull(reactContext).getNativeModule(DeviceEventManagerModule.class);
|
||||
deviceEventManagerModule.emitHardwareBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeDefaultOnBackPressed() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
if (mDefaultBackButtonImpl != null) {
|
||||
mDefaultBackButtonImpl.invokeDefaultOnBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleElementInspector() {
|
||||
if (mCurrentReactContext != null) {
|
||||
mCurrentReactContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit("toggleElementInspector", null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
mDefaultBackButtonImpl = null;
|
||||
if (mUseDeveloperSupport) {
|
||||
mDevSupportManager.setDevSupportEnabled(false);
|
||||
}
|
||||
|
||||
moveToBeforeResumeLifecycleState();
|
||||
mCurrentActivity = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* XReactInstanceManagerImpl}, 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 XReactInstanceManagerImpl}.
|
||||
*/
|
||||
@Override
|
||||
public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
mDefaultBackButtonImpl = defaultBackButtonImpl;
|
||||
if (mUseDeveloperSupport) {
|
||||
mDevSupportManager.setDevSupportEnabled(true);
|
||||
}
|
||||
|
||||
mCurrentActivity = activity;
|
||||
moveToResumedLifecycleState(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
if (mUseDeveloperSupport) {
|
||||
mDevSupportManager.setDevSupportEnabled(false);
|
||||
}
|
||||
|
||||
moveToBeforeCreateLifecycleState();
|
||||
mCurrentActivity = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
if (mUseDeveloperSupport) {
|
||||
mDevSupportManager.setDevSupportEnabled(false);
|
||||
}
|
||||
|
||||
moveToBeforeCreateLifecycleState();
|
||||
|
||||
if (mReactContextInitAsyncTask != null) {
|
||||
mReactContextInitAsyncTask.cancel(true);
|
||||
}
|
||||
|
||||
mMemoryPressureRouter.destroy(mApplicationContext);
|
||||
|
||||
if (mCurrentReactContext != null) {
|
||||
mCurrentReactContext.destroy();
|
||||
mCurrentReactContext = null;
|
||||
mHasStartedCreatingInitialContext = false;
|
||||
}
|
||||
mCurrentActivity = null;
|
||||
}
|
||||
|
||||
private void moveToResumedLifecycleState(boolean force) {
|
||||
if (mCurrentReactContext != 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) {
|
||||
mCurrentReactContext.onHostResume(mCurrentActivity);
|
||||
}
|
||||
}
|
||||
mLifecycleState = LifecycleState.RESUMED;
|
||||
}
|
||||
|
||||
private void moveToBeforeResumeLifecycleState() {
|
||||
if (mCurrentReactContext != null) {
|
||||
if (mLifecycleState == LifecycleState.BEFORE_CREATE) {
|
||||
mCurrentReactContext.onHostResume(mCurrentActivity);
|
||||
mCurrentReactContext.onHostPause();
|
||||
} else if (mLifecycleState == LifecycleState.RESUMED) {
|
||||
mCurrentReactContext.onHostPause();
|
||||
}
|
||||
}
|
||||
mLifecycleState = LifecycleState.BEFORE_RESUME;
|
||||
}
|
||||
|
||||
private void moveToBeforeCreateLifecycleState() {
|
||||
if (mCurrentReactContext != null) {
|
||||
if (mLifecycleState == LifecycleState.RESUMED) {
|
||||
mCurrentReactContext.onHostPause();
|
||||
mLifecycleState = LifecycleState.BEFORE_RESUME;
|
||||
}
|
||||
if (mLifecycleState == LifecycleState.BEFORE_RESUME) {
|
||||
mCurrentReactContext.onHostDestroy();
|
||||
}
|
||||
}
|
||||
mLifecycleState = LifecycleState.BEFORE_CREATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (mCurrentReactContext != null) {
|
||||
mCurrentReactContext.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDevOptionsDialog() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
mDevSupportManager.showDevOptionsDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL where the last bundle was loaded from.
|
||||
*/
|
||||
@Override
|
||||
public String getSourceUrl() {
|
||||
return Assertions.assertNotNull(mSourceUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach given {@param rootView} 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
|
||||
* be re-attached.
|
||||
*/
|
||||
@Override
|
||||
public void attachMeasuredRootView(ReactRootView rootView) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
mAttachedRootViews.add(rootView);
|
||||
|
||||
// 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.
|
||||
if (mReactContextInitAsyncTask == null && mCurrentReactContext != null) {
|
||||
attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* first call.
|
||||
*/
|
||||
@Override
|
||||
public void detachRootView(ReactRootView rootView) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
if (mAttachedRootViews.remove(rootView)) {
|
||||
if (mCurrentReactContext != null && mCurrentReactContext.hasActiveCatalystInstance()) {
|
||||
detachViewFromInstance(rootView, mCurrentReactContext.getCatalystInstance());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses configured {@link ReactPackage} instances to create all view managers
|
||||
*/
|
||||
@Override
|
||||
public List<ViewManager> createAllViewManagers(
|
||||
ReactApplicationContext catalystApplicationContext) {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createAllViewManagers");
|
||||
try {
|
||||
List<ViewManager> allViewManagers = new ArrayList<>();
|
||||
for (ReactPackage reactPackage : mPackages) {
|
||||
allViewManagers.addAll(reactPackage.createViewManagers(catalystApplicationContext));
|
||||
}
|
||||
return allViewManagers;
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addReactInstanceEventListener(ReactInstanceEventListener listener) {
|
||||
mReactInstanceEventListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeReactInstanceEventListener(ReactInstanceEventListener listener) {
|
||||
mReactInstanceEventListeners.remove(listener);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public @Nullable ReactContext getCurrentReactContext() {
|
||||
return mCurrentReactContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifecycleState getLifecycleState() {
|
||||
return mLifecycleState;
|
||||
}
|
||||
|
||||
private void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) {
|
||||
recreateReactContextInBackground(
|
||||
new ProxyJavaScriptExecutor.Factory(jsExecutorFactory),
|
||||
JSBundleLoader.createRemoteDebuggerBundleLoader(
|
||||
mDevSupportManager.getJSBundleURLForRemoteDebugging(),
|
||||
mDevSupportManager.getSourceUrl()));
|
||||
}
|
||||
|
||||
private void onJSBundleLoadedFromServer() {
|
||||
recreateReactContextInBackground(
|
||||
new JSCJavaScriptExecutor.Factory(
|
||||
mJSCConfig == null ? new WritableNativeMap() : mJSCConfig.getConfigMap()),
|
||||
JSBundleLoader.createCachedBundleFromNetworkLoader(
|
||||
mDevSupportManager.getSourceUrl(),
|
||||
mDevSupportManager.getDownloadedJSBundleFile()));
|
||||
}
|
||||
|
||||
private void recreateReactContextInBackground(
|
||||
JavaScriptExecutor.Factory jsExecutorFactory,
|
||||
JSBundleLoader jsBundleLoader) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
ReactContextInitParams initParams =
|
||||
new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
|
||||
if (mReactContextInitAsyncTask == null) {
|
||||
// No background task to create react context is currently running, create and execute one.
|
||||
mReactContextInitAsyncTask = new ReactContextInitAsyncTask();
|
||||
mReactContextInitAsyncTask.execute(initParams);
|
||||
} else {
|
||||
// Background task is currently running, queue up most recent init params to recreate context
|
||||
// once task completes.
|
||||
mPendingReactContextInitParams = initParams;
|
||||
}
|
||||
}
|
||||
|
||||
private void setupReactContext(ReactApplicationContext reactContext) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
Assertions.assertCondition(mCurrentReactContext == null);
|
||||
mCurrentReactContext = Assertions.assertNotNull(reactContext);
|
||||
CatalystInstance catalystInstance =
|
||||
Assertions.assertNotNull(reactContext.getCatalystInstance());
|
||||
|
||||
catalystInstance.initialize();
|
||||
mDevSupportManager.onNewReactContextCreated(reactContext);
|
||||
mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
|
||||
moveReactContextToCurrentLifecycleState();
|
||||
|
||||
for (ReactRootView rootView : mAttachedRootViews) {
|
||||
attachMeasuredRootViewToInstance(rootView, catalystInstance);
|
||||
}
|
||||
|
||||
ReactInstanceEventListener[] listeners =
|
||||
new ReactInstanceEventListener[mReactInstanceEventListeners.size()];
|
||||
listeners = mReactInstanceEventListeners.toArray(listeners);
|
||||
|
||||
for (ReactInstanceEventListener listener : listeners) {
|
||||
listener.onReactContextInitialized(reactContext);
|
||||
}
|
||||
}
|
||||
|
||||
private void attachMeasuredRootViewToInstance(
|
||||
ReactRootView rootView,
|
||||
CatalystInstance catalystInstance) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// Reset view content as it's going to be populated by the application content from JS
|
||||
rootView.removeAllViews();
|
||||
rootView.setId(View.NO_ID);
|
||||
|
||||
UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
|
||||
int rootTag = uiManagerModule.addMeasuredRootView(rootView);
|
||||
@Nullable Bundle launchOptions = rootView.getLaunchOptions();
|
||||
WritableMap initialProps = Arguments.makeNativeMap(launchOptions);
|
||||
String jsAppModuleName = rootView.getJSModuleName();
|
||||
|
||||
WritableNativeMap appParams = new WritableNativeMap();
|
||||
appParams.putDouble("rootTag", rootTag);
|
||||
appParams.putMap("initialProps", initialProps);
|
||||
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
|
||||
}
|
||||
|
||||
private void detachViewFromInstance(
|
||||
ReactRootView rootView,
|
||||
CatalystInstance catalystInstance) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
catalystInstance.getJSModule(AppRegistry.class)
|
||||
.unmountApplicationComponentAtRootTag(rootView.getId());
|
||||
}
|
||||
|
||||
private void tearDownReactContext(ReactContext reactContext) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
if (mLifecycleState == LifecycleState.RESUMED) {
|
||||
reactContext.onHostPause();
|
||||
}
|
||||
for (ReactRootView rootView : mAttachedRootViews) {
|
||||
detachViewFromInstance(rootView, reactContext.getCatalystInstance());
|
||||
}
|
||||
reactContext.destroy();
|
||||
mDevSupportManager.onReactInstanceDestroyed(reactContext);
|
||||
mMemoryPressureRouter.removeMemoryPressureListener(reactContext.getCatalystInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
|
||||
*/
|
||||
private ReactApplicationContext createReactContext(
|
||||
JavaScriptExecutor jsExecutor,
|
||||
JSBundleLoader jsBundleLoader) {
|
||||
FLog.i(ReactConstants.TAG, "Creating react context.");
|
||||
ReactMarker.logMarker(CREATE_REACT_CONTEXT_START);
|
||||
mSourceUrl = jsBundleLoader.getSourceUrl();
|
||||
NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
|
||||
JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();
|
||||
|
||||
ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
|
||||
if (mUseDeveloperSupport) {
|
||||
reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
|
||||
}
|
||||
|
||||
ReactMarker.logMarker(PROCESS_PACKAGES_START);
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
"createAndProcessCoreModulesPackage");
|
||||
try {
|
||||
CoreModulesPackage coreModulesPackage =
|
||||
new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
|
||||
processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
|
||||
// TODO(6818138): Solve use-case of native/js modules overriding
|
||||
for (ReactPackage reactPackage : mPackages) {
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
"createAndProcessCustomReactPackage");
|
||||
try {
|
||||
processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
ReactMarker.logMarker(PROCESS_PACKAGES_END);
|
||||
|
||||
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_START);
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
|
||||
NativeModuleRegistry nativeModuleRegistry;
|
||||
try {
|
||||
nativeModuleRegistry = nativeRegistryBuilder.build();
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
|
||||
}
|
||||
|
||||
NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
|
||||
? mNativeModuleCallExceptionHandler
|
||||
: mDevSupportManager;
|
||||
CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
|
||||
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
|
||||
.setJSExecutor(jsExecutor)
|
||||
.setRegistry(nativeModuleRegistry)
|
||||
.setJSModuleRegistry(jsModulesBuilder.build())
|
||||
.setJSBundleLoader(jsBundleLoader)
|
||||
.setNativeModuleCallExceptionHandler(exceptionHandler);
|
||||
|
||||
ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
|
||||
// CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
|
||||
final CatalystInstance catalystInstance;
|
||||
try {
|
||||
catalystInstance = catalystInstanceBuilder.build();
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
|
||||
}
|
||||
|
||||
if (mBridgeIdleDebugListener != null) {
|
||||
catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
|
||||
}
|
||||
|
||||
reactContext.initializeWithInstance(catalystInstance);
|
||||
|
||||
ReactMarker.logMarker(RUN_JS_BUNDLE_START);
|
||||
catalystInstance.getReactQueueConfiguration().getJSQueueThread().runOnQueue(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
|
||||
try {
|
||||
catalystInstance.runJSBundle();
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
ReactMarker.logMarker(RUN_JS_BUNDLE_END);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return reactContext;
|
||||
}
|
||||
|
||||
private void processPackage(
|
||||
ReactPackage reactPackage,
|
||||
ReactApplicationContext reactContext,
|
||||
NativeModuleRegistry.Builder nativeRegistryBuilder,
|
||||
JavaScriptModuleRegistry.Builder jsModulesBuilder) {
|
||||
for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {
|
||||
nativeRegistryBuilder.add(nativeModule);
|
||||
}
|
||||
for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()) {
|
||||
jsModulesBuilder.add(jsModuleClass);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveReactContextToCurrentLifecycleState() {
|
||||
if (mLifecycleState == LifecycleState.RESUMED) {
|
||||
moveToResumedLifecycleState(true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.ReadableType;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
public class Arguments {
|
||||
private static Object makeNativeObject(Object object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
} else if (object instanceof Float ||
|
||||
object instanceof Long ||
|
||||
object instanceof Byte ||
|
||||
object instanceof Short) {
|
||||
return new Double(((Number) object).doubleValue());
|
||||
} else if (object.getClass().isArray()) {
|
||||
return makeNativeArray(object);
|
||||
} else if (object instanceof List) {
|
||||
return makeNativeArray((List) object);
|
||||
} else if (object instanceof Map) {
|
||||
return makeNativeMap((Map<String, Object>) object);
|
||||
} else if (object instanceof Bundle) {
|
||||
return makeNativeMap((Bundle) object);
|
||||
} else {
|
||||
// Boolean, Integer, Double, String, WritableNativeArray, WritableNativeMap
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts a List into a NativeArray. The data types supported
|
||||
* are boolean, int, float, double, and String. List, Map, and Bundle
|
||||
* objects, as well as arrays, containing values of the above types and/or
|
||||
* null, or any recursive arrangement of these, are also supported. The best
|
||||
* way to think of this is a way to generate a Java representation of a json
|
||||
* list, from Java types which have a natural representation in json.
|
||||
*/
|
||||
public static WritableNativeArray makeNativeArray(List objects) {
|
||||
WritableNativeArray nativeArray = new WritableNativeArray();
|
||||
if (objects == null) {
|
||||
return nativeArray;
|
||||
}
|
||||
for (Object elem : objects) {
|
||||
elem = makeNativeObject(elem);
|
||||
if (elem == null) {
|
||||
nativeArray.pushNull();
|
||||
} else if (elem instanceof Boolean) {
|
||||
nativeArray.pushBoolean((Boolean) elem);
|
||||
} else if (elem instanceof Integer) {
|
||||
nativeArray.pushInt((Integer) elem);
|
||||
} else if (elem instanceof Double) {
|
||||
nativeArray.pushDouble((Double) elem);
|
||||
} else if (elem instanceof String) {
|
||||
nativeArray.pushString((String) elem);
|
||||
} else if (elem instanceof WritableNativeArray) {
|
||||
nativeArray.pushArray((WritableNativeArray) elem);
|
||||
} else if (elem instanceof WritableNativeMap) {
|
||||
nativeArray.pushMap((WritableNativeMap) elem);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not convert " + elem.getClass());
|
||||
}
|
||||
}
|
||||
return nativeArray;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This overload is like the above, but uses reflection to operate on any
|
||||
* primitive or object type.
|
||||
*/
|
||||
public static <T> WritableNativeArray makeNativeArray(final Object objects) {
|
||||
if (objects == null) {
|
||||
return new WritableNativeArray();
|
||||
}
|
||||
// No explicit check for objects's type here. If it's not an array, the
|
||||
// Array methods will throw IllegalArgumentException.
|
||||
return makeNativeArray(new AbstractList() {
|
||||
public int size() {
|
||||
return Array.getLength(objects);
|
||||
}
|
||||
public Object get(int index) {
|
||||
return Array.get(objects, index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void addEntry(WritableNativeMap nativeMap, String key, Object value) {
|
||||
value = makeNativeObject(value);
|
||||
if (value == null) {
|
||||
nativeMap.putNull(key);
|
||||
} else if (value instanceof Boolean) {
|
||||
nativeMap.putBoolean(key, (Boolean) value);
|
||||
} else if (value instanceof Integer) {
|
||||
nativeMap.putInt(key, (Integer) value);
|
||||
} else if (value instanceof Number) {
|
||||
nativeMap.putDouble(key, ((Number) value).doubleValue());
|
||||
} else if (value instanceof String) {
|
||||
nativeMap.putString(key, (String) value);
|
||||
} else if (value instanceof WritableNativeArray) {
|
||||
nativeMap.putArray(key, (WritableNativeArray) value);
|
||||
} else if (value instanceof WritableNativeMap) {
|
||||
nativeMap.putMap(key, (WritableNativeMap) value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not convert " + value.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts a Map into a NativeMap. Value types are supported as
|
||||
* with makeNativeArray. The best way to think of this is a way to generate
|
||||
* a Java representation of a json object, from Java types which have a
|
||||
* natural representation in json.
|
||||
*/
|
||||
public static WritableNativeMap makeNativeMap(Map<String, Object> objects) {
|
||||
WritableNativeMap nativeMap = new WritableNativeMap();
|
||||
if (objects == null) {
|
||||
return nativeMap;
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : objects.entrySet()) {
|
||||
addEntry(nativeMap, entry.getKey(), entry.getValue());
|
||||
}
|
||||
return nativeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like the above, but takes a Bundle instead of a Map.
|
||||
*/
|
||||
public static WritableNativeMap makeNativeMap(Bundle bundle) {
|
||||
WritableNativeMap nativeMap = new WritableNativeMap();
|
||||
if (bundle == null) {
|
||||
return nativeMap;
|
||||
}
|
||||
for (String key: bundle.keySet()) {
|
||||
addEntry(nativeMap, key, bundle.get(key));
|
||||
}
|
||||
return nativeMap;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
include_defs('//ReactAndroid/DEFS')
|
||||
|
||||
android_library(
|
||||
name = 'bridge',
|
||||
srcs = glob(['**/*.java']),
|
||||
exported_deps = [
|
||||
react_native_dep('java/com/facebook/jni:jni'),
|
||||
react_native_dep('java/com/facebook/proguard/annotations:annotations'),
|
||||
],
|
||||
proguard_config = 'bridge.pro',
|
||||
deps = [
|
||||
'//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
|
||||
react_native_dep('java/com/facebook/systrace:systrace'),
|
||||
react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
|
||||
# TODO mhorowitz:
|
||||
# java/com/facebook/catalyst/js/react-native-github/ReactAndroid/src/main/java/com/facebook/react/bridge/
|
||||
# lacks a similar dependency to this. This means that the
|
||||
# loadLibrary calls in it are not guaranteed to succeed. This is
|
||||
# kind of a mess for the jni/jni-internal stuff. In theory, we
|
||||
# should be creating -internal android_library rules, too. In
|
||||
# practice, since these are resolved at runtime, putting the
|
||||
# dependency in the app works, too. gross.
|
||||
# '//native/react/jni:jni-internal',
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jackson:core'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
react_native_dep('third-party/java/okhttp:okhttp3-ws'),
|
||||
react_native_target('java/com/facebook/react/bridge:bridge'),
|
||||
react_native_target('java/com/facebook/react/common:common'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
||||
|
||||
project_config(
|
||||
src_target = ':bridge',
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.*;
|
||||
|
||||
import static com.facebook.react.bridge.Arguments.*;
|
||||
|
||||
/**
|
||||
* Callback impl that calls directly into the cxxbridge. Created from C++.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class CallbackImpl implements Callback {
|
||||
@DoNotStrip
|
||||
private final HybridData mHybridData;
|
||||
|
||||
@DoNotStrip
|
||||
private CallbackImpl(HybridData hybridData) {
|
||||
mHybridData = hybridData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Object... args) {
|
||||
nativeInvoke(fromJavaArgs(args));
|
||||
}
|
||||
|
||||
private native void nativeInvoke(NativeArray arguments);
|
||||
}
|
|
@ -0,0 +1,461 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.react.bridge.CatalystInstance;
|
||||
import com.facebook.react.bridge.ExecutorToken;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.JavaScriptModuleRegistry;
|
||||
import com.facebook.react.bridge.MemoryPressure;
|
||||
import com.facebook.react.bridge.NativeArray;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
|
||||
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
|
||||
import com.facebook.react.bridge.queue.MessageQueueThread;
|
||||
import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl;
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import com.facebook.systrace.TraceListener;
|
||||
|
||||
/**
|
||||
* This provides an implementation of the public CatalystInstance instance. It is public because
|
||||
* it is built by XReactInstanceManager which is in a different package.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class CatalystInstanceImpl implements CatalystInstance {
|
||||
|
||||
/* package */ static final String REACT_NATIVE_LIB = "reactnativejnifb";
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
private static final int BRIDGE_SETUP_TIMEOUT_MS = 30000;
|
||||
private static final int LOAD_JS_BUNDLE_TIMEOUT_MS = 30000;
|
||||
|
||||
private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1);
|
||||
|
||||
// Access from any thread
|
||||
private final ReactQueueConfigurationImpl mReactQueueConfiguration;
|
||||
private final CopyOnWriteArrayList<NotThreadSafeBridgeIdleDebugListener> mBridgeIdleListeners;
|
||||
private final AtomicInteger mPendingJSCalls = new AtomicInteger(0);
|
||||
private final String mJsPendingCallsTitleForTrace =
|
||||
"pending_js_calls_instance" + sNextInstanceIdForTrace.getAndIncrement();
|
||||
private volatile boolean mDestroyed = false;
|
||||
private final TraceListener mTraceListener;
|
||||
private final JavaScriptModuleRegistry mJSModuleRegistry;
|
||||
private final JSBundleLoader mJSBundleLoader;
|
||||
private ExecutorToken mMainExecutorToken;
|
||||
|
||||
private final NativeModuleRegistry mJavaRegistry;
|
||||
private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
|
||||
private boolean mInitialized = false;
|
||||
|
||||
private boolean mJSBundleHasLoaded;
|
||||
|
||||
// C++ parts
|
||||
private final HybridData mHybridData;
|
||||
private native static HybridData initHybrid();
|
||||
|
||||
private CatalystInstanceImpl(
|
||||
final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
|
||||
final JavaScriptExecutor jsExecutor,
|
||||
final NativeModuleRegistry registry,
|
||||
final JavaScriptModuleRegistry jsModuleRegistry,
|
||||
final JSBundleLoader jsBundleLoader,
|
||||
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
|
||||
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
|
||||
mHybridData = initHybrid();
|
||||
|
||||
mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
|
||||
ReactQueueConfigurationSpec,
|
||||
new NativeExceptionHandler());
|
||||
mBridgeIdleListeners = new CopyOnWriteArrayList<>();
|
||||
mJavaRegistry = registry;
|
||||
mJSModuleRegistry = jsModuleRegistry;
|
||||
mJSBundleLoader = jsBundleLoader;
|
||||
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
|
||||
mTraceListener = new JSProfilerTraceListener(this);
|
||||
|
||||
initializeBridge(
|
||||
new BridgeCallback(this),
|
||||
jsExecutor,
|
||||
mReactQueueConfiguration.getJSQueueThread(),
|
||||
mReactQueueConfiguration.getNativeModulesQueueThread(),
|
||||
mJavaRegistry.getModuleRegistryHolder(this));
|
||||
mMainExecutorToken = getMainExecutorToken();
|
||||
}
|
||||
|
||||
private static class BridgeCallback implements ReactCallback {
|
||||
// We do this so the callback doesn't keep the CatalystInstanceImpl alive.
|
||||
// In this case, the callback is held in C++ code, so the GC can't see it
|
||||
// and determine there's an inaccessible cycle.
|
||||
private final WeakReference<CatalystInstanceImpl> mOuter;
|
||||
|
||||
public BridgeCallback(CatalystInstanceImpl outer) {
|
||||
mOuter = new WeakReference<CatalystInstanceImpl>(outer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchComplete() {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.mJavaRegistry.onBatchComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementPendingJSCalls() {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.incrementPendingJSCalls();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrementPendingJSCalls() {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.decrementPendingJSCalls();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNativeException(Exception e) {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.onNativeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private native void initializeBridge(ReactCallback callback,
|
||||
JavaScriptExecutor jsExecutor,
|
||||
MessageQueueThread jsQueue,
|
||||
MessageQueueThread moduleQueue,
|
||||
ModuleRegistryHolder registryHolder);
|
||||
|
||||
/* package */ native void loadScriptFromAssets(AssetManager assetManager, String assetURL);
|
||||
/* package */ native void loadScriptFromFile(String fileName, String sourceURL);
|
||||
|
||||
@Override
|
||||
public void runJSBundle() {
|
||||
Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
|
||||
mJSBundleHasLoaded = true;
|
||||
// incrementPendingJSCalls();
|
||||
mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
|
||||
// This is registered after JS starts since it makes a JS call
|
||||
Systrace.registerListener(mTraceListener);
|
||||
}
|
||||
|
||||
private native void callJSFunction(
|
||||
ExecutorToken token,
|
||||
String module,
|
||||
String method,
|
||||
NativeArray arguments,
|
||||
String tracingName);
|
||||
|
||||
@Override
|
||||
public void callFunction(
|
||||
ExecutorToken executorToken,
|
||||
final String module,
|
||||
final String method,
|
||||
final NativeArray arguments,
|
||||
final String tracingName) {
|
||||
if (mDestroyed) {
|
||||
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
|
||||
return;
|
||||
}
|
||||
|
||||
callJSFunction(executorToken, module, method, arguments, tracingName);
|
||||
}
|
||||
|
||||
private native void callJSCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments);
|
||||
|
||||
@Override
|
||||
public void invokeCallback(ExecutorToken executorToken, final int callbackID, final NativeArray arguments) {
|
||||
if (mDestroyed) {
|
||||
FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
|
||||
return;
|
||||
}
|
||||
|
||||
callJSCallback(executorToken, callbackID, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration
|
||||
* (besides the UI thread) to finish running. Must be called from the UI thread so that we can
|
||||
* fully shut down other threads.
|
||||
*/
|
||||
@Override
|
||||
public void destroy() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
if (mDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: tell all APIs to shut down
|
||||
mDestroyed = true;
|
||||
mHybridData.resetNative();
|
||||
mJavaRegistry.notifyCatalystInstanceDestroy();
|
||||
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
|
||||
if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
|
||||
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
|
||||
listener.onTransitionToBridgeIdle();
|
||||
}
|
||||
}
|
||||
|
||||
// This is a noop if the listener was not yet registered.
|
||||
Systrace.unregisterListener(mTraceListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyed() {
|
||||
return mDestroyed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all the native modules
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public void initialize() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
Assertions.assertCondition(
|
||||
!mInitialized,
|
||||
"This catalyst instance has already been initialized");
|
||||
mInitialized = true;
|
||||
mJavaRegistry.notifyCatalystInstanceInitialized();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactQueueConfiguration getReactQueueConfiguration() {
|
||||
return mReactQueueConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
|
||||
return getJSModule(mMainExecutorToken, jsInterface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
|
||||
return Assertions.assertNotNull(mJSModuleRegistry)
|
||||
.getJavaScriptModule(this, executorToken, jsInterface);
|
||||
}
|
||||
|
||||
private native ExecutorToken getMainExecutorToken();
|
||||
|
||||
@Override
|
||||
public <T extends NativeModule> boolean hasNativeModule(Class<T> nativeModuleInterface) {
|
||||
return mJavaRegistry.hasModule(nativeModuleInterface);
|
||||
}
|
||||
|
||||
// This is only ever called with UIManagerModule or CurrentViewerModule.
|
||||
@Override
|
||||
public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
|
||||
return mJavaRegistry.getModule(nativeModuleInterface);
|
||||
}
|
||||
|
||||
// This is only used by com.facebook.react.modules.common.ModuleDataCleaner
|
||||
@Override
|
||||
public Collection<NativeModule> getNativeModules() {
|
||||
return mJavaRegistry.getAllModules();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMemoryPressure(MemoryPressure level) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a idle listener for this Catalyst instance. The listener will receive notifications
|
||||
* whenever the bridge transitions from idle to busy and vice-versa, where the busy state is
|
||||
* defined as there being some non-zero number of calls to JS that haven't resolved via a
|
||||
* onBatchComplete call. The listener should be purely passive and not affect application logic.
|
||||
*/
|
||||
@Override
|
||||
public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
|
||||
mBridgeIdleListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a NotThreadSafeBridgeIdleDebugListener previously added with
|
||||
* {@link #addBridgeIdleDebugListener}
|
||||
*/
|
||||
@Override
|
||||
public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
|
||||
mBridgeIdleListeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public native void setGlobalVariable(String propName, String jsonValue);
|
||||
|
||||
// TODO mhorowitz: add mDestroyed checks to the next three methods
|
||||
|
||||
@Override
|
||||
public native boolean supportsProfiling();
|
||||
|
||||
@Override
|
||||
public native void startProfiler(String title);
|
||||
|
||||
@Override
|
||||
public native void stopProfiler(String title, String filename);
|
||||
|
||||
private void incrementPendingJSCalls() {
|
||||
int oldPendingCalls = mPendingJSCalls.getAndIncrement();
|
||||
boolean wasIdle = oldPendingCalls == 0;
|
||||
Systrace.traceCounter(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
mJsPendingCallsTitleForTrace,
|
||||
oldPendingCalls + 1);
|
||||
if (wasIdle && !mBridgeIdleListeners.isEmpty()) {
|
||||
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
|
||||
listener.onTransitionToBridgeBusy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void decrementPendingJSCalls() {
|
||||
int newPendingCalls = mPendingJSCalls.decrementAndGet();
|
||||
// TODO(9604406): handle case of web workers injecting messages to main thread
|
||||
//Assertions.assertCondition(newPendingCalls >= 0);
|
||||
boolean isNowIdle = newPendingCalls == 0;
|
||||
Systrace.traceCounter(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
mJsPendingCallsTitleForTrace,
|
||||
newPendingCalls);
|
||||
|
||||
if (isNowIdle && !mBridgeIdleListeners.isEmpty()) {
|
||||
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
|
||||
listener.onTransitionToBridgeIdle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onNativeException(Exception e) {
|
||||
mNativeModuleCallExceptionHandler.handleException(e);
|
||||
mReactQueueConfiguration.getUIQueueThread().runOnQueue(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class NativeExceptionHandler implements QueueThreadExceptionHandler {
|
||||
@Override
|
||||
public void handleException(Exception e) {
|
||||
// Any Exception caught here is because of something in JS. Even if it's a bug in the
|
||||
// framework/native code, it was triggered by JS and theoretically since we were able
|
||||
// to set up the bridge, JS could change its logic, reload, and not trigger that crash.
|
||||
onNativeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class JSProfilerTraceListener implements TraceListener {
|
||||
// We do this so the callback doesn't keep the CatalystInstanceImpl alive.
|
||||
// In this case, Systrace will keep the registered listener around forever
|
||||
// if the CatalystInstanceImpl is not explicitly destroyed. These instances
|
||||
// can still leak, but they are at least small.
|
||||
private final WeakReference<CatalystInstanceImpl> mOuter;
|
||||
|
||||
public JSProfilerTraceListener(CatalystInstanceImpl outer) {
|
||||
mOuter = new WeakReference<CatalystInstanceImpl>(outer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTraceStarted() {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTraceStopped() {
|
||||
CatalystInstanceImpl impl = mOuter.get();
|
||||
if (impl != null) {
|
||||
impl.getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private @Nullable ReactQueueConfigurationSpec mReactQueueConfigurationSpec;
|
||||
private @Nullable JSBundleLoader mJSBundleLoader;
|
||||
private @Nullable NativeModuleRegistry mRegistry;
|
||||
private @Nullable JavaScriptModuleRegistry mJSModuleRegistry;
|
||||
private @Nullable JavaScriptExecutor mJSExecutor;
|
||||
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
|
||||
|
||||
public Builder setReactQueueConfigurationSpec(
|
||||
ReactQueueConfigurationSpec ReactQueueConfigurationSpec) {
|
||||
mReactQueueConfigurationSpec = ReactQueueConfigurationSpec;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRegistry(NativeModuleRegistry registry) {
|
||||
mRegistry = registry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJSModuleRegistry(JavaScriptModuleRegistry jsModuleRegistry) {
|
||||
mJSModuleRegistry = jsModuleRegistry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) {
|
||||
mJSBundleLoader = jsBundleLoader;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJSExecutor(JavaScriptExecutor jsExecutor) {
|
||||
mJSExecutor = jsExecutor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNativeModuleCallExceptionHandler(
|
||||
NativeModuleCallExceptionHandler handler) {
|
||||
mNativeModuleCallExceptionHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CatalystInstanceImpl build() {
|
||||
return new CatalystInstanceImpl(
|
||||
Assertions.assertNotNull(mReactQueueConfigurationSpec),
|
||||
Assertions.assertNotNull(mJSExecutor),
|
||||
Assertions.assertNotNull(mRegistry),
|
||||
Assertions.assertNotNull(mJSModuleRegistry),
|
||||
Assertions.assertNotNull(mJSBundleLoader),
|
||||
Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import com.facebook.react.bridge.BaseJavaModule;
|
||||
import com.facebook.react.bridge.CatalystInstance;
|
||||
import com.facebook.react.bridge.ExecutorToken;
|
||||
import com.facebook.react.bridge.JsonWriter;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactBridge;
|
||||
import com.facebook.react.bridge.ReadableNativeArray;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A Java Object which represents a cross-platform C++ module
|
||||
*
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class CxxModuleWrapper implements NativeModule
|
||||
{
|
||||
static {
|
||||
SoLoader.loadLibrary(CatalystInstanceImpl.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
private HybridData mHybridData;
|
||||
|
||||
@DoNotStrip
|
||||
private static class MethodWrapper implements NativeMethod
|
||||
{
|
||||
@DoNotStrip
|
||||
HybridData mHybridData;
|
||||
|
||||
MethodWrapper() {
|
||||
mHybridData = initHybrid();
|
||||
}
|
||||
|
||||
public native HybridData initHybrid();
|
||||
|
||||
@Override
|
||||
public native void invoke(CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray args);
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return BaseJavaModule.METHOD_TYPE_REMOTE;
|
||||
}
|
||||
}
|
||||
|
||||
public CxxModuleWrapper(String library, String factory) {
|
||||
SoLoader.loadLibrary(library);
|
||||
mHybridData =
|
||||
initHybrid(SoLoader.unpackLibraryAndDependencies(library).getAbsolutePath(), factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public native String getName();
|
||||
|
||||
@Override
|
||||
public native Map<String, NativeMethod> getMethods();
|
||||
|
||||
@Override
|
||||
public void writeConstantsField(JsonWriter writer, String fieldName) throws IOException {
|
||||
String constants = getConstantsJson();
|
||||
if (constants == null || constants.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
writer.name(fieldName).rawValue(constants);
|
||||
}
|
||||
|
||||
public native String getConstantsJson();
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOverrideExistingModule() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWebWorkers() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReactBridgeInitialized(ReactBridge bridge) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCatalystInstanceDestroy() {
|
||||
mHybridData.resetNative();
|
||||
}
|
||||
|
||||
// For creating a wrapper from C++, or from a derived class.
|
||||
protected CxxModuleWrapper(HybridData hd) {
|
||||
mHybridData = hd;
|
||||
}
|
||||
|
||||
private native HybridData initHybrid(String soPath, String factory);
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* A class that stores JS bundle information and allows {@link CatalystInstance} to load a correct
|
||||
* bundle through {@link ReactBridge}.
|
||||
*/
|
||||
public abstract class JSBundleLoader {
|
||||
|
||||
/**
|
||||
* This loader is recommended one for release version of your app. In that case local JS executor
|
||||
* should be used. JS bundle will be read from assets directory in native code to save on passing
|
||||
* large strings from java to native memory.
|
||||
*/
|
||||
public static JSBundleLoader createFileLoader(
|
||||
final Context context,
|
||||
final String fileName) {
|
||||
return new JSBundleLoader() {
|
||||
@Override
|
||||
public void loadScript(CatalystInstanceImpl instance) {
|
||||
if (fileName.startsWith("assets://")) {
|
||||
instance.loadScriptFromAssets(context.getAssets(), fileName);
|
||||
} else {
|
||||
instance.loadScriptFromFile(fileName, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceUrl() {
|
||||
return fileName;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This loader is used when bundle gets reloaded from dev server. In that case loader expect JS
|
||||
* bundle to be prefetched and stored in local file. We do that to avoid passing large strings
|
||||
* between java and native code and avoid allocating memory in java to fit whole JS bundle in it.
|
||||
* Providing correct {@param sourceURL} of downloaded bundle is required for JS stacktraces to
|
||||
* work correctly and allows for source maps to correctly symbolize those.
|
||||
*/
|
||||
public static JSBundleLoader createCachedBundleFromNetworkLoader(
|
||||
final String sourceURL,
|
||||
final String cachedFileLocation) {
|
||||
return new JSBundleLoader() {
|
||||
@Override
|
||||
public void loadScript(CatalystInstanceImpl instance) {
|
||||
instance.loadScriptFromFile(cachedFileLocation, sourceURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceUrl() {
|
||||
return sourceURL;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This loader is used when proxy debugging is enabled. In that case there is no point in fetching
|
||||
* the bundle from device as remote executor will have to do it anyway.
|
||||
*/
|
||||
public static JSBundleLoader createRemoteDebuggerBundleLoader(
|
||||
final String proxySourceURL,
|
||||
final String realSourceURL) {
|
||||
return new JSBundleLoader() {
|
||||
@Override
|
||||
public void loadScript(CatalystInstanceImpl instance) {
|
||||
instance.loadScriptFromFile(null, proxySourceURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceUrl() {
|
||||
return realSourceURL;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public abstract void loadScript(CatalystInstanceImpl instance);
|
||||
public abstract String getSourceUrl();
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.ReadableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
@DoNotStrip
|
||||
public class JSCJavaScriptExecutor extends JavaScriptExecutor {
|
||||
public static class Factory implements JavaScriptExecutor.Factory {
|
||||
private ReadableNativeArray mJSCConfig;
|
||||
|
||||
public Factory(WritableNativeMap jscConfig) {
|
||||
// TODO (t10707444): use NativeMap, which requires moving NativeMap out of OnLoad.
|
||||
WritableNativeArray array = new WritableNativeArray();
|
||||
array.pushMap(jscConfig);
|
||||
mJSCConfig = array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaScriptExecutor create() throws Exception {
|
||||
return new JSCJavaScriptExecutor(mJSCConfig);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(CatalystInstanceImpl.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
public JSCJavaScriptExecutor(ReadableNativeArray jscConfig) {
|
||||
super(initHybrid(jscConfig));
|
||||
}
|
||||
|
||||
private native static HybridData initHybrid(ReadableNativeArray jscConfig);
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
import com.facebook.react.bridge.BaseJavaModule;
|
||||
import com.facebook.react.bridge.CatalystInstance;
|
||||
import com.facebook.react.bridge.ExecutorToken;
|
||||
import com.facebook.react.bridge.NativeArray;
|
||||
import com.facebook.react.bridge.ReadableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
|
||||
/**
|
||||
* This is part of the glue which wraps a java BaseJavaModule in a C++
|
||||
* NativeModule. This could all be in C++, but it's android-specific
|
||||
* initialization code, and writing it this way is easier to read and means
|
||||
* fewer JNI calls.
|
||||
*/
|
||||
|
||||
@DoNotStrip
|
||||
/* package */ class JavaModuleWrapper {
|
||||
@DoNotStrip
|
||||
public class MethodDescriptor {
|
||||
@DoNotStrip
|
||||
Method method;
|
||||
@DoNotStrip
|
||||
String signature;
|
||||
@DoNotStrip
|
||||
String name;
|
||||
@DoNotStrip
|
||||
String type;
|
||||
}
|
||||
|
||||
private final CatalystInstance mCatalystInstance;
|
||||
private final BaseJavaModule mModule;
|
||||
private final ArrayList<BaseJavaModule.JavaMethod> mMethods;
|
||||
|
||||
public JavaModuleWrapper(CatalystInstance catalystinstance, BaseJavaModule module) {
|
||||
mCatalystInstance = catalystinstance;
|
||||
mModule = module;
|
||||
mMethods = new ArrayList<BaseJavaModule.JavaMethod>();
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public BaseJavaModule getModule() {
|
||||
return mModule;
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public String getName() {
|
||||
return mModule.getName();
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public List<MethodDescriptor> getMethodDescriptors() {
|
||||
ArrayList<MethodDescriptor> descs = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, BaseJavaModule.NativeMethod> entry :
|
||||
mModule.getMethods().entrySet()) {
|
||||
MethodDescriptor md = new MethodDescriptor();
|
||||
md.name = entry.getKey();
|
||||
md.type = entry.getValue().getType();
|
||||
|
||||
BaseJavaModule.JavaMethod method = (BaseJavaModule.JavaMethod) entry.getValue();
|
||||
mMethods.add(method);
|
||||
|
||||
descs.add(md);
|
||||
}
|
||||
|
||||
return descs;
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public List<MethodDescriptor> newGetMethodDescriptors() {
|
||||
ArrayList<MethodDescriptor> descs = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, BaseJavaModule.NativeMethod> entry :
|
||||
mModule.getMethods().entrySet()) {
|
||||
MethodDescriptor md = new MethodDescriptor();
|
||||
md.name = entry.getKey();
|
||||
md.type = entry.getValue().getType();
|
||||
|
||||
BaseJavaModule.JavaMethod method = (BaseJavaModule.JavaMethod) entry.getValue();
|
||||
md.method = method.getMethod();
|
||||
md.signature = method.getSignature();
|
||||
|
||||
descs.add(md);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, BaseJavaModule.SyncNativeHook> entry :
|
||||
mModule.getSyncHooks().entrySet()) {
|
||||
MethodDescriptor md = new MethodDescriptor();
|
||||
md.name = entry.getKey();
|
||||
md.type = BaseJavaModule.METHOD_TYPE_SYNC_HOOK;
|
||||
|
||||
BaseJavaModule.SyncJavaHook method = (BaseJavaModule.SyncJavaHook) entry.getValue();
|
||||
md.method = method.getMethod();
|
||||
md.signature = method.getSignature();
|
||||
|
||||
descs.add(md);
|
||||
}
|
||||
|
||||
return descs;
|
||||
}
|
||||
|
||||
// TODO mhorowitz: make this return NativeMap, which requires moving
|
||||
// NativeMap out of OnLoad.
|
||||
@DoNotStrip
|
||||
public NativeArray getConstants() {
|
||||
WritableNativeArray array = new WritableNativeArray();
|
||||
array.pushMap(Arguments.makeNativeMap(mModule.getConstants()));
|
||||
return array;
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public boolean supportsWebWorkers() {
|
||||
return mModule.supportsWebWorkers();
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public void invoke(ExecutorToken token, int methodId, ReadableNativeArray parameters) {
|
||||
if (mMethods == null || methodId >= mMethods.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mMethods.get(methodId).invoke(mCatalystInstance, token, parameters);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
@DoNotStrip
|
||||
public abstract class JavaScriptExecutor {
|
||||
public interface Factory {
|
||||
JavaScriptExecutor create() throws Exception;
|
||||
}
|
||||
|
||||
private final HybridData mHybridData;
|
||||
|
||||
protected JavaScriptExecutor(HybridData hybridData) {
|
||||
mHybridData = hybridData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close this executor and cleanup any resources that it was using. No further calls are
|
||||
* expected after this.
|
||||
* TODO mhorowitz: This may no longer be used; check and delete if possible.
|
||||
*/
|
||||
public void close() {
|
||||
mHybridData.resetNative();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
|
||||
public class ModuleRegistryHolder {
|
||||
private final HybridData mHybridData;
|
||||
private static native HybridData initHybrid(
|
||||
CatalystInstanceImpl catalystInstanceImpl,
|
||||
Collection<JavaModuleWrapper> javaModules,
|
||||
Collection<CxxModuleWrapper> cxxModules);
|
||||
|
||||
public ModuleRegistryHolder(CatalystInstanceImpl catalystInstanceImpl,
|
||||
Collection<JavaModuleWrapper> javaModules,
|
||||
Collection<CxxModuleWrapper> cxxModules) {
|
||||
mHybridData = initHybrid(catalystInstanceImpl, javaModules, cxxModules);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.BaseJavaModule;
|
||||
import com.facebook.react.bridge.CatalystInstance;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.OnBatchCompleteListener;
|
||||
import com.facebook.react.bridge.ReadableNativeArray;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.common.SetBuilder;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.systrace.Systrace;
|
||||
|
||||
/**
|
||||
* A set of Java APIs to expose to a particular JavaScript instance.
|
||||
*/
|
||||
public class NativeModuleRegistry {
|
||||
private final Map<Class<NativeModule>, NativeModule> mModuleInstances;
|
||||
private final ArrayList<OnBatchCompleteListener> mBatchCompleteListenerModules;
|
||||
|
||||
private NativeModuleRegistry(Map<Class<NativeModule>, NativeModule> moduleInstances) {
|
||||
mModuleInstances = moduleInstances;
|
||||
mBatchCompleteListenerModules = new ArrayList<OnBatchCompleteListener>(mModuleInstances.size());
|
||||
for (NativeModule module : mModuleInstances.values()) {
|
||||
if (module instanceof OnBatchCompleteListener) {
|
||||
mBatchCompleteListenerModules.add((OnBatchCompleteListener) module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ ModuleRegistryHolder getModuleRegistryHolder(
|
||||
CatalystInstanceImpl catalystInstanceImpl) {
|
||||
ArrayList<JavaModuleWrapper> javaModules = new ArrayList<>();
|
||||
ArrayList<CxxModuleWrapper> cxxModules = new ArrayList<>();
|
||||
for (NativeModule module : mModuleInstances.values()) {
|
||||
if (module instanceof BaseJavaModule) {
|
||||
javaModules.add(new JavaModuleWrapper(catalystInstanceImpl, (BaseJavaModule) module));
|
||||
} else if (module instanceof CxxModuleWrapper) {
|
||||
cxxModules.add((CxxModuleWrapper) module);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown module type " + module.getClass());
|
||||
}
|
||||
}
|
||||
return new ModuleRegistryHolder(catalystInstanceImpl, javaModules, cxxModules);
|
||||
}
|
||||
|
||||
/* package */ void notifyCatalystInstanceDestroy() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
"NativeModuleRegistry_notifyCatalystInstanceDestroy");
|
||||
try {
|
||||
for (NativeModule nativeModule : mModuleInstances.values()) {
|
||||
nativeModule.onCatalystInstanceDestroy();
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void notifyCatalystInstanceInitialized() {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
ReactMarker.logMarker("NativeModule_start");
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
"NativeModuleRegistry_notifyCatalystInstanceInitialized");
|
||||
try {
|
||||
for (NativeModule nativeModule : mModuleInstances.values()) {
|
||||
nativeModule.initialize();
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
ReactMarker.logMarker("NativeModule_end");
|
||||
}
|
||||
}
|
||||
|
||||
public void onBatchComplete() {
|
||||
for (int i = 0; i < mBatchCompleteListenerModules.size(); i++) {
|
||||
mBatchCompleteListenerModules.get(i).onBatchComplete();
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends NativeModule> boolean hasModule(Class<T> moduleInterface) {
|
||||
return mModuleInstances.containsKey(moduleInterface);
|
||||
}
|
||||
|
||||
public <T extends NativeModule> T getModule(Class<T> moduleInterface) {
|
||||
return (T) Assertions.assertNotNull(mModuleInstances.get(moduleInterface));
|
||||
}
|
||||
|
||||
public Collection<NativeModule> getAllModules() {
|
||||
return mModuleInstances.values();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final HashMap<String, NativeModule> mModules = MapBuilder.newHashMap();
|
||||
|
||||
public Builder add(NativeModule module) {
|
||||
NativeModule existing = mModules.get(module.getName());
|
||||
if (existing != null && !module.canOverrideExistingModule()) {
|
||||
throw new IllegalStateException("Native module " + module.getClass().getSimpleName() +
|
||||
" tried to override " + existing.getClass().getSimpleName() + " for module name " +
|
||||
module.getName() + ". If this was your intention, return true from " +
|
||||
module.getClass().getSimpleName() + "#canOverrideExistingModule()");
|
||||
}
|
||||
mModules.put(module.getName(), module);
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeModuleRegistry build() {
|
||||
Map<Class<NativeModule>, NativeModule> moduleInstances = new HashMap<>();
|
||||
for (NativeModule module : mModules.values()) {
|
||||
moduleInstances.put((Class<NativeModule>)module.getClass(), module);
|
||||
}
|
||||
return new NativeModuleRegistry(moduleInstances);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.JavaJSExecutor;
|
||||
|
||||
/**
|
||||
* JavaScript executor that delegates JS calls processed by native code back to a java version
|
||||
* of the native executor interface.
|
||||
*
|
||||
* When set as a executor with {@link CatalystInstance.Builder}, catalyst native code will delegate
|
||||
* low level javascript calls to the implementation of {@link JavaJSExecutor} interface provided
|
||||
* with the constructor of this class.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class ProxyJavaScriptExecutor extends JavaScriptExecutor {
|
||||
public static class Factory implements JavaScriptExecutor.Factory {
|
||||
private final JavaJSExecutor.Factory mJavaJSExecutorFactory;
|
||||
|
||||
public Factory(JavaJSExecutor.Factory javaJSExecutorFactory) {
|
||||
mJavaJSExecutorFactory = javaJSExecutorFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaScriptExecutor create() throws Exception {
|
||||
return new ProxyJavaScriptExecutor(mJavaJSExecutorFactory.create());
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
SoLoader.loadLibrary(CatalystInstanceImpl.REACT_NATIVE_LIB);
|
||||
}
|
||||
|
||||
private @Nullable JavaJSExecutor mJavaJSExecutor;
|
||||
|
||||
/**
|
||||
* Create {@link ProxyJavaScriptExecutor} instance
|
||||
* @param executor implementation of {@link JavaJSExecutor} which will be responsible for handling
|
||||
* javascript calls
|
||||
*/
|
||||
public ProxyJavaScriptExecutor(JavaJSExecutor executor) {
|
||||
super(initHybrid(executor));
|
||||
mJavaJSExecutor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mJavaJSExecutor != null) {
|
||||
mJavaJSExecutor.close();
|
||||
mJavaJSExecutor = null;
|
||||
}
|
||||
}
|
||||
|
||||
private native static HybridData initHybrid(JavaJSExecutor executor);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
|
||||
@DoNotStrip
|
||||
/* package */ interface ReactCallback {
|
||||
@DoNotStrip
|
||||
void onBatchComplete();
|
||||
|
||||
@DoNotStrip
|
||||
void incrementPendingJSCalls();
|
||||
|
||||
@DoNotStrip
|
||||
void decrementPendingJSCalls();
|
||||
|
||||
@DoNotStrip
|
||||
void onNativeException(Exception e);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
/**
|
||||
* Static class that allows markers to be placed in React code and responded to in a
|
||||
* configurable way
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class ReactMarker {
|
||||
|
||||
public interface MarkerListener {
|
||||
void logMarker(String name);
|
||||
};
|
||||
|
||||
@Nullable static private MarkerListener sMarkerListener = null;
|
||||
|
||||
static public void setMarkerListener(MarkerListener listener) {
|
||||
sMarkerListener = listener;
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
static public void logMarker(String name) {
|
||||
if (sMarkerListener != null) {
|
||||
sMarkerListener.logMarker(name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.AssertionException;
|
||||
|
||||
/**
|
||||
* Utility class to make assertions that should not hard-crash the app but instead be handled by the
|
||||
* Catalyst app {@link NativeModuleCallExceptionHandler}. See the javadoc on that class for
|
||||
* more information about our opinion on when these assertions should be used as opposed to
|
||||
* assertions that might throw AssertionError Throwables that will cause the app to hard crash.
|
||||
*/
|
||||
public class SoftAssertions {
|
||||
|
||||
/**
|
||||
* Throw {@link AssertionException} with a given message. Use this method surrounded with
|
||||
* {@code if} block with assert condition in case you plan to do string concatenation to produce
|
||||
* the message.
|
||||
*/
|
||||
public static void assertUnreachable(String message) {
|
||||
throw new AssertionException(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the given condition, throwing an {@link AssertionException} if the condition doesn't
|
||||
* hold.
|
||||
*/
|
||||
public static void assertCondition(boolean condition, String message) {
|
||||
if (!condition) {
|
||||
throw new AssertionException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given Object isn't null, throwing an {@link AssertionException} if it was.
|
||||
*/
|
||||
public static <T> T assertNotNull(@Nullable T instance) {
|
||||
if (instance == null) {
|
||||
throw new AssertionException("Expected object to not be null!");
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.cxxbridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
/**
|
||||
* Utility for interacting with the UI thread.
|
||||
*/
|
||||
public class UiThreadUtil {
|
||||
|
||||
@Nullable private static Handler sMainHandler;
|
||||
|
||||
/**
|
||||
* @return {@code true} if the current thread is the UI thread.
|
||||
*/
|
||||
public static boolean isOnUiThread() {
|
||||
return Looper.getMainLooper().getThread() == Thread.currentThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an {@link AssertionException} if the current thread is not the UI thread.
|
||||
*/
|
||||
public static void assertOnUiThread() {
|
||||
SoftAssertions.assertCondition(isOnUiThread(), "Expected to run on UI thread!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an {@link AssertionException} if the current thread is the UI thread.
|
||||
*/
|
||||
public static void assertNotOnUiThread() {
|
||||
SoftAssertions.assertCondition(!isOnUiThread(), "Expected not to run on UI thread!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given {@code Runnable} on the UI thread.
|
||||
*/
|
||||
public static void runOnUiThread(Runnable runnable) {
|
||||
synchronized (UiThreadUtil.class) {
|
||||
if (sMainHandler == null) {
|
||||
sMainHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
}
|
||||
sMainHandler.post(runnable);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
## Putting this here is kind of a hack. I don't want to modify the OSS bridge.
|
||||
## TODO mhorowitz: add @DoNotStrip to the interface directly.
|
||||
|
||||
-keepclassmembers class com.facebook.react.bridge.queue.MessageQueueThread {
|
||||
public boolean isOnThread();
|
||||
public void assertIsOnThread();
|
||||
}
|
Loading…
Reference in New Issue