Clean up the interface to the bridge

Summary: Change the following classes into interfaces, with a separate
Impl file:  CatalystInstance, ReactInstanceManager,
CatalystQueueConfiguration, MessageQueueThread.  This is done to help
isolate the interface between React Native and applications which use it.
This will also help some intrusive development work on a branch
such as porting parts of the bridge to common C++ code, without affecting
app reliability while this work is ongoing.
public

Reviewed By: astreet

Differential Revision: D2651277

fb-gh-sync-id: f04dc04a6e68df7acbc2bbf8b2529287d7b5b2ae
This commit is contained in:
Marc Horowitz
2015-11-20 12:17:23 -08:00
committed by facebook-github-bot-6
parent facf8a56d2
commit 9a61628f13
9 changed files with 1435 additions and 1165 deletions

View File

@@ -11,27 +11,11 @@ package com.facebook.react.bridge;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.queue.CatalystQueueConfiguration;
import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec;
import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
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.systrace.Systrace;
import com.facebook.systrace.TraceListener;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
/**
* A higher level API on top of the asynchronous JSC bridge. This provides an
@@ -39,254 +23,31 @@ import com.fasterxml.jackson.core.JsonGenerator;
* Java APIs be invokable from JavaScript as well.
*/
@DoNotStrip
public class CatalystInstance {
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 CatalystQueueConfiguration mCatalystQueueConfiguration;
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;
// Access from native modules thread
private final NativeModuleRegistry mJavaRegistry;
private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private boolean mInitialized = false;
// Access from JS thread
private @Nullable ReactBridge mBridge;
private boolean mJSBundleHasLoaded;
private CatalystInstance(
final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry registry,
final JavaScriptModulesConfig jsModulesConfig,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
mCatalystQueueConfiguration = CatalystQueueConfiguration.create(
catalystQueueConfigurationSpec,
new NativeExceptionHandler());
mBridgeIdleListeners = new CopyOnWriteArrayList<>();
mJavaRegistry = registry;
mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstance.this, jsModulesConfig);
mJSBundleLoader = jsBundleLoader;
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
mTraceListener = new JSProfilerTraceListener();
final CountDownLatch initLatch = new CountDownLatch(1);
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
initializeBridge(jsExecutor, jsModulesConfig);
initLatch.countDown();
}
});
try {
Assertions.assertCondition(
initLatch.await(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS),
"Timed out waiting for bridge to initialize!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private void initializeBridge(
JavaScriptExecutor jsExecutor,
JavaScriptModulesConfig jsModulesConfig) {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
mBridge = new ReactBridge(
jsExecutor,
new NativeModulesReactCallback(),
mCatalystQueueConfiguration.getNativeModulesQueueThread());
mBridge.setGlobalVariable(
"__fbBatchedBridgeConfig",
buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
Systrace.registerListener(mTraceListener);
}
public void runJSBundle() {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"CatalystInstance_runJSBundle");
try {
final CountDownLatch initLatch = new CountDownLatch(1);
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
mJSBundleHasLoaded = true;
incrementPendingJSCalls();
try {
mJSBundleLoader.loadScript(mBridge);
} catch (JSExecutionException e) {
mNativeModuleCallExceptionHandler.handleException(e);
}
initLatch.countDown();
}
});
Assertions.assertCondition(
initLatch.await(LOAD_JS_BUNDLE_TIMEOUT_MS, TimeUnit.MILLISECONDS),
"Timed out loading JS!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
/* package */ void callFunction(
final int moduleId,
final int methodId,
final NativeArray arguments,
final String tracingName) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
return;
}
incrementPendingJSCalls();
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
if (mDestroyed) {
return;
}
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, tracingName);
try {
Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
});
}
public interface CatalystInstance {
void runJSBundle();
// This is called from java code, so it won't be stripped anyway, but proguard will rename it,
// which this prevents.
@DoNotStrip
/* package */ void invokeCallback(final int callbackID, final NativeArray arguments) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
return;
}
incrementPendingJSCalls();
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
if (mDestroyed) {
return;
}
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "<callback>");
try {
Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
});
}
void invokeCallback(final int callbackID, final NativeArray arguments);
/**
* Destroys this catalyst instance, waiting for any other threads in CatalystQueueConfiguration
* (besides the UI thread) to finish running. Must be called from the UI thread so that we can
* fully shut down other threads.
*/
/* package */ void destroy() {
UiThreadUtil.assertOnUiThread();
if (mDestroyed) {
return;
}
// TODO: tell all APIs to shut down
mDestroyed = true;
mJavaRegistry.notifyCatalystInstanceDestroy();
mCatalystQueueConfiguration.destroy();
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
listener.onTransitionToBridgeIdle();
}
}
if (mBridge != null) {
Systrace.unregisterListener(mTraceListener);
}
// We can access the Bridge from any thread now because we know either we are on the JS thread
// or the JS thread has finished via CatalystQueueConfiguration#destroy()
Assertions.assertNotNull(mBridge).dispose();
}
public boolean isDestroyed() {
return mDestroyed;
}
void destroy();
boolean isDestroyed();
/**
* Initialize all the native modules
*/
@VisibleForTesting
public void initialize() {
UiThreadUtil.assertOnUiThread();
Assertions.assertCondition(
!mInitialized,
"This catalyst instance has already been initialized");
mInitialized = true;
mJavaRegistry.notifyCatalystInstanceInitialized();
}
void initialize();
public CatalystQueueConfiguration getCatalystQueueConfiguration() {
return mCatalystQueueConfiguration;
}
CatalystQueueConfiguration getCatalystQueueConfiguration();
@VisibleForTesting
public @Nullable
ReactBridge getBridge() {
return mBridge;
}
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface);
}
public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
return mJavaRegistry.getModule(nativeModuleInterface);
}
public Collection<NativeModule> getNativeModules() {
return mJavaRegistry.getAllModules();
}
<T extends JavaScriptModule> T getJSModule(Class<T> jsInterface);
<T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface);
Collection<NativeModule> getNativeModules();
/**
* Adds a idle listener for this Catalyst instance. The listener will receive notifications
@@ -294,204 +55,15 @@ public class CatalystInstance {
* defined as there being some non-zero number of calls to JS that haven't resolved via a
* onBatchCompleted call. The listener should be purely passive and not affect application logic.
*/
public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
mBridgeIdleListeners.add(listener);
}
void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener);
/**
* Removes a NotThreadSafeBridgeIdleDebugListener previously added with
* {@link #addBridgeIdleDebugListener}
*/
public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
mBridgeIdleListeners.remove(listener);
}
void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener);
public boolean supportsProfiling() {
if (mBridge == null) {
return false;
}
return mBridge.supportsProfiling();
}
public void startProfiler(String title) {
if (mBridge == null) {
return;
}
mBridge.startProfiler(title);
}
public void stopProfiler(String title, String filename) {
if (mBridge == null) {
return;
}
mBridge.stopProfiler(title, filename);
}
private String buildModulesConfigJSONProperty(
NativeModuleRegistry nativeModuleRegistry,
JavaScriptModulesConfig jsModulesConfig) {
// TODO(5300733): Serialize config using single json generator
JsonFactory jsonFactory = new JsonFactory();
StringWriter writer = new StringWriter();
try {
JsonGenerator jg = jsonFactory.createGenerator(writer);
jg.writeStartObject();
jg.writeFieldName("remoteModuleConfig");
jg.writeRawValue(nativeModuleRegistry.moduleDescriptions());
jg.writeFieldName("localModulesConfig");
jg.writeRawValue(jsModulesConfig.moduleDescriptions());
jg.writeEndObject();
jg.close();
} catch (IOException ioe) {
throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
}
return writer.getBuffer().toString();
}
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();
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 class NativeModulesReactCallback implements ReactCallback {
@Override
public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// Suppress any callbacks if destroyed - will only lead to sadness.
if (mDestroyed) {
return;
}
mJavaRegistry.call(CatalystInstance.this, moduleId, methodId, parameters);
}
@Override
public void onBatchComplete() {
mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// The bridge may have been destroyed due to an exception during the batch. In that case
// native modules could be in a bad state so we don't want to call anything on them. We
// still want to trigger the debug listener since it allows instrumentation tests to end and
// check their assertions without waiting for a timeout.
if (!mDestroyed) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete");
try {
mJavaRegistry.onBatchComplete();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
decrementPendingJSCalls();
}
}
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.
mNativeModuleCallExceptionHandler.handleException(e);
mCatalystQueueConfiguration.getUIQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
destroy();
}
});
}
}
private class JSProfilerTraceListener implements TraceListener {
@Override
public void onTraceStarted() {
getJSModule(BridgeProfiling.class).setEnabled(true);
}
@Override
public void onTraceStopped() {
getJSModule(BridgeProfiling.class).setEnabled(false);
}
}
public static class Builder {
private @Nullable CatalystQueueConfigurationSpec mCatalystQueueConfigurationSpec;
private @Nullable JSBundleLoader mJSBundleLoader;
private @Nullable NativeModuleRegistry mRegistry;
private @Nullable JavaScriptModulesConfig mJSModulesConfig;
private @Nullable JavaScriptExecutor mJSExecutor;
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
public Builder setCatalystQueueConfigurationSpec(
CatalystQueueConfigurationSpec catalystQueueConfigurationSpec) {
mCatalystQueueConfigurationSpec = catalystQueueConfigurationSpec;
return this;
}
public Builder setRegistry(NativeModuleRegistry registry) {
mRegistry = registry;
return this;
}
public Builder setJSModulesConfig(JavaScriptModulesConfig jsModulesConfig) {
mJSModulesConfig = jsModulesConfig;
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 CatalystInstance build() {
return new CatalystInstance(
Assertions.assertNotNull(mCatalystQueueConfigurationSpec),
Assertions.assertNotNull(mJSExecutor),
Assertions.assertNotNull(mRegistry),
Assertions.assertNotNull(mJSModulesConfig),
Assertions.assertNotNull(mJSBundleLoader),
Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
}
}
boolean supportsProfiling();
void startProfiler(String title);
void stopProfiler(String title, String filename);
}

View File

@@ -0,0 +1,510 @@
/**
* 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.bridge;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.queue.CatalystQueueConfiguration;
import com.facebook.react.bridge.queue.CatalystQueueConfigurationImpl;
import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec;
import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
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.systrace.Systrace;
import com.facebook.systrace.TraceListener;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
/**
* This provides an implementation of the public CatalystInstance instance. It is public because
* it is built by ReactInstanceManager which is in a different package.
*/
@DoNotStrip
public class CatalystInstanceImpl implements CatalystInstance {
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 CatalystQueueConfigurationImpl mCatalystQueueConfiguration;
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;
// Access from native modules thread
private final NativeModuleRegistry mJavaRegistry;
private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private boolean mInitialized = false;
// Access from JS thread
private @Nullable ReactBridge mBridge;
private boolean mJSBundleHasLoaded;
private CatalystInstanceImpl(
final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec,
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry registry,
final JavaScriptModulesConfig jsModulesConfig,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
mCatalystQueueConfiguration = CatalystQueueConfigurationImpl.create(
catalystQueueConfigurationSpec,
new NativeExceptionHandler());
mBridgeIdleListeners = new CopyOnWriteArrayList<>();
mJavaRegistry = registry;
mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig);
mJSBundleLoader = jsBundleLoader;
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
mTraceListener = new JSProfilerTraceListener();
final CountDownLatch initLatch = new CountDownLatch(1);
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
initializeBridge(jsExecutor, jsModulesConfig);
initLatch.countDown();
}
});
try {
Assertions.assertCondition(
initLatch.await(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS),
"Timed out waiting for bridge to initialize!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private void initializeBridge(
JavaScriptExecutor jsExecutor,
JavaScriptModulesConfig jsModulesConfig) {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
mBridge = new ReactBridge(
jsExecutor,
new NativeModulesReactCallback(),
mCatalystQueueConfiguration.getNativeModulesQueueThread());
mBridge.setGlobalVariable(
"__fbBatchedBridgeConfig",
buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
Systrace.registerListener(mTraceListener);
}
@Override
public void runJSBundle() {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"CatalystInstance_runJSBundle");
try {
final CountDownLatch initLatch = new CountDownLatch(1);
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
mJSBundleHasLoaded = true;
incrementPendingJSCalls();
try {
mJSBundleLoader.loadScript(mBridge);
} catch (JSExecutionException e) {
mNativeModuleCallExceptionHandler.handleException(e);
}
initLatch.countDown();
}
});
Assertions.assertCondition(
initLatch.await(LOAD_JS_BUNDLE_TIMEOUT_MS, TimeUnit.MILLISECONDS),
"Timed out loading JS!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
/* package */ void callFunction(
final int moduleId,
final int methodId,
final NativeArray arguments,
final String tracingName) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
return;
}
incrementPendingJSCalls();
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
if (mDestroyed) {
return;
}
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, tracingName);
try {
Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
});
}
// This is called from java code, so it won't be stripped anyway, but proguard will rename it,
// which this prevents.
@DoNotStrip
@Override
public void invokeCallback(final int callbackID, final NativeArray arguments) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
return;
}
incrementPendingJSCalls();
mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
if (mDestroyed) {
return;
}
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "<callback>");
try {
Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
});
}
/**
* Destroys this catalyst instance, waiting for any other threads in CatalystQueueConfiguration
* (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;
mJavaRegistry.notifyCatalystInstanceDestroy();
mCatalystQueueConfiguration.destroy();
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
listener.onTransitionToBridgeIdle();
}
}
if (mBridge != null) {
Systrace.unregisterListener(mTraceListener);
}
// We can access the Bridge from any thread now because we know either we are on the JS thread
// or the JS thread has finished via CatalystQueueConfiguration#destroy()
Assertions.assertNotNull(mBridge).dispose();
}
@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 CatalystQueueConfiguration getCatalystQueueConfiguration() {
return mCatalystQueueConfiguration;
}
@VisibleForTesting
public @Nullable
ReactBridge getBridge() {
return mBridge;
}
@Override
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface);
}
@Override
public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
return mJavaRegistry.getModule(nativeModuleInterface);
}
@Override
public Collection<NativeModule> getNativeModules() {
return mJavaRegistry.getAllModules();
}
/**
* 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
* onBatchCompleted 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 boolean supportsProfiling() {
if (mBridge == null) {
return false;
}
return mBridge.supportsProfiling();
}
@Override
public void startProfiler(String title) {
if (mBridge == null) {
return;
}
mBridge.startProfiler(title);
}
@Override
public void stopProfiler(String title, String filename) {
if (mBridge == null) {
return;
}
mBridge.stopProfiler(title, filename);
}
private String buildModulesConfigJSONProperty(
NativeModuleRegistry nativeModuleRegistry,
JavaScriptModulesConfig jsModulesConfig) {
// TODO(5300733): Serialize config using single json generator
JsonFactory jsonFactory = new JsonFactory();
StringWriter writer = new StringWriter();
try {
JsonGenerator jg = jsonFactory.createGenerator(writer);
jg.writeStartObject();
jg.writeFieldName("remoteModuleConfig");
jg.writeRawValue(nativeModuleRegistry.moduleDescriptions());
jg.writeFieldName("localModulesConfig");
jg.writeRawValue(jsModulesConfig.moduleDescriptions());
jg.writeEndObject();
jg.close();
} catch (IOException ioe) {
throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
}
return writer.getBuffer().toString();
}
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();
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 class NativeModulesReactCallback implements ReactCallback {
@Override
public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// Suppress any callbacks if destroyed - will only lead to sadness.
if (mDestroyed) {
return;
}
mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters);
}
@Override
public void onBatchComplete() {
mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// The bridge may have been destroyed due to an exception during the batch. In that case
// native modules could be in a bad state so we don't want to call anything on them. We
// still want to trigger the debug listener since it allows instrumentation tests to end and
// check their assertions without waiting for a timeout.
if (!mDestroyed) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete");
try {
mJavaRegistry.onBatchComplete();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
decrementPendingJSCalls();
}
}
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.
mNativeModuleCallExceptionHandler.handleException(e);
mCatalystQueueConfiguration.getUIQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
destroy();
}
});
}
}
private class JSProfilerTraceListener implements TraceListener {
@Override
public void onTraceStarted() {
getJSModule(BridgeProfiling.class).setEnabled(true);
}
@Override
public void onTraceStopped() {
getJSModule(BridgeProfiling.class).setEnabled(false);
}
}
public static class Builder {
private @Nullable CatalystQueueConfigurationSpec mCatalystQueueConfigurationSpec;
private @Nullable JSBundleLoader mJSBundleLoader;
private @Nullable NativeModuleRegistry mRegistry;
private @Nullable JavaScriptModulesConfig mJSModulesConfig;
private @Nullable JavaScriptExecutor mJSExecutor;
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
public Builder setCatalystQueueConfigurationSpec(
CatalystQueueConfigurationSpec catalystQueueConfigurationSpec) {
mCatalystQueueConfigurationSpec = catalystQueueConfigurationSpec;
return this;
}
public Builder setRegistry(NativeModuleRegistry registry) {
mRegistry = registry;
return this;
}
public Builder setJSModulesConfig(JavaScriptModulesConfig jsModulesConfig) {
mJSModulesConfig = jsModulesConfig;
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(mCatalystQueueConfigurationSpec),
Assertions.assertNotNull(mJSExecutor),
Assertions.assertNotNull(mRegistry),
Assertions.assertNotNull(mJSModulesConfig),
Assertions.assertNotNull(mJSBundleLoader),
Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
}
}
}

View File

@@ -30,7 +30,7 @@ import com.facebook.infer.annotation.Assertions;
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
public JavaScriptModuleRegistry(
CatalystInstance instance,
CatalystInstanceImpl instance,
JavaScriptModulesConfig config) {
mModuleInstances = new HashMap<>();
for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) {
@@ -52,11 +52,11 @@ import com.facebook.infer.annotation.Assertions;
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final CatalystInstance mCatalystInstance;
private final CatalystInstanceImpl mCatalystInstance;
private final JavaScriptModuleRegistration mModuleRegistration;
public JavaScriptModuleInvocationHandler(
CatalystInstance catalystInstance,
CatalystInstanceImpl catalystInstance,
JavaScriptModuleRegistration moduleRegistration) {
mCatalystInstance = catalystInstance;
mModuleRegistration = moduleRegistration;

View File

@@ -9,12 +9,6 @@
package com.facebook.react.bridge.queue;
import java.util.Map;
import android.os.Looper;
import com.facebook.react.common.MapBuilder;
/**
* Specifies which {@link MessageQueueThread}s must be used to run the various contexts of
* execution within catalyst (Main UI thread, native modules, and JS). Some of these queues *may* be
@@ -24,67 +18,8 @@ import com.facebook.react.common.MapBuilder;
* Native Modules Queue Thread: The thread and Looper that native modules are invoked on.
* JS Queue Thread: The thread and Looper that JS is executed on.
*/
public class CatalystQueueConfiguration {
private final MessageQueueThread mUIQueueThread;
private final MessageQueueThread mNativeModulesQueueThread;
private final MessageQueueThread mJSQueueThread;
private CatalystQueueConfiguration(
MessageQueueThread uiQueueThread,
MessageQueueThread nativeModulesQueueThread,
MessageQueueThread jsQueueThread) {
mUIQueueThread = uiQueueThread;
mNativeModulesQueueThread = nativeModulesQueueThread;
mJSQueueThread = jsQueueThread;
}
public MessageQueueThread getUIQueueThread() {
return mUIQueueThread;
}
public MessageQueueThread getNativeModulesQueueThread() {
return mNativeModulesQueueThread;
}
public MessageQueueThread getJSQueueThread() {
return mJSQueueThread;
}
/**
* Should be called when the corresponding {@link com.facebook.react.bridge.CatalystInstance}
* is destroyed so that we shut down the proper queue threads.
*/
public void destroy() {
if (mNativeModulesQueueThread.getLooper() != Looper.getMainLooper()) {
mNativeModulesQueueThread.quitSynchronous();
}
if (mJSQueueThread.getLooper() != Looper.getMainLooper()) {
mJSQueueThread.quitSynchronous();
}
}
public static CatalystQueueConfiguration create(
CatalystQueueConfigurationSpec spec,
QueueThreadExceptionHandler exceptionHandler) {
Map<MessageQueueThreadSpec, MessageQueueThread> specsToThreads = MapBuilder.newHashMap();
MessageQueueThreadSpec uiThreadSpec = MessageQueueThreadSpec.mainThreadSpec();
MessageQueueThread uiThread = MessageQueueThread.create( uiThreadSpec, exceptionHandler);
specsToThreads.put(uiThreadSpec, uiThread);
MessageQueueThread jsThread = specsToThreads.get(spec.getJSQueueThreadSpec());
if (jsThread == null) {
jsThread = MessageQueueThread.create(spec.getJSQueueThreadSpec(), exceptionHandler);
}
MessageQueueThread nativeModulesThread =
specsToThreads.get(spec.getNativeModulesQueueThreadSpec());
if (nativeModulesThread == null) {
nativeModulesThread =
MessageQueueThread.create(spec.getNativeModulesQueueThreadSpec(), exceptionHandler);
}
return new CatalystQueueConfiguration(uiThread, nativeModulesThread, jsThread);
}
public interface CatalystQueueConfiguration {
MessageQueueThread getUIQueueThread();
MessageQueueThread getNativeModulesQueueThread();
MessageQueueThread getJSQueueThread();
}

View File

@@ -0,0 +1,85 @@
/**
* 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.bridge.queue;
import java.util.Map;
import android.os.Looper;
import com.facebook.react.common.MapBuilder;
public class CatalystQueueConfigurationImpl implements CatalystQueueConfiguration {
private final MessageQueueThreadImpl mUIQueueThread;
private final MessageQueueThreadImpl mNativeModulesQueueThread;
private final MessageQueueThreadImpl mJSQueueThread;
private CatalystQueueConfigurationImpl(
MessageQueueThreadImpl uiQueueThread,
MessageQueueThreadImpl nativeModulesQueueThread,
MessageQueueThreadImpl jsQueueThread) {
mUIQueueThread = uiQueueThread;
mNativeModulesQueueThread = nativeModulesQueueThread;
mJSQueueThread = jsQueueThread;
}
@Override
public MessageQueueThread getUIQueueThread() {
return mUIQueueThread;
}
@Override
public MessageQueueThread getNativeModulesQueueThread() {
return mNativeModulesQueueThread;
}
@Override
public MessageQueueThread getJSQueueThread() {
return mJSQueueThread;
}
/**
* Should be called when the corresponding {@link com.facebook.react.bridge.CatalystInstance}
* is destroyed so that we shut down the proper queue threads.
*/
public void destroy() {
if (mNativeModulesQueueThread.getLooper() != Looper.getMainLooper()) {
mNativeModulesQueueThread.quitSynchronous();
}
if (mJSQueueThread.getLooper() != Looper.getMainLooper()) {
mJSQueueThread.quitSynchronous();
}
}
public static CatalystQueueConfigurationImpl create(
CatalystQueueConfigurationSpec spec,
QueueThreadExceptionHandler exceptionHandler) {
Map<MessageQueueThreadSpec, MessageQueueThreadImpl> specsToThreads = MapBuilder.newHashMap();
MessageQueueThreadSpec uiThreadSpec = MessageQueueThreadSpec.mainThreadSpec();
MessageQueueThreadImpl uiThread =
MessageQueueThreadImpl.create( uiThreadSpec, exceptionHandler);
specsToThreads.put(uiThreadSpec, uiThread);
MessageQueueThreadImpl jsThread = specsToThreads.get(spec.getJSQueueThreadSpec());
if (jsThread == null) {
jsThread = MessageQueueThreadImpl.create(spec.getJSQueueThreadSpec(), exceptionHandler);
}
MessageQueueThreadImpl nativeModulesThread =
specsToThreads.get(spec.getNativeModulesQueueThreadSpec());
if (nativeModulesThread == null) {
nativeModulesThread =
MessageQueueThreadImpl.create(spec.getNativeModulesQueueThreadSpec(), exceptionHandler);
}
return new CatalystQueueConfigurationImpl(uiThread, nativeModulesThread, jsThread);
}
}

