diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 9c78a3f030135..bb824555b277b 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -118,6 +118,9 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega // Map of unique IDs to views that render overlay layers. private final SparseArray overlayLayerViews; + // Platform views to be disposed on the next frame. + private ArrayList platformViewsToDispose; + // The platform view wrappers that are appended to FlutterView. // // These platform views use a TextureLayer in the framework. This is different than @@ -239,6 +242,15 @@ public void dispose(int viewId) { Log.e(TAG, "Disposing unknown platform view with id: " + viewId); return; } + + // The platform view is displayed using a PlatformViewLayer. Enqueue + // the platform view for deletion during the next frame. + final FlutterMutatorView parentView = platformViewParent.get(viewId); + if (parentView != null) { + platformViewsToDispose.add(viewId); + return; + } + if (platformView.getView() != null) { final View embeddedView = platformView.getView(); final ViewGroup pvParent = (ViewGroup) embeddedView.getParent(); @@ -280,18 +292,6 @@ public void dispose(int viewId) { viewWrappers.remove(viewId); return; } - // The platform view is displayed using a PlatformViewLayer. - final FlutterMutatorView parentView = platformViewParent.get(viewId); - if (parentView != null) { - parentView.removeAllViews(); - parentView.unsetOnDescendantFocusChangeListener(); - - final ViewGroup mutatorViewParent = (ViewGroup) parentView.getParent(); - if (mutatorViewParent != null) { - mutatorViewParent.removeView(parentView); - } - platformViewParent.remove(viewId); - } } @Override @@ -744,6 +744,7 @@ public PlatformViewsController() { viewWrappers = new SparseArray<>(); platformViews = new SparseArray<>(); platformViewParent = new SparseArray<>(); + platformViewsToDispose = new ArrayList<>(); motionEventTracker = MotionEventTracker.getInstance(); } @@ -1071,6 +1072,10 @@ private void diposeAllViews() { // Dispose deletes the entry from platformViews and clears associated resources. channelHandler.dispose(viewId); } + for (Integer viewId : platformViewsToDispose) { + disposeHybridCompositionPlatformView(viewId); + } + platformViewsToDispose.clear(); } // Invoked when the Android system is requesting we reduce memory usage. @@ -1247,6 +1252,19 @@ public void onBeginFrame() { *

This member is not intended for public use, and is only visible for testing. */ public void onEndFrame() { + ArrayList nextPlatformViewsToDispose = new ArrayList<>(); + for (Integer viewId : platformViewsToDispose) { + // Any platform views that are queued for disposal but still in the composition + // tree must survive at least one more frame, but otherwise can be deleted. + if (currentFrameUsedPlatformViewIds.contains(viewId)) { + nextPlatformViewsToDispose.add(viewId); + continue; + } + + disposeHybridCompositionPlatformView(viewId); + } + platformViewsToDispose = nextPlatformViewsToDispose; + // If there are no platform views in the current frame, // then revert the image view surface and use the previous surface. // @@ -1388,4 +1406,38 @@ private void removeOverlaySurfaces() { public SparseArray getOverlayLayerViews() { return overlayLayerViews; } + + private void disposeHybridCompositionPlatformView(int viewId) { + final PlatformView platformView = platformViews.get(viewId); + if (platformView != null) { + if (platformView.getView() != null) { + final View embeddedView = platformView.getView(); + final ViewGroup pvParent = (ViewGroup) embeddedView.getParent(); + if (pvParent != null) { + // Eagerly remove the embedded view from the PlatformViewWrapper. + // Without this call, we see some crashes because removing the view + // is used as a signal to stop processing. + pvParent.removeView(embeddedView); + } + } + platformViews.remove(viewId); + try { + platformView.dispose(); + } catch (RuntimeException exception) { + Log.e(TAG, "Disposing platform view threw an exception", exception); + } + } + + final FlutterMutatorView parentView = platformViewParent.get(viewId); + if (parentView != null) { + parentView.removeAllViews(); + parentView.unsetOnDescendantFocusChangeListener(); + + final ViewGroup mutatorViewParent = (ViewGroup) parentView.getParent(); + if (mutatorViewParent != null) { + mutatorViewParent.removeView(parentView); + } + platformViewParent.remove(viewId); + } + } } 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 af3a79e7adc22..9608bbf676c21 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -908,6 +908,10 @@ public void disposeAndroidView_hybridComposition() { // Simulate dispose call from the framework. disposePlatformView(jni, platformViewsController, platformViewId); + assertNotNull(androidView.getParent()); + + // pump frame to force disposal. + platformViewsController.onEndFrame(); assertNull(androidView.getParent()); // Simulate create call from the framework. @@ -947,6 +951,9 @@ public void disposeNullAndroidView() { // Simulate dispose call from the framework. disposePlatformView(jni, platformViewsController, platformViewId); + // pump frame to force disposal. + platformViewsController.onEndFrame(); + verify(platformView, times(1)).dispose(); } @@ -1090,6 +1097,9 @@ public void onEndFrame_removesPlatformViewParent() { // Simulate dispose call from the framework. disposePlatformView(jni, platformViewsController, platformViewId); + platformViewsController.onBeginFrame(); + platformViewsController.onEndFrame(); + assertEquals(flutterView.getChildCount(), 1); }