Sync diff : Enable initializing react context off UI thread
Reviewed By: @astreet Differential Revision: D2480130
This commit is contained in:
parent
8f13560fd5
commit
dcae4bada0
|
@ -16,6 +16,7 @@ import java.util.List;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
@ -72,6 +73,8 @@ public class ReactInstanceManager {
|
||||||
/* should only be accessed from main thread (UI thread) */
|
/* should only be accessed from main thread (UI thread) */
|
||||||
private final List<ReactRootView> mAttachedRootViews = new ArrayList<>();
|
private final List<ReactRootView> mAttachedRootViews = new ArrayList<>();
|
||||||
private LifecycleState mLifecycleState;
|
private LifecycleState mLifecycleState;
|
||||||
|
private boolean mIsContextInitAsyncTaskRunning;
|
||||||
|
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 final @Nullable String mBundleAssetName; /* name of JS bundle file in assets folder */
|
||||||
|
@ -111,6 +114,65 @@ public class ReactInstanceManager {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private class ReactContextInitParams {
|
||||||
|
private final JavaScriptExecutor mJsExecutor;
|
||||||
|
private final JSBundleLoader mJsBundleLoader;
|
||||||
|
|
||||||
|
public ReactContextInitParams(
|
||||||
|
JavaScriptExecutor jsExecutor,
|
||||||
|
JSBundleLoader jsBundleLoader) {
|
||||||
|
mJsExecutor = Assertions.assertNotNull(jsExecutor);
|
||||||
|
mJsBundleLoader = Assertions.assertNotNull(jsBundleLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JavaScriptExecutor getJsExecutor() {
|
||||||
|
return mJsExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, ReactApplicationContext> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
if (mCurrentReactContext != null) {
|
||||||
|
tearDownReactContext(mCurrentReactContext);
|
||||||
|
mCurrentReactContext = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ReactApplicationContext doInBackground(ReactContextInitParams... params) {
|
||||||
|
Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
|
||||||
|
return createReactContext(params[0].getJsExecutor(), params[0].getJsBundleLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(ReactApplicationContext reactContext) {
|
||||||
|
try {
|
||||||
|
setupReactContext(reactContext);
|
||||||
|
} finally {
|
||||||
|
mIsContextInitAsyncTaskRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle enqueued request to re-initialize react context.
|
||||||
|
if (mPendingReactContextInitParams != null) {
|
||||||
|
recreateReactContextInBackground(
|
||||||
|
mPendingReactContextInitParams.getJsExecutor(),
|
||||||
|
mPendingReactContextInitParams.getJsBundleLoader());
|
||||||
|
mPendingReactContextInitParams = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ReactInstanceManager(
|
private ReactInstanceManager(
|
||||||
Context applicationContext,
|
Context applicationContext,
|
||||||
@Nullable String bundleAssetName,
|
@Nullable String bundleAssetName,
|
||||||
|
@ -161,10 +223,35 @@ public class ReactInstanceManager {
|
||||||
SoLoader.init(applicationContext, /* native exopackage */ false);
|
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.
|
||||||
|
*/
|
||||||
|
public void createReactContextInBackground() {
|
||||||
|
if (mUseDeveloperSupport) {
|
||||||
|
if (mDevSupportManager.hasUpToDateJSBundleInCache()) {
|
||||||
|
// If there is a up-to-date bundle downloaded from server, always use that
|
||||||
|
onJSBundleLoadedFromServer();
|
||||||
|
return;
|
||||||
|
} else if (mBundleAssetName == null ||
|
||||||
|
!mDevSupportManager.hasBundleInAssets(mBundleAssetName)) {
|
||||||
|
// Bundle not available in assets, fetch from the server
|
||||||
|
mDevSupportManager.handleReloadJS();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Use JS file from assets
|
||||||
|
recreateReactContextInBackground(
|
||||||
|
new JSCJavaScriptExecutor(),
|
||||||
|
JSBundleLoader.createAssetLoader(
|
||||||
|
mApplicationContext.getAssets(),
|
||||||
|
mBundleAssetName));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will give JS the opportunity to consume the back button event. If JS does not
|
* 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
|
* consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS.
|
||||||
* to JS.
|
|
||||||
*/
|
*/
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
UiThreadUtil.assertOnUiThread();
|
UiThreadUtil.assertOnUiThread();
|
||||||
|
@ -255,18 +342,26 @@ public class ReactInstanceManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach given {@param rootView} to a catalyst instance manager and start JS application using
|
* Attach given {@param rootView} to a catalyst instance manager and start JS application using
|
||||||
* JS module provided by {@link ReactRootView#getJSModuleName}. This view will then be tracked
|
* JS module provided by {@link ReactRootView#getJSModuleName}. If the react context is currently
|
||||||
* by this manager and in case of catalyst instance restart it will be re-attached.
|
* 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.
|
||||||
*/
|
*/
|
||||||
/* package */ void attachMeasuredRootView(ReactRootView rootView) {
|
/* package */ void attachMeasuredRootView(ReactRootView rootView) {
|
||||||
UiThreadUtil.assertOnUiThread();
|
UiThreadUtil.assertOnUiThread();
|
||||||
mAttachedRootViews.add(rootView);
|
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 (!mIsContextInitAsyncTaskRunning) {
|
||||||
if (mCurrentReactContext == null) {
|
if (mCurrentReactContext == null) {
|
||||||
initializeReactContext();
|
createReactContextInBackground();
|
||||||
} else {
|
} else {
|
||||||
attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance());
|
attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detach given {@param rootView} from current catalyst instance. It's safe to call this method
|
* Detach given {@param rootView} from current catalyst instance. It's safe to call this method
|
||||||
|
@ -300,53 +395,51 @@ public class ReactInstanceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onReloadWithJSDebugger(ProxyJavaScriptExecutor proxyExecutor) {
|
private void onReloadWithJSDebugger(ProxyJavaScriptExecutor proxyExecutor) {
|
||||||
recreateReactContext(
|
recreateReactContextInBackground(
|
||||||
proxyExecutor,
|
proxyExecutor,
|
||||||
JSBundleLoader.createRemoteDebuggerBundleLoader(
|
JSBundleLoader.createRemoteDebuggerBundleLoader(
|
||||||
mDevSupportManager.getJSBundleURLForRemoteDebugging()));
|
mDevSupportManager.getJSBundleURLForRemoteDebugging()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onJSBundleLoadedFromServer() {
|
private void onJSBundleLoadedFromServer() {
|
||||||
recreateReactContext(
|
recreateReactContextInBackground(
|
||||||
new JSCJavaScriptExecutor(),
|
new JSCJavaScriptExecutor(),
|
||||||
JSBundleLoader.createCachedBundleFromNetworkLoader(
|
JSBundleLoader.createCachedBundleFromNetworkLoader(
|
||||||
mDevSupportManager.getSourceUrl(),
|
mDevSupportManager.getSourceUrl(),
|
||||||
mDevSupportManager.getDownloadedJSBundleFile()));
|
mDevSupportManager.getDownloadedJSBundleFile()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeReactContext() {
|
private void recreateReactContextInBackground(
|
||||||
if (mUseDeveloperSupport) {
|
|
||||||
if (mDevSupportManager.hasUpToDateJSBundleInCache()) {
|
|
||||||
// If there is a up-to-date bundle downloaded from server, always use that
|
|
||||||
onJSBundleLoadedFromServer();
|
|
||||||
return;
|
|
||||||
} else if (mBundleAssetName == null ||
|
|
||||||
!mDevSupportManager.hasBundleInAssets(mBundleAssetName)) {
|
|
||||||
// Bundle not available in assets, fetch from the server
|
|
||||||
mDevSupportManager.handleReloadJS();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Use JS file from assets
|
|
||||||
recreateReactContext(
|
|
||||||
new JSCJavaScriptExecutor(),
|
|
||||||
JSBundleLoader.createAssetLoader(
|
|
||||||
mApplicationContext.getAssets(),
|
|
||||||
mBundleAssetName));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recreateReactContext(
|
|
||||||
JavaScriptExecutor jsExecutor,
|
JavaScriptExecutor jsExecutor,
|
||||||
JSBundleLoader jsBundleLoader) {
|
JSBundleLoader jsBundleLoader) {
|
||||||
UiThreadUtil.assertOnUiThread();
|
UiThreadUtil.assertOnUiThread();
|
||||||
if (mCurrentReactContext != null) {
|
|
||||||
tearDownReactContext(mCurrentReactContext);
|
ReactContextInitParams initParams = new ReactContextInitParams(jsExecutor, jsBundleLoader);
|
||||||
|
if (!mIsContextInitAsyncTaskRunning) {
|
||||||
|
// No background task to create react context is currently running, create and execute one.
|
||||||
|
ReactContextInitAsyncTask initTask = new ReactContextInitAsyncTask();
|
||||||
|
initTask.execute(initParams);
|
||||||
|
mIsContextInitAsyncTaskRunning = true;
|
||||||
|
} else {
|
||||||
|
// Background task is currently running, queue up most recent init params to recreate context
|
||||||
|
// once task completes.
|
||||||
|
mPendingReactContextInitParams = initParams;
|
||||||
}
|
}
|
||||||
mCurrentReactContext = createReactContext(jsExecutor, jsBundleLoader);
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
moveReactContextToCurrentLifecycleState(reactContext);
|
||||||
|
|
||||||
for (ReactRootView rootView : mAttachedRootViews) {
|
for (ReactRootView rootView : mAttachedRootViews) {
|
||||||
attachMeasuredRootViewToInstance(
|
attachMeasuredRootViewToInstance(rootView, catalystInstance);
|
||||||
rootView,
|
|
||||||
mCurrentReactContext.getCatalystInstance());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,10 +523,6 @@ public class ReactInstanceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
reactContext.initializeWithInstance(catalystInstance);
|
reactContext.initializeWithInstance(catalystInstance);
|
||||||
catalystInstance.initialize();
|
|
||||||
mDevSupportManager.onNewReactContextCreated(reactContext);
|
|
||||||
|
|
||||||
moveReactContextToCurrentLifecycleState(reactContext);
|
|
||||||
|
|
||||||
return reactContext;
|
return reactContext;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,10 +78,10 @@ import okio.Sink;
|
||||||
|
|
||||||
private final DevInternalSettings mSettings;
|
private final DevInternalSettings mSettings;
|
||||||
private final OkHttpClient mClient;
|
private final OkHttpClient mClient;
|
||||||
|
private final Handler mRestartOnChangePollingHandler;
|
||||||
|
|
||||||
private boolean mOnChangePollingEnabled;
|
private boolean mOnChangePollingEnabled;
|
||||||
private @Nullable OkHttpClient mOnChangePollingClient;
|
private @Nullable OkHttpClient mOnChangePollingClient;
|
||||||
private @Nullable Handler mRestartOnChangePollingHandler;
|
|
||||||
private @Nullable OnServerContentChangeListener mOnServerContentChangeListener;
|
private @Nullable OnServerContentChangeListener mOnServerContentChangeListener;
|
||||||
|
|
||||||
public DevServerHelper(DevInternalSettings settings) {
|
public DevServerHelper(DevInternalSettings settings) {
|
||||||
|
@ -92,6 +92,7 @@ import okio.Sink;
|
||||||
// No read or write timeouts by default
|
// No read or write timeouts by default
|
||||||
mClient.setReadTimeout(0, TimeUnit.MILLISECONDS);
|
mClient.setReadTimeout(0, TimeUnit.MILLISECONDS);
|
||||||
mClient.setWriteTimeout(0, TimeUnit.MILLISECONDS);
|
mClient.setWriteTimeout(0, TimeUnit.MILLISECONDS);
|
||||||
|
mRestartOnChangePollingHandler = new Handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Intent action for reloading the JS */
|
/** Intent action for reloading the JS */
|
||||||
|
@ -193,10 +194,7 @@ import okio.Sink;
|
||||||
|
|
||||||
public void stopPollingOnChangeEndpoint() {
|
public void stopPollingOnChangeEndpoint() {
|
||||||
mOnChangePollingEnabled = false;
|
mOnChangePollingEnabled = false;
|
||||||
if (mRestartOnChangePollingHandler != null) {
|
|
||||||
mRestartOnChangePollingHandler.removeCallbacksAndMessages(null);
|
mRestartOnChangePollingHandler.removeCallbacksAndMessages(null);
|
||||||
mRestartOnChangePollingHandler = null;
|
|
||||||
}
|
|
||||||
if (mOnChangePollingClient != null) {
|
if (mOnChangePollingClient != null) {
|
||||||
mOnChangePollingClient.cancel(this);
|
mOnChangePollingClient.cancel(this);
|
||||||
mOnChangePollingClient = null;
|
mOnChangePollingClient = null;
|
||||||
|
@ -216,7 +214,6 @@ import okio.Sink;
|
||||||
mOnChangePollingClient
|
mOnChangePollingClient
|
||||||
.setConnectionPool(new ConnectionPool(1, LONG_POLL_KEEP_ALIVE_DURATION_MS))
|
.setConnectionPool(new ConnectionPool(1, LONG_POLL_KEEP_ALIVE_DURATION_MS))
|
||||||
.setConnectTimeout(HTTP_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
.setConnectTimeout(HTTP_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||||
mRestartOnChangePollingHandler = new Handler();
|
|
||||||
enqueueOnChangeEndpointLongPolling();
|
enqueueOnChangeEndpointLongPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +243,7 @@ import okio.Sink;
|
||||||
// of a failure, so that we don't flood network queue with frequent requests in case when
|
// of a failure, so that we don't flood network queue with frequent requests in case when
|
||||||
// dev server is down
|
// dev server is down
|
||||||
FLog.d(ReactConstants.TAG, "Error while requesting /onchange endpoint", e);
|
FLog.d(ReactConstants.TAG, "Error while requesting /onchange endpoint", e);
|
||||||
Assertions.assertNotNull(mRestartOnChangePollingHandler).postDelayed(
|
mRestartOnChangePollingHandler.postDelayed(
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
|
@ -82,7 +82,8 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
|
||||||
Assertions.assertNotNull(mJSTimersModule).callTimers(timersToCall);
|
Assertions.assertNotNull(mJSTimersModule).callTimers(timersToCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
mReactChoreographer.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, this);
|
Assertions.assertNotNull(mReactChoreographer)
|
||||||
|
.postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,14 +91,13 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
|
||||||
private final PriorityQueue<Timer> mTimers;
|
private final PriorityQueue<Timer> mTimers;
|
||||||
private final SparseArray<Timer> mTimerIdsToTimers;
|
private final SparseArray<Timer> mTimerIdsToTimers;
|
||||||
private final AtomicBoolean isPaused = new AtomicBoolean(false);
|
private final AtomicBoolean isPaused = new AtomicBoolean(false);
|
||||||
private final ReactChoreographer mReactChoreographer;
|
|
||||||
private final FrameCallback mFrameCallback = new FrameCallback();
|
private final FrameCallback mFrameCallback = new FrameCallback();
|
||||||
|
private @Nullable ReactChoreographer mReactChoreographer;
|
||||||
private @Nullable JSTimersExecution mJSTimersModule;
|
private @Nullable JSTimersExecution mJSTimersModule;
|
||||||
private boolean mFrameCallbackPosted = false;
|
private boolean mFrameCallbackPosted = false;
|
||||||
|
|
||||||
public Timing(ReactApplicationContext reactContext) {
|
public Timing(ReactApplicationContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
mReactChoreographer = ReactChoreographer.getInstance();
|
|
||||||
// We store timers sorted by finish time.
|
// We store timers sorted by finish time.
|
||||||
mTimers = new PriorityQueue<Timer>(
|
mTimers = new PriorityQueue<Timer>(
|
||||||
11, // Default capacity: for some reason they don't expose a (Comparator) constructor
|
11, // Default capacity: for some reason they don't expose a (Comparator) constructor
|
||||||
|
@ -119,6 +119,8 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
// Safe to acquire choreographer here, as initialize() is invoked from UI thread.
|
||||||
|
mReactChoreographer = ReactChoreographer.getInstance();
|
||||||
mJSTimersModule = getReactApplicationContext().getCatalystInstance()
|
mJSTimersModule = getReactApplicationContext().getCatalystInstance()
|
||||||
.getJSModule(JSTimersExecution.class);
|
.getJSModule(JSTimersExecution.class);
|
||||||
getReactApplicationContext().addLifecycleEventListener(this);
|
getReactApplicationContext().addLifecycleEventListener(this);
|
||||||
|
@ -151,7 +153,7 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
|
||||||
|
|
||||||
private void setChoreographerCallback() {
|
private void setChoreographerCallback() {
|
||||||
if (!mFrameCallbackPosted) {
|
if (!mFrameCallbackPosted) {
|
||||||
mReactChoreographer.postFrameCallback(
|
Assertions.assertNotNull(mReactChoreographer).postFrameCallback(
|
||||||
ReactChoreographer.CallbackType.TIMERS_EVENTS,
|
ReactChoreographer.CallbackType.TIMERS_EVENTS,
|
||||||
mFrameCallback);
|
mFrameCallback);
|
||||||
mFrameCallbackPosted = true;
|
mFrameCallbackPosted = true;
|
||||||
|
@ -160,7 +162,7 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
|
||||||
|
|
||||||
private void clearChoreographerCallback() {
|
private void clearChoreographerCallback() {
|
||||||
if (mFrameCallbackPosted) {
|
if (mFrameCallbackPosted) {
|
||||||
mReactChoreographer.removeFrameCallback(
|
Assertions.assertNotNull(mReactChoreographer).removeFrameCallback(
|
||||||
ReactChoreographer.CallbackType.TIMERS_EVENTS,
|
ReactChoreographer.CallbackType.TIMERS_EVENTS,
|
||||||
mFrameCallback);
|
mFrameCallback);
|
||||||
mFrameCallbackPosted = false;
|
mFrameCallbackPosted = false;
|
||||||
|
|
Loading…
Reference in New Issue