diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index dd0026c3bab50..a0cf5c1fb2eb6 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1289,6 +1289,13 @@ public void detachFromFlutterEngine() { } renderSurface.detachFromRenderer(); + releaseImageView(); + + previousRenderSurface = null; + flutterEngine = null; + } + + private void releaseImageView() { if (flutterImageView != null) { flutterImageView.closeImageReader(); // Remove the FlutterImageView that was previously added by {@code convertToImageView} to @@ -1297,8 +1304,6 @@ public void detachFromFlutterEngine() { removeView(flutterImageView); flutterImageView = null; } - previousRenderSurface = null; - flutterEngine = null; } @VisibleForTesting @@ -1352,14 +1357,12 @@ public void revertImageView(@NonNull Runnable onDone) { } renderSurface = previousRenderSurface; previousRenderSurface = null; - if (flutterEngine == null) { - flutterImageView.detachFromRenderer(); - onDone.run(); - return; - } + final FlutterRenderer renderer = flutterEngine.getRenderer(); - if (renderer == null) { + + if (flutterEngine == null || renderer == null) { flutterImageView.detachFromRenderer(); + releaseImageView(); onDone.run(); return; } @@ -1377,6 +1380,7 @@ public void onFlutterUiDisplayed() { onDone.run(); if (!(renderSurface instanceof FlutterImageView) && flutterImageView != null) { flutterImageView.detachFromRenderer(); + releaseImageView(); } } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 87a04f567127e..2b2e97e5f3ce3 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -1205,6 +1205,7 @@ private void finishFrame(boolean isFrameRenderedUsingImageReaders) { } // Hide overlay surfaces that aren't rendered in the current frame. overlayView.setVisibility(View.GONE); + flutterView.removeView(overlayView); } } @@ -1297,4 +1298,9 @@ private void removeOverlaySurfaces() { } overlayLayerViews.clear(); } + + @VisibleForTesting + public SparseArray getOverlayLayerViews() { + return overlayLayerViews; + } } diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 5dd43edc132ce..87fb8eb8cee98 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -2,6 +2,8 @@ import static android.os.Looper.getMainLooper; import static io.flutter.embedding.engine.systemchannels.PlatformViewsChannel.PlatformViewTouch; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -1214,6 +1216,74 @@ public void reattachToFlutterView() { verify(newFlutterView, times(1)).addView(any(PlatformViewWrapper.class)); } + @Config( + shadows = { + ShadowFlutterSurfaceView.class, + ShadowFlutterJNI.class, + ShadowPlatformTaskQueue.class + }) + public void revertImageViewAndRemoveImageView() { + final PlatformViewsController platformViewsController = new PlatformViewsController(); + + final int platformViewId = 0; + assertNull(platformViewsController.getPlatformViewById(platformViewId)); + + final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class); + final PlatformView platformView = mock(PlatformView.class); + final View androidView = mock(View.class); + when(platformView.getView()).thenReturn(androidView); + when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView); + + platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); + + final FlutterJNI jni = new FlutterJNI(); + jni.attachToNative(); + + final FlutterView flutterView = attach(jni, platformViewsController); + + jni.onFirstFrame(); + + // Simulate create call from the framework. + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); + + // The simulation creates an Overlay on top of the PlatformView + // This is going to be called `flutterView.convertToImageView` + platformViewsController.createOverlaySurface(); + platformViewsController.onDisplayOverlaySurface(platformViewId, 0, 0, 10, 10); + + // This will contain three views: Background ImageView、PlatformView、Overlay ImageView + assertEquals(flutterView.getChildCount(), 3); + + FlutterImageView imageView = flutterView.getCurrentImageSurface(); + + // Make sure the ImageView is inside the current FlutterView. + assertTrue(imageView != null); + assertTrue(flutterView.indexOfChild(imageView) != -1); + + // Make sure the overlayView is inside the current FlutterView + assertTrue(platformViewsController.getOverlayLayerViews().size() != 0); + PlatformOverlayView overlayView = platformViewsController.getOverlayLayerViews().get(0); + assertTrue(overlayView != null); + assertTrue(flutterView.indexOfChild(overlayView) != -1); + + // Simulate in a new frame, there's no PlatformView, which is called + // `flutterView.revertImageView`. And register a `FlutterUiDisplayListener` callback. + // During callback execution it will invoke `flutterImageView.detachFromRenderer()`. + platformViewsController.onBeginFrame(); + platformViewsController.onEndFrame(); + + // Invoke all registered `FlutterUiDisplayListener` callback + jni.onFirstFrame(); + + assertEquals(null, flutterView.getCurrentImageSurface()); + + // Make sure the background ImageVIew is not in the FlutterView + assertTrue(flutterView.indexOfChild(imageView) == -1); + + // Make sure the overlay ImageVIew is not in the FlutterView + assertTrue(flutterView.indexOfChild(overlayView) == -1); + } + private static ByteBuffer encodeMethodCall(MethodCall call) { final ByteBuffer buffer = StandardMethodCodec.INSTANCE.encodeMethodCall(call); buffer.rewind();