View File

@@ -9,136 +9,28 @@
package com.facebook.react.bridge.queue;
import android.os.Looper;
import com.facebook.common.logging.FLog;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.AssertionException;
import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.futures.SimpleSettableFuture;
/**
* Encapsulates a Thread that has a {@link Looper} running on it that can accept Runnables.
* Encapsulates a Thread that can accept Runnables.
*/
@DoNotStrip
public class MessageQueueThread {
private final String mName;
private final Looper mLooper;
private final MessageQueueThreadHandler mHandler;
private final String mAssertionErrorMessage;
private volatile boolean mIsFinished = false;
private MessageQueueThread(
String name,
Looper looper,
QueueThreadExceptionHandler exceptionHandler) {
mName = name;
mLooper = looper;
mHandler = new MessageQueueThreadHandler(looper, exceptionHandler);
mAssertionErrorMessage = "Expected to be called from the '" + getName() + "' thread!";
}
public interface MessageQueueThread {
/**
* Runs the given Runnable on this Thread. It will be submitted to the end of the event queue even
* if it is being submitted from the same queue Thread.
*/
@DoNotStrip
public void runOnQueue(Runnable runnable) {
if (mIsFinished) {
FLog.w(
ReactConstants.TAG,
"Tried to enqueue runnable on already finished thread: '" + getName() +
"... dropping Runnable.");
}
mHandler.post(runnable);
}
void runOnQueue(Runnable runnable);
/**
* @return whether the current Thread is also the Thread associated with this MessageQueueThread.
*/
public boolean isOnThread() {
return mLooper.getThread() == Thread.currentThread();
}
boolean isOnThread();
/**
* Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an
* {@link AssertionError}) if the assertion fails.
*/
public void assertIsOnThread() {
SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage);
}
/**
* Quits this queue's Looper. If that Looper was running on a different Thread than the current
* Thread, also waits for the last message being processed to finish and the Thread to die.
*/
public void quitSynchronous() {
mIsFinished = true;
mLooper.quit();
if (mLooper.getThread() != Thread.currentThread()) {
try {
mLooper.getThread().join();
} catch (InterruptedException e) {
throw new RuntimeException("Got interrupted waiting to join thread " + mName);
}
}
}
public Looper getLooper() {
return mLooper;
}
public String getName() {
return mName;
}
public static MessageQueueThread create(
MessageQueueThreadSpec spec,
QueueThreadExceptionHandler exceptionHandler) {
switch (spec.getThreadType()) {
case MAIN_UI:
return createForMainThread(spec.getName(), exceptionHandler);
case NEW_BACKGROUND:
return startNewBackgroundThread(spec.getName(), exceptionHandler);
default:
throw new RuntimeException("Unknown thread type: " + spec.getThreadType());
}
}
/**
* @return a MessageQueueThread corresponding to Android's main UI thread.
*/
private static MessageQueueThread createForMainThread(
String name,
QueueThreadExceptionHandler exceptionHandler) {
Looper mainLooper = Looper.getMainLooper();
return new MessageQueueThread(name, mainLooper, exceptionHandler);
}
/**
* Creates and starts a new MessageQueueThread encapsulating a new Thread with a new Looper
* running on it. Give it a name for easier debugging. When this method exits, the new
* MessageQueueThread is ready to receive events.
*/
private static MessageQueueThread startNewBackgroundThread(
String name,
QueueThreadExceptionHandler exceptionHandler) {
final SimpleSettableFuture<Looper> simpleSettableFuture = new SimpleSettableFuture<>();
Thread bgThread = new Thread(
new Runnable() {
@Override
public void run() {
Looper.prepare();
simpleSettableFuture.set(Looper.myLooper());
Looper.loop();
}
}, "mqt_" + name);
bgThread.start();
return new MessageQueueThread(name, simpleSettableFuture.get(5000), exceptionHandler);
}
void assertIsOnThread();
}

