From a861a66d71d61a3b6e6d3f014ffb4502eae850e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 28 Feb 2024 09:53:18 -0800 Subject: [PATCH] Add uiThreadConditionalSync executor to AndroidExecutors Summary: Changelog: [internal] This adds a new type of executor in AndroidExecutors to execute runnables on the UI thread. If the caller is already on the UI thread it'd call the runnable immediately. Otherwise it'd be scheduled in the UI thread to execute asynchronously. Reviewed By: huntie Differential Revision: D53941120 fbshipit-source-id: b68c7a4540be2a12df930e4e52eeb7b7a1aa91d8 --- .../ReactAndroid/api/ReactAndroid.api | 1 + .../internal/bolts/AndroidExecutors.java | 44 +++++++++++++++++-- .../react/runtime/internal/bolts/Task.java | 4 ++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 903db80d5c0..10388444d06 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -3673,6 +3673,7 @@ public abstract interface class com/facebook/react/runtime/internal/bolts/Contin public class com/facebook/react/runtime/internal/bolts/Task : com/facebook/react/interfaces/TaskInterface { public static final field BACKGROUND_EXECUTOR Ljava/util/concurrent/ExecutorService; + public static final field UI_THREAD_CONDITIONAL_SYNC_EXECUTOR Ljava/util/concurrent/Executor; public static final field UI_THREAD_EXECUTOR Ljava/util/concurrent/Executor; public static fun call (Ljava/util/concurrent/Callable;)Lcom/facebook/react/runtime/internal/bolts/Task; public static fun call (Ljava/util/concurrent/Callable;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/AndroidExecutors.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/AndroidExecutors.java index 49518ccc90d..dc4e691dc0b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/AndroidExecutors.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/AndroidExecutors.java @@ -39,9 +39,11 @@ final class AndroidExecutors { private static final AndroidExecutors INSTANCE = new AndroidExecutors(); @NonNull private final Executor uiThread; + @NonNull private final Executor uiThreadConditionalSync; private AndroidExecutors() { uiThread = new UIThreadExecutor(); + uiThreadConditionalSync = new UIThreadConditionalSyncExecutor(); } /** @@ -122,16 +124,52 @@ final class AndroidExecutors { } } - /** An {@link java.util.concurrent.Executor} that executes tasks on the UI thread. */ + /** + * An {@link java.util.concurrent.Executor} that schedules tasks to run asynchronously on the UI + * thread. + */ public static @NonNull Executor uiThread() { return INSTANCE.uiThread; } - /** An {@link java.util.concurrent.Executor} that runs tasks on the UI thread. */ + /** + * An {@link java.util.concurrent.Executor} that runs tasks on the UI thread (immediately if + * already on that thread). + */ + public static @NonNull Executor uiThreadConditionalSync() { + return INSTANCE.uiThreadConditionalSync; + } + + /** + * An {@link java.util.concurrent.Executor} that schedules tasks to run asynchronously on the UI + * thread. + */ private static class UIThreadExecutor implements Executor { + private final Handler mHandler = new Handler(Looper.getMainLooper()); + @Override public void execute(@NonNull Runnable command) { - new Handler(Looper.getMainLooper()).post(command); + // Otherwise, post it on the main thread handler + mHandler.post(command); + } + } + + /** + * An {@link java.util.concurrent.Executor} that runs tasks on the UI thread (immediately if + * already on that thread). + */ + private static class UIThreadConditionalSyncExecutor implements Executor { + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + @Override + public void execute(@NonNull Runnable command) { + if (Looper.myLooper() == mHandler.getLooper()) { + // If we're already on the main thread, execute the command immediately + command.run(); + } else { + // Otherwise, post it on the main thread handler + mHandler.post(command); + } } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Task.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Task.java index 1f4db71e336..634fb318a15 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Task.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Task.java @@ -42,6 +42,10 @@ public class Task implements TaskInterface { /** An {@link java.util.concurrent.Executor} that executes tasks on the UI thread. */ public static final Executor UI_THREAD_EXECUTOR = AndroidExecutors.uiThread(); + /** An {@link java.util.concurrent.Executor} that executes tasks on the UI thread. */ + public static final Executor UI_THREAD_CONDITIONAL_SYNC_EXECUTOR = + AndroidExecutors.uiThreadConditionalSync(); + /** * Interface for handlers invoked when a failed {@code Task} is about to be finalized, but the * exception has not been consumed.