/**
* 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
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!";
}
/**
* 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 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);
}
}