Skip to content

Commit a6ce951

Browse files
author
Emmanuel Garcia
authored
Fix memory leak in PlatformViewsController (flutter#27915)
1 parent 7476b03 commit a6ce951

File tree

2 files changed

+111
-17
lines changed

2 files changed

+111
-17
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
5959
private View flutterView;
6060

6161
// The texture registry maintaining the textures into which the embedded views will be rendered.
62-
private TextureRegistry textureRegistry;
62+
@Nullable private TextureRegistry textureRegistry;
6363

6464
@Nullable private TextInputPlugin textInputPlugin;
6565

@@ -79,7 +79,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
7979
// Since each virtual display has it's unique context this allows associating any view with the
8080
// platform view that
8181
// it is associated with(e.g if a platform view creates other views in the same virtual display.
82-
private final HashMap<Context, View> contextToPlatformView;
82+
@VisibleForTesting /* package */ final HashMap<Context, View> contextToPlatformView;
8383

8484
// The views returned by `PlatformView#getView()`.
8585
//
@@ -711,6 +711,10 @@ private void flushAllViews() {
711711
while (platformViews.size() > 0) {
712712
channelHandler.disposeAndroidViewForPlatformView(platformViews.keyAt(0));
713713
}
714+
715+
if (contextToPlatformView.size() > 0) {
716+
contextToPlatformView.clear();
717+
}
714718
}
715719

716720
private void initializeRootImageViewIfNeeded() {

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

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77

88
import android.content.Context;
99
import android.content.res.AssetManager;
10+
import android.graphics.SurfaceTexture;
1011
import android.util.SparseArray;
1112
import android.view.MotionEvent;
1213
import android.view.Surface;
1314
import android.view.SurfaceHolder;
1415
import android.view.SurfaceView;
1516
import android.view.View;
1617
import android.view.ViewParent;
18+
import android.widget.FrameLayout.LayoutParams;
1719
import io.flutter.embedding.android.FlutterImageView;
1820
import io.flutter.embedding.android.FlutterView;
1921
import io.flutter.embedding.android.MotionEventTracker;
@@ -32,6 +34,7 @@
3234
import io.flutter.plugin.common.MethodCall;
3335
import io.flutter.plugin.common.StandardMethodCodec;
3436
import io.flutter.plugin.localization.LocalizationPlugin;
37+
import io.flutter.view.TextureRegistry;
3538
import java.nio.ByteBuffer;
3639
import java.util.Arrays;
3740
import java.util.HashMap;
@@ -232,7 +235,7 @@ public void getPlatformViewById__hybridComposition() {
232235
attach(jni, platformViewsController);
233236

234237
// Simulate create call from the framework.
235-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
238+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
236239

237240
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
238241

@@ -259,7 +262,7 @@ public void createPlatformViewMessage__initializesAndroidView() {
259262
attach(jni, platformViewsController);
260263

261264
// Simulate create call from the framework.
262-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
265+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
263266
verify(viewFactory, times(1)).create(any(), eq(platformViewId), any());
264267
}
265268

@@ -281,7 +284,7 @@ public void createPlatformViewMessage__throwsIfViewIsNull() {
281284
attach(jni, platformViewsController);
282285

283286
// Simulate create call from the framework.
284-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
287+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
285288
assertEquals(ShadowFlutterJNI.getResponses().size(), 1);
286289

287290
assertThrows(
@@ -291,6 +294,66 @@ public void createPlatformViewMessage__throwsIfViewIsNull() {
291294
});
292295
}
293296

297+
@Test
298+
@Config(shadows = {ShadowFlutterJNI.class})
299+
public void onDetachedFromJNI_clearsPlatformViewContext() {
300+
PlatformViewsController platformViewsController = new PlatformViewsController();
301+
302+
int platformViewId = 0;
303+
assertNull(platformViewsController.getPlatformViewById(platformViewId));
304+
305+
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
306+
PlatformView platformView = mock(PlatformView.class);
307+
308+
View pv = mock(View.class);
309+
when(pv.getLayoutParams()).thenReturn(new LayoutParams(1, 1));
310+
311+
when(platformView.getView()).thenReturn(pv);
312+
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
313+
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
314+
315+
FlutterJNI jni = new FlutterJNI();
316+
attach(jni, platformViewsController);
317+
318+
// Simulate create call from the framework.
319+
createPlatformView(
320+
jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ false);
321+
322+
assertFalse(platformViewsController.contextToPlatformView.isEmpty());
323+
platformViewsController.onDetachedFromJNI();
324+
assertTrue(platformViewsController.contextToPlatformView.isEmpty());
325+
}
326+
327+
@Test
328+
@Config(shadows = {ShadowFlutterJNI.class})
329+
public void onPreEngineRestart_clearsPlatformViewContext() {
330+
PlatformViewsController platformViewsController = new PlatformViewsController();
331+
332+
int platformViewId = 0;
333+
assertNull(platformViewsController.getPlatformViewById(platformViewId));
334+
335+
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
336+
PlatformView platformView = mock(PlatformView.class);
337+
338+
View pv = mock(View.class);
339+
when(pv.getLayoutParams()).thenReturn(new LayoutParams(1, 1));
340+
341+
when(platformView.getView()).thenReturn(pv);
342+
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
343+
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
344+
345+
FlutterJNI jni = new FlutterJNI();
346+
attach(jni, platformViewsController);
347+
348+
// Simulate create call from the framework.
349+
createPlatformView(
350+
jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ false);
351+
352+
assertFalse(platformViewsController.contextToPlatformView.isEmpty());
353+
platformViewsController.onDetachedFromJNI();
354+
assertTrue(platformViewsController.contextToPlatformView.isEmpty());
355+
}
356+
294357
@Test
295358
@Config(shadows = {ShadowFlutterJNI.class})
296359
public void createPlatformViewMessage__throwsIfViewHasParent() {
@@ -311,7 +374,7 @@ public void createPlatformViewMessage__throwsIfViewHasParent() {
311374
attach(jni, platformViewsController);
312375

313376
// Simulate create call from the framework.
314-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
377+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
315378
assertEquals(ShadowFlutterJNI.getResponses().size(), 1);
316379

317380
assertThrows(
@@ -343,7 +406,7 @@ public void disposeAndroidView__hybridComposition() {
343406
attach(jni, platformViewsController);
344407

345408
// Simulate create call from the framework.
346-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
409+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
347410
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
348411

349412
assertNotNull(androidView.getParent());
@@ -354,7 +417,7 @@ public void disposeAndroidView__hybridComposition() {
354417
assertNull(androidView.getParent());
355418

356419
// Simulate create call from the framework.
357-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
420+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
358421
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
359422

360423
assertNotNull(androidView.getParent());
@@ -384,7 +447,7 @@ public void onEndFrame__destroysOverlaySurfaceAfterFrameOnFlutterSurfaceView() {
384447
jni.onFirstFrame();
385448

386449
// Simulate create call from the framework.
387-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
450+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
388451

389452
// Produce a frame that displays a platform view and an overlay surface.
390453
platformViewsController.onBeginFrame();
@@ -447,7 +510,7 @@ public void onEndFrame__removesPlatformView() {
447510
jni.onFirstFrame();
448511

449512
// Simulate create call from the framework.
450-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
513+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
451514

452515
// Simulate first frame from the framework.
453516
jni.onFirstFrame();
@@ -484,7 +547,7 @@ public void onEndFrame__removesPlatformViewParent() {
484547
jni.onFirstFrame();
485548

486549
// Simulate create call from the framework.
487-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
550+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
488551
platformViewsController.initializePlatformViewIfNeeded(platformViewId);
489552
assertEquals(flutterView.getChildCount(), 2);
490553

@@ -520,7 +583,7 @@ public void detach__destroysOverlaySurfaces() {
520583
jni.onFirstFrame();
521584

522585
// Simulate create call from the framework.
523-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
586+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
524587

525588
// Produce a frame that displays a platform view and an overlay surface.
526589
platformViewsController.onBeginFrame();
@@ -653,7 +716,7 @@ public void convertPlatformViewRenderSurfaceAsDefault() {
653716
jni.onFirstFrame();
654717

655718
// Simulate create call from the framework.
656-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
719+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
657720

658721
// Produce a frame that displays a platform view and an overlay surface.
659722
platformViewsController.onBeginFrame();
@@ -702,7 +765,7 @@ public void dontConverRenderSurfaceWhenFlagIsTrue() {
702765
synchronizeToNativeViewHierarchy(jni, platformViewsController, false);
703766

704767
// Simulate create call from the framework.
705-
createPlatformView(jni, platformViewsController, platformViewId, "testType");
768+
createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true);
706769

707770
// Produce a frame that displays a platform view and an overlay surface.
708771
platformViewsController.onBeginFrame();
@@ -734,12 +797,16 @@ private static void createPlatformView(
734797
FlutterJNI jni,
735798
PlatformViewsController platformViewsController,
736799
int platformViewId,
737-
String viewType) {
800+
String viewType,
801+
boolean hybrid) {
738802
final Map<String, Object> platformViewCreateArguments = new HashMap<>();
739-
platformViewCreateArguments.put("hybrid", true);
803+
platformViewCreateArguments.put("hybrid", hybrid);
740804
platformViewCreateArguments.put("id", platformViewId);
741805
platformViewCreateArguments.put("viewType", viewType);
742806
platformViewCreateArguments.put("direction", 0);
807+
platformViewCreateArguments.put("width", 1.0);
808+
platformViewCreateArguments.put("height", 1.0);
809+
743810
final MethodCall platformCreateMethodCall =
744811
new MethodCall("create", platformViewCreateArguments);
745812

@@ -776,7 +843,30 @@ private static FlutterView attach(
776843
executor.onAttachedToJNI();
777844

778845
final Context context = RuntimeEnvironment.application.getApplicationContext();
779-
platformViewsController.attach(context, null, executor);
846+
final TextureRegistry registry =
847+
new TextureRegistry() {
848+
public void TextureRegistry() {}
849+
850+
@Override
851+
public SurfaceTextureEntry createSurfaceTexture() {
852+
return new SurfaceTextureEntry() {
853+
@Override
854+
public SurfaceTexture surfaceTexture() {
855+
return mock(SurfaceTexture.class);
856+
}
857+
858+
@Override
859+
public long id() {
860+
return 0;
861+
}
862+
863+
@Override
864+
public void release() {}
865+
};
866+
}
867+
};
868+
869+
platformViewsController.attach(context, registry, executor);
780870

781871
final FlutterView view =
782872
new FlutterView(context, FlutterView.RenderMode.surface) {

0 commit comments

Comments
 (0)