diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java index 48328da25..c0961b0c6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java @@ -10,10 +10,8 @@ package com.facebook.react.bridge.queue; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import android.os.Looper; @@ -21,6 +19,7 @@ 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.bridge.UiThreadUtil; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.futures.SimpleSettableFuture; @@ -28,7 +27,7 @@ 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 { +public class MessageQueueThreadImpl implements MessageQueueThread { private final String mName; private final Looper mLooper; @@ -142,7 +141,25 @@ import com.facebook.react.common.futures.SimpleSettableFuture; String name, QueueThreadExceptionHandler exceptionHandler) { Looper mainLooper = Looper.getMainLooper(); - return new MessageQueueThreadImpl(name, mainLooper, exceptionHandler); + final MessageQueueThreadImpl mqt = + new MessageQueueThreadImpl(name, mainLooper, exceptionHandler); + + // Ensure that the MQT is registered by the time this method returns + if (UiThreadUtil.isOnUiThread()) { + MessageQueueThreadRegistry.register(mqt); + } else { + final SimpleSettableFuture registrationFuture = new SimpleSettableFuture<>(); + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + MessageQueueThreadRegistry.register(mqt); + registrationFuture.set(null); + } + }); + registrationFuture.getOrThrow(5000, TimeUnit.MILLISECONDS); + } + return mqt; } /** @@ -150,30 +167,29 @@ import com.facebook.react.common.futures.SimpleSettableFuture; * 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, + public static MessageQueueThreadImpl startNewBackgroundThread( + final String name, QueueThreadExceptionHandler exceptionHandler) { - final SimpleSettableFuture simpleSettableFuture = new SimpleSettableFuture<>(); + final SimpleSettableFuture looperFuture = new SimpleSettableFuture<>(); + final SimpleSettableFuture mqtFuture = new SimpleSettableFuture<>(); Thread bgThread = new Thread( new Runnable() { @Override public void run() { Looper.prepare(); - simpleSettableFuture.set(Looper.myLooper()); + looperFuture.set(Looper.myLooper()); + MessageQueueThreadRegistry.register(mqtFuture.getOrThrow(5000, TimeUnit.MILLISECONDS)); Looper.loop(); } }, "mqt_" + name); bgThread.start(); - try { - return new MessageQueueThreadImpl( - name, - simpleSettableFuture.get(5000, TimeUnit.MILLISECONDS), - exceptionHandler); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new RuntimeException(e); - } + Looper myLooper = looperFuture.getOrThrow(5000, TimeUnit.MILLISECONDS); + MessageQueueThreadImpl mqt = new MessageQueueThreadImpl(name, myLooper, exceptionHandler); + mqtFuture.set(mqt); + + return mqt; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadRegistry.java new file mode 100644 index 000000000..b924fd88b --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadRegistry.java @@ -0,0 +1,38 @@ +/** + * 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 com.facebook.infer.annotation.Assertions; +import com.facebook.proguard.annotations.DoNotStrip; + +/** + * A utility for getting the MessageQueueThread for the current thread. Once there is only one + * implementation of MessageQueueThread, this should move to that class. + */ +@DoNotStrip +public class MessageQueueThreadRegistry { + + private static ThreadLocal sMyMessageQueueThread = new ThreadLocal<>(); + + /*package*/ static void register(MessageQueueThread mqt) { + mqt.assertIsOnThread(); + sMyMessageQueueThread.set(mqt); + } + + /** + * @return the MessageQueueThread that owns the current Thread. + */ + public static MessageQueueThread myMessageQueueThread() { + return Assertions.assertNotNull( + sMyMessageQueueThread.get(), + "This thread doesn't have a MessageQueueThread registered to it!"); + } +} + diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java b/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java index 23619eccf..b2809ae37 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java +++ b/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java @@ -91,6 +91,18 @@ public class SimpleSettableFuture implements Future { return mResult; } + /** + * Convenience wrapper for {@link #get(long, TimeUnit)} that re-throws get()'s Exceptions as + * RuntimeExceptions. + */ + public @Nullable T getOrThrow(long timeout, TimeUnit unit) { + try { + return get(timeout, unit); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } + private void checkNotSet() { if (mReadyLatch.getCount() == 0) { throw new RuntimeException("Result has already been set!");