View File

@@ -0,0 +1,144 @@
/**
* 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.bridge.queue;
import android.os.Looper;
import com.facebook.common.logging.FLog;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.AssertionException;
import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.futures.SimpleSettableFuture;
/**
* Encapsulates a Thread that has a {@link Looper} running on it that can accept Runnables.
*/
@DoNotStrip
/* package */ class MessageQueueThreadImpl implements MessageQueueThread {
private final String mName;
private final Looper mLooper;
private final MessageQueueThreadHandler mHandler;
private final String mAssertionErrorMessage;
private volatile boolean mIsFinished = false;
private MessageQueueThreadImpl(
String name,
Looper looper,
QueueThreadExceptionHandler exceptionHandler) {
mName = name;
mLooper = looper;
mHandler = new MessageQueueThreadHandler(looper, exceptionHandler);
mAssertionErrorMessage = "Expected to be called from the '" + getName() + "' thread!";
}
/**
* Runs the given Runnable on this Thread. It will be submitted to the end of the event queue even
* if it is being submitted from the same queue Thread.
*/
@DoNotStrip
public void runOnQueue(Runnable runnable) {
if (mIsFinished) {
FLog.w(
ReactConstants.TAG,
"Tried to enqueue runnable on already finished thread: '" + getName() +
"... dropping Runnable.");
}
mHandler.post(runnable);
}
/**
* @return whether the current Thread is also the Thread associated with this MessageQueueThread.
*/
public boolean isOnThread() {
return mLooper.getThread() == Thread.currentThread();
}
/**
* Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an
* {@link AssertionError}) if the assertion fails.
*/
public void assertIsOnThread() {
SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage);
}
/**
* Quits this queue's Looper. If that Looper was running on a different Thread than the current
* Thread, also waits for the last message being processed to finish and the Thread to die.
*/
public void quitSynchronous() {
mIsFinished = true;
mLooper.quit();
if (mLooper.getThread() != Thread.currentThread()) {
try {
mLooper.getThread().join();
} catch (InterruptedException e) {
throw new RuntimeException("Got interrupted waiting to join thread " + mName);
}
}
}
public Looper getLooper() {
return mLooper;
}
public String getName() {
return mName;
}
public static MessageQueueThreadImpl create(
MessageQueueThreadSpec spec,
QueueThreadExceptionHandler exceptionHandler) {
switch (spec.getThreadType()) {
case MAIN_UI:
return createForMainThread(spec.getName(), exceptionHandler);
case NEW_BACKGROUND:
return startNewBackgroundThread(spec.getName(), exceptionHandler);
default:
throw new RuntimeException("Unknown thread type: " + spec.getThreadType());
}
}
/**
* @return a MessageQueueThreadImpl corresponding to Android's main UI thread.
*/
private static MessageQueueThreadImpl createForMainThread(
String name,
QueueThreadExceptionHandler exceptionHandler) {
Looper mainLooper = Looper.getMainLooper();
return new MessageQueueThreadImpl(name, mainLooper, exceptionHandler);
}
/**
* Creates and starts a new MessageQueueThreadImpl encapsulating a new Thread with a new Looper
* running on it. Give it a name for easier debugging. When this method exits, the new
* MessageQueueThreadImpl is ready to receive events.
*/
private static MessageQueueThreadImpl startNewBackgroundThread(
String name,
QueueThreadExceptionHandler exceptionHandler) {
final SimpleSettableFuture<Looper> simpleSettableFuture = new SimpleSettableFuture<>();
Thread bgThread = new Thread(
new Runnable() {
@Override
public void run() {
Looper.prepare();
simpleSettableFuture.set(Looper.myLooper());
Looper.loop();
}
}, "mqt_" + name);
bgThread.start();
return new MessageQueueThreadImpl(name, simpleSettableFuture.get(5000), exceptionHandler);
}
}