Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Fix: The Background and Overlay ImageView leak #37424

Merged
merged 4 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -1297,8 +1304,6 @@ public void detachFromFlutterEngine() {
removeView(flutterImageView);
flutterImageView = null;
}
previousRenderSurface = null;
flutterEngine = null;
}

@VisibleForTesting
Expand Down Expand Up @@ -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;
}
Expand All @@ -1377,6 +1380,7 @@ public void onFlutterUiDisplayed() {
onDone.run();
if (!(renderSurface instanceof FlutterImageView) && flutterImageView != null) {
flutterImageView.detachFromRenderer();
releaseImageView();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -1297,4 +1298,9 @@ private void removeOverlaySurfaces() {
}
overlayLayerViews.clear();
}

@VisibleForTesting
public SparseArray<PlatformOverlayView> getOverlayLayerViews() {
return overlayLayerViews;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -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();
Expand Down