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:
Matthew Arbesfeld 2015-10-28 04:56:26 -07:00 committed by facebook-github-bot-7
parent b59f250214
commit 3a743ef228
5 changed files with 65 additions and 45 deletions

View File

@ -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,

View File

@ -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

View File

@ -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);

View File

@ -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));
} }

View File

@ -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),