diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 66e98087c4e5c..c5d92e1752f26 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -7,6 +7,7 @@ import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.SurfaceTexture; +import android.os.Looper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; @@ -17,6 +18,7 @@ import java.util.HashSet; import java.util.Set; +import io.flutter.BuildConfig; import io.flutter.embedding.engine.dart.PlatformMessageHandler; import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; import io.flutter.embedding.engine.renderer.FlutterRenderer; @@ -102,6 +104,13 @@ public class FlutterJNI { private PlatformMessageHandler platformMessageHandler; private final Set engineLifecycleListeners = new HashSet<>(); private final Set firstFrameListeners = new HashSet<>(); + private final Looper mainLooper; // cached to avoid synchronization on repeat access. + + public FlutterJNI() { + // We cache the main looper so that we can ensure calls are made on the main thread + // without consistently paying the synchronization cost of getMainLooper(). + mainLooper = Looper.getMainLooper(); + } /** * Sets the {@link FlutterRenderer.RenderSurface} delegate for the attached Flutter context. @@ -119,6 +128,7 @@ public class FlutterJNI { */ @UiThread public void setRenderSurface(@Nullable FlutterRenderer.RenderSurface renderSurface) { + ensureRunningOnMainThread(); this.renderSurface = renderSurface; } @@ -134,6 +144,7 @@ public void setRenderSurface(@Nullable FlutterRenderer.RenderSurface renderSurfa */ @UiThread public void setAccessibilityDelegate(@Nullable AccessibilityDelegate accessibilityDelegate) { + ensureRunningOnMainThread(); this.accessibilityDelegate = accessibilityDelegate; } @@ -146,6 +157,7 @@ public void setAccessibilityDelegate(@Nullable AccessibilityDelegate accessibili @SuppressWarnings("unused") @UiThread private void updateSemantics(ByteBuffer buffer, String[] strings) { + ensureRunningOnMainThread(); if (accessibilityDelegate != null) { accessibilityDelegate.updateSemantics(buffer, strings); } @@ -163,6 +175,7 @@ private void updateSemantics(ByteBuffer buffer, String[] strings) { @SuppressWarnings("unused") @UiThread private void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) { + ensureRunningOnMainThread(); if (accessibilityDelegate != null) { accessibilityDelegate.updateCustomAccessibilityActions(buffer, strings); } @@ -173,6 +186,7 @@ private void updateCustomAccessibilityActions(ByteBuffer buffer, String[] string @SuppressWarnings("unused") @UiThread private void onFirstFrame() { + ensureRunningOnMainThread(); if (renderSurface != null) { renderSurface.onFirstFrameRendered(); } @@ -209,6 +223,7 @@ private void onFirstFrame() { */ @UiThread public void setPlatformMessageHandler(@Nullable PlatformMessageHandler platformMessageHandler) { + ensureRunningOnMainThread(); this.platformMessageHandler = platformMessageHandler; } @@ -232,21 +247,25 @@ private void handlePlatformMessageResponse(int replyId, byte[] reply) { @UiThread public void addEngineLifecycleListener(@NonNull EngineLifecycleListener engineLifecycleListener) { + ensureRunningOnMainThread(); engineLifecycleListeners.add(engineLifecycleListener); } @UiThread public void removeEngineLifecycleListener(@NonNull EngineLifecycleListener engineLifecycleListener) { + ensureRunningOnMainThread(); engineLifecycleListeners.remove(engineLifecycleListener); } @UiThread public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { + ensureRunningOnMainThread(); firstFrameListeners.add(listener); } @UiThread public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { + ensureRunningOnMainThread(); firstFrameListeners.remove(listener); } @@ -254,6 +273,7 @@ public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedList //----- Start from FlutterView ----- @UiThread public void onSurfaceCreated(@NonNull Surface surface) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeSurfaceCreated(nativePlatformViewId, surface); } @@ -262,6 +282,7 @@ public void onSurfaceCreated(@NonNull Surface surface) { @UiThread public void onSurfaceChanged(int width, int height) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeSurfaceChanged(nativePlatformViewId, width, height); } @@ -270,6 +291,7 @@ public void onSurfaceChanged(int width, int height) { @UiThread public void onSurfaceDestroyed() { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeSurfaceDestroyed(nativePlatformViewId); } @@ -290,6 +312,7 @@ public void setViewportMetrics( int physicalViewInsetBottom, int physicalViewInsetLeft ) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeSetViewportMetrics( nativePlatformViewId, @@ -324,6 +347,7 @@ private native void nativeSetViewportMetrics( @UiThread public Bitmap getBitmap() { + ensureRunningOnMainThread(); ensureAttachedToNative(); return nativeGetBitmap(nativePlatformViewId); } @@ -332,6 +356,7 @@ public Bitmap getBitmap() { @UiThread public void dispatchPointerDataPacket(ByteBuffer buffer, int position) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeDispatchPointerDataPacket(nativePlatformViewId, buffer, position); } @@ -358,6 +383,7 @@ public void dispatchSemanticsAction(int id, @NonNull AccessibilityBridge.Action @UiThread public void dispatchSemanticsAction(int id, int action, ByteBuffer args, int argsPosition) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeDispatchSemanticsAction(nativePlatformViewId, id, action, args, argsPosition); } @@ -372,6 +398,7 @@ private native void nativeDispatchSemanticsAction( @UiThread public void setSemanticsEnabled(boolean enabled) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeSetSemanticsEnabled(nativePlatformViewId, enabled); } @@ -380,6 +407,7 @@ public void setSemanticsEnabled(boolean enabled) { @UiThread public void setAccessibilityFeatures(int flags) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeSetAccessibilityFeatures(nativePlatformViewId, flags); } @@ -388,6 +416,7 @@ public void setAccessibilityFeatures(int flags) { @UiThread public void registerTexture(long textureId, SurfaceTexture surfaceTexture) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeRegisterTexture(nativePlatformViewId, textureId, surfaceTexture); } @@ -396,6 +425,7 @@ public void registerTexture(long textureId, SurfaceTexture surfaceTexture) { @UiThread public void markTextureFrameAvailable(long textureId) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeMarkTextureFrameAvailable(nativePlatformViewId, textureId); } @@ -404,6 +434,7 @@ public void markTextureFrameAvailable(long textureId) { @UiThread public void unregisterTexture(long textureId) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeUnregisterTexture(nativePlatformViewId, textureId); } @@ -419,6 +450,7 @@ public boolean isAttached() { @UiThread public void attachToNative(boolean isBackgroundView) { + ensureRunningOnMainThread(); ensureNotAttachedToNative(); nativePlatformViewId = nativeAttach(this, isBackgroundView); } @@ -427,6 +459,7 @@ public void attachToNative(boolean isBackgroundView) { @UiThread public void detachFromNativeAndReleaseResources() { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeDestroy(nativePlatformViewId); nativePlatformViewId = null; @@ -441,6 +474,7 @@ public void runBundleAndSnapshotFromLibrary( @Nullable String pathToEntrypointFunction, @NonNull AssetManager assetManager ) { + ensureRunningOnMainThread(); ensureAttachedToNative(); nativeRunBundleAndSnapshotFromLibrary( nativePlatformViewId, @@ -461,6 +495,7 @@ private native void nativeRunBundleAndSnapshotFromLibrary( @UiThread public void dispatchEmptyPlatformMessage(String channel, int responseId) { + ensureRunningOnMainThread(); if (isAttached()) { nativeDispatchEmptyPlatformMessage(nativePlatformViewId, channel, responseId); } else { @@ -477,6 +512,7 @@ private native void nativeDispatchEmptyPlatformMessage( @UiThread public void dispatchPlatformMessage(String channel, ByteBuffer message, int position, int responseId) { + ensureRunningOnMainThread(); if (isAttached()) { nativeDispatchPlatformMessage( nativePlatformViewId, @@ -501,6 +537,7 @@ private native void nativeDispatchPlatformMessage( @UiThread public void invokePlatformMessageEmptyResponseCallback(int responseId) { + ensureRunningOnMainThread(); if (isAttached()) { nativeInvokePlatformMessageEmptyResponseCallback(nativePlatformViewId, responseId); } else { @@ -516,6 +553,7 @@ private native void nativeInvokePlatformMessageEmptyResponseCallback( @UiThread public void invokePlatformMessageResponseCallback(int responseId, ByteBuffer message, int position) { + ensureRunningOnMainThread(); if (isAttached()) { nativeInvokePlatformMessageResponseCallback( nativePlatformViewId, @@ -560,6 +598,15 @@ private void ensureAttachedToNative() { } } + private void ensureRunningOnMainThread() { + if (Looper.myLooper() != mainLooper) { + throw new RuntimeException( + "Methods marked with @UiThread must be executed on the main thread. Current thread: " + + Thread.currentThread().getName() + ); + } + } + /** * Delegate responsible for creating and updating Android-side caches of Flutter's semantics * tree and custom accessibility actions.