Skip to content

Commit 7cd9e0f

Browse files
author
Jonah Williams
authored
[Android] Remove overlay when platform views are removed from screen. (#162908)
When there are no more platform views, make sure the overlay layer is hidden. When a platform view is added again, show the overlay. The show/hide allows us to avoid continually recreating and destroying the overlay surface + swapchain when a platform view slides in and out of frame. To further reduce memory usage, we could do a delayed de-allocation of the overlay layer (say after 50 frames of no platform view, destroy it). But I'm leaving this to a follow up.
1 parent 2d39a73 commit 7cd9e0f

File tree

14 files changed

+191
-32
lines changed

14 files changed

+191
-32
lines changed

dev/integration_tests/android_engine_test/lib/hcpp/platform_view_main.dart

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:flutter/rendering.dart';
1212
import 'package:flutter/services.dart';
1313
import 'package:flutter_driver/driver_extension.dart';
1414

15+
import '../platform_view/_shared.dart';
1516
import '../src/allow_list_devices.dart';
1617

1718
void main() async {
@@ -27,40 +28,18 @@ void main() async {
2728

2829
// Run on full screen.
2930
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
30-
runApp(const MainApp());
31-
}
32-
33-
final class MainApp extends StatelessWidget {
34-
const MainApp({super.key});
35-
36-
// This should appear as the yellow line over a blue box. The
37-
// green box should not be visible unless the platform view has not loaded yet.
38-
@override
39-
Widget build(BuildContext context) {
40-
return const MaterialApp(
41-
debugShowCheckedModeBanner: false,
42-
home: Stack(
43-
alignment: AlignmentDirectional.center,
44-
children: <Widget>[
45-
SizedBox(width: 190, height: 190, child: ColoredBox(color: Colors.green)),
46-
SizedBox(
47-
width: 200,
48-
height: 200,
49-
child: _HybridCompositionAndroidPlatformView(viewType: 'box_platform_view'),
50-
),
51-
SizedBox(width: 800, height: 25, child: ColoredBox(color: Colors.yellow)),
52-
],
53-
),
54-
);
55-
}
31+
runApp(
32+
const MainApp(
33+
platformView: _HybridCompositionAndroidPlatformView(viewType: 'box_platform_view'),
34+
),
35+
);
5636
}
5737

5838
final class _HybridCompositionAndroidPlatformView extends StatelessWidget {
5939
const _HybridCompositionAndroidPlatformView({required this.viewType});
6040

6141
final String viewType;
6242

63-
// TODO(jonahwilliams): swap this out with new platform view APIs.
6443
@override
6544
Widget build(BuildContext context) {
6645
return PlatformViewLink(

dev/integration_tests/android_engine_test/lib/platform_view/_shared.dart

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,50 @@
44

55
import 'package:flutter/material.dart';
66

7-
final class MainApp extends StatelessWidget {
7+
// This should appear as the yellow line over a blue box. The
8+
// green box should not be visible unless the platform view has not loaded yet.
9+
final class MainApp extends StatefulWidget {
810
const MainApp({super.key, required this.platformView});
11+
912
final Widget platformView;
1013

14+
@override
15+
State<MainApp> createState() => _MainAppState();
16+
}
17+
18+
class _MainAppState extends State<MainApp> {
19+
bool showPlatformView = true;
20+
21+
void _togglePlatformView() {
22+
setState(() {
23+
showPlatformView = !showPlatformView;
24+
});
25+
}
26+
1127
@override
1228
Widget build(BuildContext context) {
1329
return MaterialApp(
1430
debugShowCheckedModeBanner: false,
1531
home: Stack(
32+
alignment: AlignmentDirectional.center,
1633
children: <Widget>[
17-
platformView,
18-
Center(child: Container(width: 100, height: 100, color: Colors.red)),
34+
TextButton(
35+
key: const ValueKey<String>('AddOverlay'),
36+
onPressed: _togglePlatformView,
37+
child: const SizedBox(width: 190, height: 190, child: ColoredBox(color: Colors.green)),
38+
),
39+
if (showPlatformView) ...<Widget>[
40+
SizedBox(width: 200, height: 200, child: widget.platformView),
41+
TextButton(
42+
key: const ValueKey<String>('RemoveOverlay'),
43+
onPressed: _togglePlatformView,
44+
child: const SizedBox(
45+
width: 800,
46+
height: 25,
47+
child: ColoredBox(color: Colors.yellow),
48+
),
49+
),
50+
],
1951
],
2052
),
2153
);

dev/integration_tests/android_engine_test/test_driver/hcpp/platform_view_main_test.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ void main() async {
4141
});
4242

4343
tearDownAll(() async {
44+
await flutterDriver.tap(find.byValueKey('AddOverlay'));
45+
4446
await nativeDriver.close();
4547
await flutterDriver.close();
4648
});
@@ -72,4 +74,16 @@ void main() async {
7274
matchesGoldenFile('$goldenPrefix.platform_view_portait_rotated_back.png'),
7375
);
7476
}, timeout: Timeout.none);
77+
78+
// Note: this doesn't reset the app so if additional test cases are added
79+
// make sure to press the button again.
80+
test('should remove overlay when platform view is removed', () async {
81+
await flutterDriver.tap(find.byValueKey('RemoveOverlay'));
82+
await Future<void>.delayed(const Duration(seconds: 1));
83+
84+
await expectLater(
85+
nativeDriver.screenshot(),
86+
matchesGoldenFile('$goldenPrefix.removed_overlay.png'),
87+
);
88+
}, timeout: Timeout.none);
7589
}

dev/integration_tests/android_engine_test/test_driver/platform_view/texture_layer_hybrid_composition_platform_view_main_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ void main() async {
4545
});
4646

4747
tearDownAll(() async {
48+
await flutterDriver.tap(find.byValueKey('AddOverlay'));
49+
4850
await nativeDriver.close();
4951
await flutterDriver.close();
5052
});
@@ -69,4 +71,14 @@ void main() async {
6971
matchesGoldenFile('$goldenPrefix.blue_orange_gradient_portait_rotated_back.png'),
7072
);
7173
}, timeout: Timeout.none);
74+
75+
test('should hide overlay layer', () async {
76+
await flutterDriver.tap(find.byValueKey('RemoveOverlay'));
77+
await Future<void>.delayed(const Duration(seconds: 1));
78+
79+
await expectLater(
80+
nativeDriver.screenshot(),
81+
matchesGoldenFile('$goldenPrefix.hide_overlay.png'),
82+
);
83+
}, timeout: Timeout.none);
7284
}

