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

Commit abb68f4

Browse files
authored
Fix: The Background and Overlay ImageView leak (#37424)
* remove image * add test * remove
1 parent 972e8d8 commit abb68f4

File tree

3 files changed

+88
-8
lines changed

3 files changed

+88
-8
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterView.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,13 @@ public void detachFromFlutterEngine() {
12891289
}
12901290
renderSurface.detachFromRenderer();
12911291

1292+
releaseImageView();
1293+
1294+
previousRenderSurface = null;
1295+
flutterEngine = null;
1296+
}
1297+
1298+
private void releaseImageView() {
12921299
if (flutterImageView != null) {
12931300
flutterImageView.closeImageReader();
12941301
// Remove the FlutterImageView that was previously added by {@code convertToImageView} to
@@ -1297,8 +1304,6 @@ public void detachFromFlutterEngine() {
12971304
removeView(flutterImageView);
12981305
flutterImageView = null;
12991306
}
1300-
previousRenderSurface = null;
1301-
flutterEngine = null;
13021307
}
13031308

13041309
@VisibleForTesting
@@ -1352,14 +1357,12 @@ public void revertImageView(@NonNull Runnable onDone) {
13521357
}
13531358
renderSurface = previousRenderSurface;
13541359
previousRenderSurface = null;
1355-
if (flutterEngine == null) {
1356-
flutterImageView.detachFromRenderer();
1357-
onDone.run();
1358-
return;
1359-
}
1360+
13601361
final FlutterRenderer renderer = flutterEngine.getRenderer();
1361-
if (renderer == null) {
1362+
1363+
if (flutterEngine == null || renderer == null) {
13621364
flutterImageView.detachFromRenderer();
1365+
releaseImageView();
13631366
onDone.run();
13641367
return;
13651368
}
@@ -1377,6 +1380,7 @@ public void onFlutterUiDisplayed() {
13771380
onDone.run();
13781381
if (!(renderSurface instanceof FlutterImageView) && flutterImageView != null) {
13791382
flutterImageView.detachFromRenderer();
1383+
releaseImageView();
13801384
}
13811385
}
13821386

shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,7 @@ private void finishFrame(boolean isFrameRenderedUsingImageReaders) {
12151215
}
12161216
// Hide overlay surfaces that aren't rendered in the current frame.
12171217
overlayView.setVisibility(View.GONE);
1218+
flutterView.removeView(overlayView);
12181219
}
12191220
}
12201221

@@ -1307,4 +1308,9 @@ private void removeOverlaySurfaces() {
13071308
}
13081309
overlayLayerViews.clear();
13091310
}
1311+
1312+
@VisibleForTesting
1313+
public SparseArray<PlatformOverlayView> getOverlayLayerViews() {
1314+
return overlayLayerViews;
1315+
}
13101316
}

shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static android.os.Looper.getMainLooper;
44
import static io.flutter.embedding.engine.systemchannels.PlatformViewsChannel.PlatformViewTouch;
5+
import static junit.framework.Assert.assertEquals;
6+
import static junit.framework.Assert.assertTrue;
57
import static org.junit.Assert.*;
68
import static org.mockito.ArgumentMatchers.*;
79
import static org.mockito.Mockito.*;
@@ -1240,6 +1242,74 @@ public void reattachToFlutterView() {
12401242
verify(newFlutterView, times(1)).addView(any(PlatformViewWrapper.class));
12411243
}
12421244

1245+
@Config(
1246+
shadows = {
1247+
ShadowFlutterSurfaceView.class,
1248+
ShadowFlutterJNI.class,
1249+
ShadowPlatformTaskQueue.class
1250+
})
1251+
public void revertImageViewAndRemoveImageView() {
1252+
final PlatformViewsController platformViewsController = new PlatformViewsController();
1253+
1254+
final int platformViewId = 0;
1255+
assertNull(platformViewsController.getPlatformViewById(platformViewId));
1256+
1257+
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
1258+
final PlatformView platformView = mock(PlatformView.class);
1259+
final View androidView = mock(View.class);
1260+
when(platformView.getView()).thenReturn(androidView);
1261+
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
1262+
1263+
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
1264+
1265+
final FlutterJNI jni = new FlutterJNI();
1266+
jni.attachToNative();
1267+
1268+
final FlutterView flutterView = attach(jni, platformViewsController);
1269+
1270+
jni.onFirstFrame();
1271+
1272+
// Simulate create call from the framework.
1273+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
1274+
1275+
// The simulation creates an Overlay on top of the PlatformView
1276+
// This is going to be called `flutterView.convertToImageView`
1277+
platformViewsController.createOverlaySurface();
1278+
platformViewsController.onDisplayOverlaySurface(platformViewId, 0, 0, 10, 10);
1279+
1280+
// This will contain three views: Background ImageView、PlatformView、Overlay ImageView
1281+
assertEquals(flutterView.getChildCount(), 3);
1282+
1283+
FlutterImageView imageView = flutterView.getCurrentImageSurface();
1284+
1285+
// Make sure the ImageView is inside the current FlutterView.
1286+
assertTrue(imageView != null);
1287+
assertTrue(flutterView.indexOfChild(imageView) != -1);
1288+
1289+
// Make sure the overlayView is inside the current FlutterView
1290+
assertTrue(platformViewsController.getOverlayLayerViews().size() != 0);
1291+
PlatformOverlayView overlayView = platformViewsController.getOverlayLayerViews().get(0);
1292+
assertTrue(overlayView != null);
1293+
assertTrue(flutterView.indexOfChild(overlayView) != -1);
1294+
1295+
// Simulate in a new frame, there's no PlatformView, which is called
1296+
// `flutterView.revertImageView`. And register a `FlutterUiDisplayListener` callback.
1297+
// During callback execution it will invoke `flutterImageView.detachFromRenderer()`.
1298+
platformViewsController.onBeginFrame();
1299+
platformViewsController.onEndFrame();
1300+
1301+
// Invoke all registered `FlutterUiDisplayListener` callback
1302+
jni.onFirstFrame();
1303+
1304+
assertEquals(null, flutterView.getCurrentImageSurface());
1305+
1306+
// Make sure the background ImageVIew is not in the FlutterView
1307+
assertTrue(flutterView.indexOfChild(imageView) == -1);
1308+
1309+
// Make sure the overlay ImageVIew is not in the FlutterView
1310+
assertTrue(flutterView.indexOfChild(overlayView) == -1);
1311+
}
1312+
12431313
private static ByteBuffer encodeMethodCall(MethodCall call) {
12441314
final ByteBuffer buffer = StandardMethodCodec.INSTANCE.encodeMethodCall(call);
12451315
buffer.rewind();

0 commit comments

Comments
 (0)