diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 76126952b..e526076e7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -41,6 +41,7 @@ import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; +import com.facebook.react.devsupport.DevServerHelper; import com.facebook.react.devsupport.DevSupportManager; import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; @@ -232,18 +233,42 @@ public class ReactInstanceManager { * 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. + * + * Called from UI thread. */ public void createReactContextInBackground() { if (mUseDeveloperSupport && mJSMainModuleName != null) { if (mDevSupportManager.hasUpToDateJSBundleInCache()) { // If there is a up-to-date bundle downloaded from server, always use that onJSBundleLoadedFromServer(); - } else { + } 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(), JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile)); @@ -579,7 +604,7 @@ public class ReactInstanceManager { * Example: {@code "index.android.js"} */ public Builder setBundleAssetName(String bundleAssetName) { - return this.setJSBundleFile("assets://" + bundleAssetName); + return this.setJSBundleFile(bundleAssetName == null ? null : "assets://" + bundleAssetName); } /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index 312dd1f69..294e9047b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -20,6 +20,7 @@ import android.content.Context; import android.os.Build; import android.os.Handler; import android.text.TextUtils; +import android.util.Log; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; @@ -32,6 +33,7 @@ import com.squareup.okhttp.ConnectionPool; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; +import com.squareup.okhttp.ResponseBody; import okio.Okio; import okio.Sink; @@ -44,7 +46,7 @@ import okio.Sink; * - Android stock emulator with standard non-configurable local loopback alias: 10.0.2.2, * - Genymotion emulator with default settings: 10.0.3.2 */ -/* package */ class DevServerHelper { +public class DevServerHelper { public static final String RELOAD_APP_EXTRA_JS_PROXY = "jsproxy"; private static final String RELOAD_APP_ACTION_SUFFIX = ".RELOAD_APP_ACTION"; @@ -62,6 +64,9 @@ import okio.Sink; private static final String ONCHANGE_ENDPOINT_URL_FORMAT = "http://%s/onchange"; private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy"; + private static final String PACKAGER_STATUS_URL_FORMAT = "http://%s/status"; + + private static final String PACKAGER_OK_STATUS = "packager-status:running"; private static final int LONG_POLL_KEEP_ALIVE_DURATION_MS = 2 * 60 * 1000; // 2 mins private static final int LONG_POLL_FAILURE_DELAY_MS = 5000; @@ -76,6 +81,10 @@ import okio.Sink; void onServerContentChanged(); } + public interface PackagerStatusCallback { + void onPackagerStatusFetched(boolean packagerIsRunning); + } + private final DevInternalSettings mSettings; private final OkHttpClient mClient; private final Handler mRestartOnChangePollingHandler; @@ -199,6 +208,54 @@ import okio.Sink; }); } + public void isPackagerRunning(final PackagerStatusCallback callback) { + String statusURL = createPacakgerStatusURL(getDebugServerHost()); + Request request = new Request.Builder() + .url(statusURL) + .build(); + + mClient.newCall(request).enqueue( + new Callback() { + @Override + public void onFailure(Request request, IOException e) { + Log.e(ReactConstants.TAG, "IOException requesting status from packager", e); + callback.onPackagerStatusFetched(false); + } + + @Override + public void onResponse(Response response) throws IOException { + if (!response.isSuccessful()) { + Log.e( + ReactConstants.TAG, + "Got non-success http code from packager when requesting status: " + + response.code()); + callback.onPackagerStatusFetched(false); + return; + } + ResponseBody body = response.body(); + if (body == null) { + Log.e( + ReactConstants.TAG, + "Got null body response from packager when requesting status"); + callback.onPackagerStatusFetched(false); + return; + } + if (!PACKAGER_OK_STATUS.equals(body.string())) { + Log.e( + ReactConstants.TAG, + "Got unexpected response from packager when requesting status: " + body.string()); + callback.onPackagerStatusFetched(false); + return; + } + callback.onPackagerStatusFetched(true); + } + }); + } + + private String createPacakgerStatusURL(String host) { + return String.format(PACKAGER_STATUS_URL_FORMAT, host); + } + public void stopPollingOnChangeEndpoint() { mOnChangePollingEnabled = false; mRestartOnChangePollingHandler.removeCallbacksAndMessages(null); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java index 09e237354..11b5ffefe 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java @@ -475,6 +475,8 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler { } public void handleReloadJS() { + UiThreadUtil.assertOnUiThread(); + // dismiss redbox if exists if (mRedBoxDialog != null) { mRedBoxDialog.dismiss(); @@ -496,6 +498,10 @@ public class DevSupportManager implements NativeModuleCallExceptionHandler { } } + public void isPackagerRunning(DevServerHelper.PackagerStatusCallback callback) { + mDevServerHelper.isPackagerRunning(callback); + } + private void reloadJSInProxyMode(final ProgressDialog progressDialog) { // When using js proxy, there is no need to fetch JS bundle as proxy executor will do that // anyway