dev/integration_tests/android_engine_test/test_driver/platform_view/virtual_display_platform_view_main_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ void main() async {
5656
});
5757

5858
tearDownAll(() async {
59+
await flutterDriver.tap(find.byValueKey('AddOverlay'));
60+
5961
await nativeDriver.close();
6062
await flutterDriver.close();
6163
});
@@ -86,4 +88,14 @@ void main() async {
8688
),
8789
);
8890
}, timeout: Timeout.none);
91+
92+
test('should hide overlay layer', () async {
93+
await flutterDriver.tap(find.byValueKey('RemoveOverlay'));
94+
await Future<void>.delayed(const Duration(seconds: 1));
95+
96+
await expectLater(
97+
nativeDriver.screenshot(),
98+
matchesGoldenFile('$goldenPrefix.hide_overlay.png'),
99+
);
100+
}, timeout: Timeout.none);
89101
}

engine/src/flutter/shell/platform/android/android_shell_holder_unittests.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ class MockPlatformViewAndroidJNI : public PlatformViewAndroidJNI {
111111
MutatorsStack mutators_stack),
112112
(override));
113113
MOCK_METHOD(void, onEndFrame2, (), (override));
114+
MOCK_METHOD(void, showOverlaySurface2, (), (override));
115+
MOCK_METHOD(void, hideOverlaySurface2, (), (override));
114116
MOCK_METHOD(std::unique_ptr<std::vector<std::string>>,
115117
FlutterViewComputePlatformResolvedLocale,
116118
(std::vector<std::string> supported_locales_data),

engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,15 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView(
7676

7777
if (!FrameHasPlatformLayers()) {
7878
frame->Submit();
79+
// If the previous frame had platform views, hide the overlay surface.
80+
if (previous_frame_view_count_ > 0) {
81+
jni_facade_->hideOverlaySurface2();
82+
}
7983
jni_facade_->applyTransaction();
8084
return;
8185
}
8286

87+
bool prev_frame_no_platform_views = previous_frame_view_count_ == 0;
8388
std::unordered_map<int64_t, SkRect> view_rects;
8489
for (auto platform_id : composition_order_) {
8590
view_rects[platform_id] = GetViewRect(platform_id, view_params_);
@@ -143,9 +148,13 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView(
143148
task_runners_.GetPlatformTaskRunner()->PostTask(fml::MakeCopyable(
144149
[&, composition_order = composition_order_, view_params = view_params_,
145150
jni_facade = jni_facade_, device_pixel_ratio = device_pixel_ratio_,
146-
slices = std::move(slices_)]() -> void {
151+
slices = std::move(slices_), prev_frame_no_platform_views]() -> void {
147152
jni_facade->swapTransaction();
148153

154+
if (prev_frame_no_platform_views) {
155+
jni_facade_->showOverlaySurface2();
156+
}
157+
149158
for (int64_t view_id : composition_order) {
150159
SkRect view_rect = GetViewRect(view_id, view_params);
151160
const EmbeddedViewParams& params = view_params.at(view_id);

engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,28 @@ public FlutterOverlaySurface createOverlaySurface2() {
13221322
return platformViewsController2.createOverlaySurface();
13231323
}
13241324

1325+
@SuppressWarnings("unused")
1326+
@SuppressLint("NewApi")
1327+
@UiThread
1328+
public void showOverlaySurface2() {
1329+
if (platformViewsController2 == null) {
1330+
throw new RuntimeException(
1331+
"platformViewsController must be set before attempting to destroy an overlay surface");
1332+
}
1333+
platformViewsController2.showOverlaySurface();
1334+
}
1335+
1336+
@SuppressWarnings("unused")
1337+
@SuppressLint("NewApi")
1338+
@UiThread
1339+
public void hideOverlaySurface2() {
1340+
if (platformViewsController2 == null) {
1341+
throw new RuntimeException(
1342+
"platformViewsController must be set before attempting to destroy an overlay surface");
1343+
}
1344+
platformViewsController2.hideOverlaySurface();
1345+
}
1346+
13251347
@SuppressWarnings("unused")
13261348
@SuppressLint("NewApi")
13271349
@UiThread

engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistryImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import java.util.HashMap;
88
import java.util.Map;
99

10-
class PlatformViewRegistryImpl implements PlatformViewRegistry {
10+
public class PlatformViewRegistryImpl implements PlatformViewRegistry {
1111

1212
PlatformViewRegistryImpl() {
1313
viewFactories = new HashMap<>();

engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController2.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public class PlatformViewsController2 implements PlatformViewsAccessibilityDeleg
6565
private final ArrayList<SurfaceControl.Transaction> pendingTransactions;
6666
private final ArrayList<SurfaceControl.Transaction> activeTransactions;
6767
private Surface overlayerSurface = null;
68+
private SurfaceControl overlaySurfaceControl = null;
6869

6970
public PlatformViewsController2() {
7071
accessibilityEventsDelegate = new AccessibilityEventsDelegate();
@@ -577,6 +578,7 @@ public FlutterOverlaySurface createOverlaySurface() {
577578
tx.setLayer(surfaceControl, 1000);
578579
tx.apply();
579580
overlayerSurface = new Surface(surfaceControl);
581+
overlaySurfaceControl = surfaceControl;
580582
}
581583

582584
return new FlutterOverlaySurface(0, overlayerSurface);
@@ -586,9 +588,32 @@ public void destroyOverlaySurface() {
586588
if (overlayerSurface != null) {
587589
overlayerSurface.release();
588590
overlayerSurface = null;
591+
overlaySurfaceControl = null;
589592
}
590593
}
591594

595+
@TargetApi(API_LEVELS.API_34)
596+
@RequiresApi(API_LEVELS.API_34)
597+
public void showOverlaySurface() {
598+
if (overlaySurfaceControl == null) {
599+
return;
600+
}
601+
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
602+
tx.setVisibility(overlaySurfaceControl, /*visible=*/ true);
603+
tx.apply();
604+
}
605+
606+
@TargetApi(API_LEVELS.API_34)
607+
@RequiresApi(API_LEVELS.API_34)
608+
public void hideOverlaySurface() {
609+
if (overlaySurfaceControl == null) {
610+
return;
611+
}
612+
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
613+
tx.setVisibility(overlaySurfaceControl, /*visible=*/ false);
614+
tx.apply();
615+
}
616+
592617
//// Message Handler ///////
593618

594619
private final PlatformViewsChannel2.PlatformViewsHandler channelHandler =

0 commit comments

Comments
 (0)