4242import java .util .Locale ;
4343import java .util .Set ;
4444import java .util .concurrent .CopyOnWriteArraySet ;
45+ import java .util .concurrent .atomic .AtomicReference ;
4546import java .util .concurrent .locks .ReentrantReadWriteLock ;
4647
4748/**
7374 *
7475 * <pre>{@code
7576 * // Instantiate FlutterJNI and attach to the native side.
76- * FlutterJNI flutterJNI = new FlutterJNI();
77+ * FlutterJNI flutterJNI = new FlutterJNI(Looper.getMainLooper() );
7778 * flutterJNI.attachToNative();
7879 *
7980 * // Use FlutterJNI as desired. flutterJNI.dispatchPointerDataPacket(...);
@@ -107,11 +108,13 @@ public class FlutterJNI {
107108 // platform thread and doesn't require locking.
108109 private ReentrantReadWriteLock shellHolderLock = new ReentrantReadWriteLock ();
109110
110- // Prefer using the FlutterJNI.Factory so it's easier to test.
111+ /** @param looper The main looper. Typically, Looper.getMainLooper(). */
112+ public FlutterJNI (@ NonNull Looper looper ) {
113+ mainLooper = looper ;
114+ }
115+
111116 public FlutterJNI () {
112- // We cache the main looper so that we can ensure calls are made on the main thread
113- // without consistently paying the synchronization cost of getMainLooper().
114- mainLooper = Looper .getMainLooper ();
117+ this (Looper .getMainLooper ());
115118 }
116119
117120 /**
@@ -1170,8 +1173,7 @@ public FlutterOverlaySurface createOverlaySurface() {
11701173 }
11711174
11721175 @ SuppressWarnings ("unused" )
1173- @ UiThread
1174- public void destroyOverlaySurfaces () {
1176+ public void destroyOverlaySurfaces () throws Exception {
11751177 // Normally, this method is called on the Android main thread.
11761178 //
11771179 // However, it may be called from a different thread after the thread merger lease expired
@@ -1185,14 +1187,15 @@ public void destroyOverlaySurfaces() {
11851187 // method hops to the Android main thread if necessary.
11861188 //
11871189 // See Rasterizer::Teardown in C++, and FlutterEngineCache in Java.
1188- runOnMainThread (
1190+ runOnLooper (
11891191 () -> {
11901192 if (platformViewsController == null ) {
11911193 throw new RuntimeException (
11921194 "platformViewsController must be set before attempting to destroy an overlay surface" );
11931195 }
11941196 platformViewsController .destroyOverlaySurfaces ();
1195- });
1197+ },
1198+ mainLooper );
11961199 }
11971200 // ----- End Engine Lifecycle Support ----
11981201
@@ -1423,18 +1426,39 @@ private void ensureRunningOnMainThread() {
14231426 }
14241427
14251428 /**
1426- * Causes the runnable r to be run on the Android main thread. If the current thread is different,
1427- * then it adds the runnable to the message queue of the main thread.
1429+ * Causes the runnable r to be run on the thread associated with the given looper l. Then, it
1430+ * waits for the runnable to run.
1431+ *
1432+ * <p>If the runnable throws, then the exception is captured and rethrown in the current thread.
14281433 *
14291434 * @param r The runnable that will be executed.
1435+ * @param l The looper where the runnable is added.
14301436 */
1431- private void runOnMainThread (Runnable r ) {
1432- if (Looper .myLooper () == mainLooper ) {
1437+ private void runOnLooper (Runnable r , Looper l ) throws Exception {
1438+ if (Looper .myLooper () == l ) {
14331439 r .run ();
14341440 return ;
14351441 }
1436- final Handler handler = new Handler (mainLooper );
1437- handler .post (r );
1442+ final AtomicReference <Exception > exception = new AtomicReference <>();
1443+ final Handler handler = new Handler (l );
1444+ handler .post (
1445+ () -> {
1446+ try {
1447+ r .run ();
1448+ } catch (Exception e ) {
1449+ exception .set (e );
1450+ }
1451+ synchronized (handler ) {
1452+ handler .notify ();
1453+ }
1454+ });
1455+ synchronized (handler ) {
1456+ handler .wait ();
1457+ }
1458+ if (exception .get () != null ) {
1459+ // Rethrow the exception on the current thread.
1460+ throw exception .get ();
1461+ }
14381462 }
14391463
14401464 /**
0 commit comments