Allow developers to load JavaScript bundle from any source.
Summary: This patch adds two pieces of functionality: - Exposes `JSBundleLoader` to allow a developer to load JavaScript bundles as they choose. - Adds `ReactBridge.loadScripFromFile` method which loads a JavaScript bundle from an arbitrary file path. Example usage: ``` JSBundleLoader jsBundleLoader = new JSBundleLoader() { Override public void loadScript(ReactBridge reactBridge) { reactBridge.loadScriptFromFile("/sdcard/Download/index.android.bundle"); } }; mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setJSBundleLoader(jsBundleLoader) .setJSMainModuleName("") /* necessary due to TODO(6803830) */ .addPackage(new MainReactPackage()) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); ``` cc ide Closes https://github.com/facebook/react-native/pull/3189 Reviewed By: svcscm Differential Revision: D2535819 Pulled By: mkonicek fb-gh-sync-id: f319299dbe29bab3b7e91f94249c14b270d9fec3
This commit is contained in:
parent
b59f250214
commit
3a743ef228
|
@ -77,7 +77,7 @@ public class ReactInstanceManager {
|
||||||
private @Nullable ReactContextInitParams mPendingReactContextInitParams;
|
private @Nullable ReactContextInitParams mPendingReactContextInitParams;
|
||||||
|
|
||||||
/* accessed from any thread */
|
/* accessed from any thread */
|
||||||
private final @Nullable String mBundleAssetName; /* name of JS bundle file in assets folder */
|
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 @Nullable String mJSMainModuleName; /* path to JS bundle root on packager server */
|
||||||
private final List<ReactPackage> mPackages;
|
private final List<ReactPackage> mPackages;
|
||||||
private final DevSupportManager mDevSupportManager;
|
private final DevSupportManager mDevSupportManager;
|
||||||
|
@ -176,7 +176,7 @@ public class ReactInstanceManager {
|
||||||
|
|
||||||
private ReactInstanceManager(
|
private ReactInstanceManager(
|
||||||
Context applicationContext,
|
Context applicationContext,
|
||||||
@Nullable String bundleAssetName,
|
@Nullable String jsBundleFile,
|
||||||
@Nullable String jsMainModuleName,
|
@Nullable String jsMainModuleName,
|
||||||
List<ReactPackage> packages,
|
List<ReactPackage> packages,
|
||||||
boolean useDeveloperSupport,
|
boolean useDeveloperSupport,
|
||||||
|
@ -185,7 +185,7 @@ public class ReactInstanceManager {
|
||||||
initializeSoLoaderIfNecessary(applicationContext);
|
initializeSoLoaderIfNecessary(applicationContext);
|
||||||
|
|
||||||
mApplicationContext = applicationContext;
|
mApplicationContext = applicationContext;
|
||||||
mBundleAssetName = bundleAssetName;
|
mJSBundleFile = jsBundleFile;
|
||||||
mJSMainModuleName = jsMainModuleName;
|
mJSMainModuleName = jsMainModuleName;
|
||||||
mPackages = packages;
|
mPackages = packages;
|
||||||
mUseDeveloperSupport = useDeveloperSupport;
|
mUseDeveloperSupport = useDeveloperSupport;
|
||||||
|
@ -224,30 +224,29 @@ public class ReactInstanceManager {
|
||||||
SoLoader.init(applicationContext, /* native exopackage */ false);
|
SoLoader.init(applicationContext, /* native exopackage */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setJSBundleFile(String jsBundleFile) {
|
||||||
|
mJSBundleFile = jsBundleFile;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger react context initialization asynchronously in a background async task. This enables
|
* Trigger react context initialization asynchronously in a background async task. This enables
|
||||||
* applications to pre-load the application JS, and execute global code before
|
* applications to pre-load the application JS, and execute global code before
|
||||||
* {@link ReactRootView} is available and measured.
|
* {@link ReactRootView} is available and measured.
|
||||||
*/
|
*/
|
||||||
public void createReactContextInBackground() {
|
public void createReactContextInBackground() {
|
||||||
if (mUseDeveloperSupport) {
|
if (mUseDeveloperSupport && mJSMainModuleName != null) {
|
||||||
if (mDevSupportManager.hasUpToDateJSBundleInCache()) {
|
if (mDevSupportManager.hasUpToDateJSBundleInCache()) {
|
||||||
// If there is a up-to-date bundle downloaded from server, always use that
|
// If there is a up-to-date bundle downloaded from server, always use that
|
||||||
onJSBundleLoadedFromServer();
|
onJSBundleLoadedFromServer();
|
||||||
return;
|
} else {
|
||||||
} else if (mBundleAssetName == null ||
|
|
||||||
!mDevSupportManager.hasBundleInAssets(mBundleAssetName)) {
|
|
||||||
// Bundle not available in assets, fetch from the server
|
|
||||||
mDevSupportManager.handleReloadJS();
|
mDevSupportManager.handleReloadJS();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// Use JS file from assets
|
|
||||||
recreateReactContextInBackground(
|
recreateReactContextInBackground(
|
||||||
new JSCJavaScriptExecutor(),
|
new JSCJavaScriptExecutor(),
|
||||||
JSBundleLoader.createAssetLoader(
|
JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile));
|
||||||
mApplicationContext.getAssets(),
|
|
||||||
mBundleAssetName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -564,7 +563,7 @@ public class ReactInstanceManager {
|
||||||
|
|
||||||
private final List<ReactPackage> mPackages = new ArrayList<>();
|
private final List<ReactPackage> mPackages = new ArrayList<>();
|
||||||
|
|
||||||
private @Nullable String mBundleAssetName;
|
private @Nullable String mJSBundleFile;
|
||||||
private @Nullable String mJSMainModuleName;
|
private @Nullable String mJSMainModuleName;
|
||||||
private @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
|
private @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
|
||||||
private @Nullable Application mApplication;
|
private @Nullable Application mApplication;
|
||||||
|
@ -575,11 +574,21 @@ public class ReactInstanceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the JS budle file to be loaded from application's raw assets.
|
* Name of the JS bundle file to be loaded from application's raw assets.
|
||||||
|
*
|
||||||
* Example: {@code "index.android.js"}
|
* Example: {@code "index.android.js"}
|
||||||
*/
|
*/
|
||||||
public Builder setBundleAssetName(String bundleAssetName) {
|
public Builder setBundleAssetName(String bundleAssetName) {
|
||||||
mBundleAssetName = bundleAssetName;
|
return this.setJSBundleFile("assets://" + bundleAssetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to the JS bundle file to be loaded from the file system.
|
||||||
|
*
|
||||||
|
* Example: {@code "assets://index.android.js" or "/sdcard/main.jsbundle}
|
||||||
|
*/
|
||||||
|
public Builder setJSBundleFile(String jsBundleFile) {
|
||||||
|
mJSBundleFile = jsBundleFile;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,21 +648,23 @@ public class ReactInstanceManager {
|
||||||
* Before calling {@code build}, the following must be called:
|
* Before calling {@code build}, the following must be called:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li> {@link #setApplication}
|
* <li> {@link #setApplication}
|
||||||
* <li> {@link #setBundleAssetName} or {@link #setJSMainModuleName}
|
* <li> {@link #setJSBundleFile} or {@link #setJSMainModuleName}
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public ReactInstanceManager build() {
|
public ReactInstanceManager build() {
|
||||||
Assertions.assertCondition(
|
Assertions.assertCondition(
|
||||||
mUseDeveloperSupport || mBundleAssetName != null,
|
mUseDeveloperSupport || mJSBundleFile != null,
|
||||||
"JS Bundle has to be provided in app assets when dev support is disabled");
|
"JS Bundle File has to be provided when dev support is disabled");
|
||||||
|
|
||||||
Assertions.assertCondition(
|
Assertions.assertCondition(
|
||||||
mBundleAssetName != null || mJSMainModuleName != null,
|
mJSMainModuleName != null || mJSBundleFile != null,
|
||||||
"Either BundleAssetName or MainModuleName needs to be provided");
|
"Either MainModuleName or JS Bundle File needs to be provided");
|
||||||
|
|
||||||
return new ReactInstanceManager(
|
return new ReactInstanceManager(
|
||||||
Assertions.assertNotNull(
|
Assertions.assertNotNull(
|
||||||
mApplication,
|
mApplication,
|
||||||
"Application property has not been set with this builder"),
|
"Application property has not been set with this builder"),
|
||||||
mBundleAssetName,
|
mJSBundleFile,
|
||||||
mJSMainModuleName,
|
mJSMainModuleName,
|
||||||
mPackages,
|
mPackages,
|
||||||
mUseDeveloperSupport,
|
mUseDeveloperSupport,
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
package com.facebook.react.bridge;
|
package com.facebook.react.bridge;
|
||||||
|
|
||||||
import android.content.res.AssetManager;
|
import android.content.Context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that stores JS bundle information and allows {@link CatalystInstance} to load a correct
|
* A class that stores JS bundle information and allows {@link CatalystInstance} to load a correct
|
||||||
|
@ -22,18 +22,22 @@ public abstract class JSBundleLoader {
|
||||||
* should be used. JS bundle will be read from assets directory in native code to save on passing
|
* 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.
|
* large strings from java to native memory.
|
||||||
*/
|
*/
|
||||||
public static JSBundleLoader createAssetLoader(
|
public static JSBundleLoader createFileLoader(
|
||||||
final AssetManager assetManager,
|
final Context context,
|
||||||
final String assetFileName) {
|
final String fileName) {
|
||||||
return new JSBundleLoader() {
|
return new JSBundleLoader() {
|
||||||
@Override
|
@Override
|
||||||
public void loadScript(ReactBridge bridge) {
|
public void loadScript(ReactBridge bridge) {
|
||||||
bridge.loadScriptFromAssets(assetManager, assetFileName);
|
if (fileName.startsWith("assets://")) {
|
||||||
|
bridge.loadScriptFromAssets(context.getAssets(), fileName.replaceFirst("assets://", ""));
|
||||||
|
} else {
|
||||||
|
bridge.loadScriptFromFile(fileName, fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSourceUrl() {
|
public String getSourceUrl() {
|
||||||
return "file:///android_asset/" + assetFileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -51,7 +55,7 @@ public abstract class JSBundleLoader {
|
||||||
return new JSBundleLoader() {
|
return new JSBundleLoader() {
|
||||||
@Override
|
@Override
|
||||||
public void loadScript(ReactBridge bridge) {
|
public void loadScript(ReactBridge bridge) {
|
||||||
bridge.loadScriptFromNetworkCached(sourceURL, cachedFileLocation);
|
bridge.loadScriptFromFile(cachedFileLocation, sourceURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,7 +74,7 @@ public abstract class JSBundleLoader {
|
||||||
return new JSBundleLoader() {
|
return new JSBundleLoader() {
|
||||||
@Override
|
@Override
|
||||||
public void loadScript(ReactBridge bridge) {
|
public void loadScript(ReactBridge bridge) {
|
||||||
bridge.loadScriptFromNetworkCached(sourceURL, null);
|
bridge.loadScriptFromFile(null, sourceURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -65,7 +65,7 @@ public class ReactBridge extends Countable {
|
||||||
* All native functions are not thread safe and appropriate queues should be used
|
* All native functions are not thread safe and appropriate queues should be used
|
||||||
*/
|
*/
|
||||||
public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
|
public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
|
||||||
public native void loadScriptFromNetworkCached(String sourceURL, @Nullable String tempFileName);
|
public native void loadScriptFromFile(@Nullable String fileName, @Nullable String sourceURL);
|
||||||
public native void callFunction(int moduleId, int methodId, NativeArray arguments);
|
public native void callFunction(int moduleId, int methodId, NativeArray arguments);
|
||||||
public native void invokeCallback(int callbackID, NativeArray arguments);
|
public native void invokeCallback(int callbackID, NativeArray arguments);
|
||||||
public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
|
public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
|
||||||
|
|
|
@ -367,10 +367,18 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSourceMapUrl() {
|
public String getSourceMapUrl() {
|
||||||
|
if (mJSAppBundleName == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
return mDevServerHelper.getSourceMapUrl(Assertions.assertNotNull(mJSAppBundleName));
|
return mDevServerHelper.getSourceMapUrl(Assertions.assertNotNull(mJSAppBundleName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSourceUrl() {
|
public String getSourceUrl() {
|
||||||
|
if (mJSAppBundleName == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
return mDevServerHelper.getSourceUrl(Assertions.assertNotNull(mJSAppBundleName));
|
return mDevServerHelper.getSourceUrl(Assertions.assertNotNull(mJSAppBundleName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -638,7 +638,7 @@ static void loadScriptFromAssets(JNIEnv* env, jobject obj, jobject assetManager,
|
||||||
auto bridge = extractRefPtr<Bridge>(env, obj);
|
auto bridge = extractRefPtr<Bridge>(env, obj);
|
||||||
auto assetNameStr = fromJString(env, assetName);
|
auto assetNameStr = fromJString(env, assetName);
|
||||||
|
|
||||||
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromNetworkCached_start"));
|
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromAssets_start"));
|
||||||
auto script = react::loadScriptFromAssets(env, assetManager, assetNameStr);
|
auto script = react::loadScriptFromAssets(env, assetManager, assetNameStr);
|
||||||
#ifdef WITH_FBSYSTRACE
|
#ifdef WITH_FBSYSTRACE
|
||||||
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_"
|
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_"
|
||||||
|
@ -646,30 +646,27 @@ static void loadScriptFromAssets(JNIEnv* env, jobject obj, jobject assetManager,
|
||||||
"assetName", assetNameStr);
|
"assetName", assetNameStr);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromNetworkCached_read"));
|
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromAssets_read"));
|
||||||
executeApplicationScript(bridge, script, assetNameStr);
|
executeApplicationScript(bridge, script, assetNameStr);
|
||||||
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromNetworkCached_done"));
|
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromAssets_done"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadScriptFromNetworkCached(JNIEnv* env, jobject obj, jstring sourceURL,
|
static void loadScriptFromFile(JNIEnv* env, jobject obj, jstring fileName, jstring sourceURL) {
|
||||||
jstring tempFileName) {
|
|
||||||
jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker");
|
jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker");
|
||||||
|
|
||||||
auto bridge = jni::extractRefPtr<Bridge>(env, obj);
|
auto bridge = jni::extractRefPtr<Bridge>(env, obj);
|
||||||
std::string script = "";
|
auto fileNameStr = fileName == NULL ? "" : fromJString(env, fileName);
|
||||||
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromNetworkCached_start"));
|
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromFile_start"));
|
||||||
if (tempFileName != NULL) {
|
auto script = fileName == NULL ? "" : react::loadScriptFromFile(fileNameStr);
|
||||||
script = react::loadScriptFromFile(jni::fromJString(env, tempFileName));
|
|
||||||
}
|
|
||||||
#ifdef WITH_FBSYSTRACE
|
#ifdef WITH_FBSYSTRACE
|
||||||
auto sourceURLStr = fromJString(env, sourceURL);
|
auto sourceURLStr = sourceURL == NULL ? fileNameStr : fromJString(env, sourceURL);
|
||||||
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_"
|
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_"
|
||||||
"executeApplicationScript",
|
"executeApplicationScript",
|
||||||
"sourceURL", sourceURLStr);
|
"sourceURL", sourceURLStr);
|
||||||
#endif
|
#endif
|
||||||
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromNetworkCached_read"));
|
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromFile_read"));
|
||||||
executeApplicationScript(bridge, script, jni::fromJString(env, sourceURL));
|
executeApplicationScript(bridge, script, jni::fromJString(env, sourceURL));
|
||||||
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromNetworkCached_exec"));
|
env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromFile_exec"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void callFunction(JNIEnv* env, jobject obj, jint moduleId, jint methodId,
|
static void callFunction(JNIEnv* env, jobject obj, jint moduleId, jint methodId,
|
||||||
|
@ -821,7 +818,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||||
makeNativeMethod(
|
makeNativeMethod(
|
||||||
"loadScriptFromAssets", "(Landroid/content/res/AssetManager;Ljava/lang/String;)V",
|
"loadScriptFromAssets", "(Landroid/content/res/AssetManager;Ljava/lang/String;)V",
|
||||||
bridge::loadScriptFromAssets),
|
bridge::loadScriptFromAssets),
|
||||||
makeNativeMethod("loadScriptFromNetworkCached", bridge::loadScriptFromNetworkCached),
|
makeNativeMethod("loadScriptFromFile", bridge::loadScriptFromFile),
|
||||||
makeNativeMethod("callFunction", bridge::callFunction),
|
makeNativeMethod("callFunction", bridge::callFunction),
|
||||||
makeNativeMethod("invokeCallback", bridge::invokeCallback),
|
makeNativeMethod("invokeCallback", bridge::invokeCallback),
|
||||||
makeNativeMethod("setGlobalVariable", bridge::setGlobalVariable),
|
makeNativeMethod("setGlobalVariable", bridge::setGlobalVariable),
|
||||||
|
|
Loading…
Reference in New Issue