mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[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.
This commit is contained in:
parent
2d39a739ea
commit
7cd9e0f640
@ -12,6 +12,7 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_driver/driver_extension.dart';
|
||||
|
||||
import '../platform_view/_shared.dart';
|
||||
import '../src/allow_list_devices.dart';
|
||||
|
||||
void main() async {
|
||||
@ -27,32 +28,11 @@ void main() async {
|
||||
|
||||
// Run on full screen.
|
||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||
runApp(const MainApp());
|
||||
}
|
||||
|
||||
final class MainApp extends StatelessWidget {
|
||||
const MainApp({super.key});
|
||||
|
||||
// This should appear as the yellow line over a blue box. The
|
||||
// green box should not be visible unless the platform view has not loaded yet.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: <Widget>[
|
||||
SizedBox(width: 190, height: 190, child: ColoredBox(color: Colors.green)),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
height: 200,
|
||||
child: _HybridCompositionAndroidPlatformView(viewType: 'box_platform_view'),
|
||||
),
|
||||
SizedBox(width: 800, height: 25, child: ColoredBox(color: Colors.yellow)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
runApp(
|
||||
const MainApp(
|
||||
platformView: _HybridCompositionAndroidPlatformView(viewType: 'box_platform_view'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final class _HybridCompositionAndroidPlatformView extends StatelessWidget {
|
||||
@ -60,7 +40,6 @@ final class _HybridCompositionAndroidPlatformView extends StatelessWidget {
|
||||
|
||||
final String viewType;
|
||||
|
||||
// TODO(jonahwilliams): swap this out with new platform view APIs.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PlatformViewLink(
|
||||
|
@ -4,18 +4,50 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final class MainApp extends StatelessWidget {
|
||||
// This should appear as the yellow line over a blue box. The
|
||||
// green box should not be visible unless the platform view has not loaded yet.
|
||||
final class MainApp extends StatefulWidget {
|
||||
const MainApp({super.key, required this.platformView});
|
||||
|
||||
final Widget platformView;
|
||||
|
||||
@override
|
||||
State<MainApp> createState() => _MainAppState();
|
||||
}
|
||||
|
||||
class _MainAppState extends State<MainApp> {
|
||||
bool showPlatformView = true;
|
||||
|
||||
void _togglePlatformView() {
|
||||
setState(() {
|
||||
showPlatformView = !showPlatformView;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: <Widget>[
|
||||
platformView,
|
||||
Center(child: Container(width: 100, height: 100, color: Colors.red)),
|
||||
TextButton(
|
||||
key: const ValueKey<String>('AddOverlay'),
|
||||
onPressed: _togglePlatformView,
|
||||
child: const SizedBox(width: 190, height: 190, child: ColoredBox(color: Colors.green)),
|
||||
),
|
||||
if (showPlatformView) ...<Widget>[
|
||||
SizedBox(width: 200, height: 200, child: widget.platformView),
|
||||
TextButton(
|
||||
key: const ValueKey<String>('RemoveOverlay'),
|
||||
onPressed: _togglePlatformView,
|
||||
child: const SizedBox(
|
||||
width: 800,
|
||||
height: 25,
|
||||
child: ColoredBox(color: Colors.yellow),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -41,6 +41,8 @@ void main() async {
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await flutterDriver.tap(find.byValueKey('AddOverlay'));
|
||||
|
||||
await nativeDriver.close();
|
||||
await flutterDriver.close();
|
||||
});
|
||||
@ -72,4 +74,16 @@ void main() async {
|
||||
matchesGoldenFile('$goldenPrefix.platform_view_portait_rotated_back.png'),
|
||||
);
|
||||
}, timeout: Timeout.none);
|
||||
|
||||
// Note: this doesn't reset the app so if additional test cases are added
|
||||
// make sure to press the button again.
|
||||
test('should remove overlay when platform view is removed', () async {
|
||||
await flutterDriver.tap(find.byValueKey('RemoveOverlay'));
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
await expectLater(
|
||||
nativeDriver.screenshot(),
|
||||
matchesGoldenFile('$goldenPrefix.removed_overlay.png'),
|
||||
);
|
||||
}, timeout: Timeout.none);
|
||||
}
|
||||
|
@ -45,6 +45,8 @@ void main() async {
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await flutterDriver.tap(find.byValueKey('AddOverlay'));
|
||||
|
||||
await nativeDriver.close();
|
||||
await flutterDriver.close();
|
||||
});
|
||||
@ -69,4 +71,14 @@ void main() async {
|
||||
matchesGoldenFile('$goldenPrefix.blue_orange_gradient_portait_rotated_back.png'),
|
||||
);
|
||||
}, timeout: Timeout.none);
|
||||
|
||||
test('should hide overlay layer', () async {
|
||||
await flutterDriver.tap(find.byValueKey('RemoveOverlay'));
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
await expectLater(
|
||||
nativeDriver.screenshot(),
|
||||
matchesGoldenFile('$goldenPrefix.hide_overlay.png'),
|
||||
);
|
||||
}, timeout: Timeout.none);
|
||||
}
|
||||
|
@ -56,6 +56,8 @@ void main() async {
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await flutterDriver.tap(find.byValueKey('AddOverlay'));
|
||||
|
||||
await nativeDriver.close();
|
||||
await flutterDriver.close();
|
||||
});
|
||||
@ -86,4 +88,14 @@ void main() async {
|
||||
),
|
||||
);
|
||||
}, timeout: Timeout.none);
|
||||
|
||||
test('should hide overlay layer', () async {
|
||||
await flutterDriver.tap(find.byValueKey('RemoveOverlay'));
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
await expectLater(
|
||||
nativeDriver.screenshot(),
|
||||
matchesGoldenFile('$goldenPrefix.hide_overlay.png'),
|
||||
);
|
||||
}, timeout: Timeout.none);
|
||||
}
|
||||
|
@ -111,6 +111,8 @@ class MockPlatformViewAndroidJNI : public PlatformViewAndroidJNI {
|
||||
MutatorsStack mutators_stack),
|
||||
(override));
|
||||
MOCK_METHOD(void, onEndFrame2, (), (override));
|
||||
MOCK_METHOD(void, showOverlaySurface2, (), (override));
|
||||
MOCK_METHOD(void, hideOverlaySurface2, (), (override));
|
||||
MOCK_METHOD(std::unique_ptr<std::vector<std::string>>,
|
||||
FlutterViewComputePlatformResolvedLocale,
|
||||
(std::vector<std::string> supported_locales_data),
|
||||
|
@ -76,10 +76,15 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView(
|
||||
|
||||
if (!FrameHasPlatformLayers()) {
|
||||
frame->Submit();
|
||||
// If the previous frame had platform views, hide the overlay surface.
|
||||
if (previous_frame_view_count_ > 0) {
|
||||
jni_facade_->hideOverlaySurface2();
|
||||
}
|
||||
jni_facade_->applyTransaction();
|
||||
return;
|
||||
}
|
||||
|
||||
bool prev_frame_no_platform_views = previous_frame_view_count_ == 0;
|
||||
std::unordered_map<int64_t, SkRect> view_rects;
|
||||
for (auto platform_id : composition_order_) {
|
||||
view_rects[platform_id] = GetViewRect(platform_id, view_params_);
|
||||
@ -143,9 +148,13 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView(
|
||||
task_runners_.GetPlatformTaskRunner()->PostTask(fml::MakeCopyable(
|
||||
[&, composition_order = composition_order_, view_params = view_params_,
|
||||
jni_facade = jni_facade_, device_pixel_ratio = device_pixel_ratio_,
|
||||
slices = std::move(slices_)]() -> void {
|
||||
slices = std::move(slices_), prev_frame_no_platform_views]() -> void {
|
||||
jni_facade->swapTransaction();
|
||||
|
||||
if (prev_frame_no_platform_views) {
|
||||
jni_facade_->showOverlaySurface2();
|
||||
}
|
||||
|
||||
for (int64_t view_id : composition_order) {
|
||||
SkRect view_rect = GetViewRect(view_id, view_params);
|
||||
const EmbeddedViewParams& params = view_params.at(view_id);
|
||||
|
@ -1322,6 +1322,28 @@ public class FlutterJNI {
|
||||
return platformViewsController2.createOverlaySurface();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@SuppressLint("NewApi")
|
||||
@UiThread
|
||||
public void showOverlaySurface2() {
|
||||
if (platformViewsController2 == null) {
|
||||
throw new RuntimeException(
|
||||
"platformViewsController must be set before attempting to destroy an overlay surface");
|
||||
}
|
||||
platformViewsController2.showOverlaySurface();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@SuppressLint("NewApi")
|
||||
@UiThread
|
||||
public void hideOverlaySurface2() {
|
||||
if (platformViewsController2 == null) {
|
||||
throw new RuntimeException(
|
||||
"platformViewsController must be set before attempting to destroy an overlay surface");
|
||||
}
|
||||
platformViewsController2.hideOverlaySurface();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@SuppressLint("NewApi")
|
||||
@UiThread
|
||||
|
@ -7,7 +7,7 @@ package io.flutter.plugin.platform;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
class PlatformViewRegistryImpl implements PlatformViewRegistry {
|
||||
public class PlatformViewRegistryImpl implements PlatformViewRegistry {
|
||||
|
||||
PlatformViewRegistryImpl() {
|
||||
viewFactories = new HashMap<>();
|
||||
|
@ -65,6 +65,7 @@ public class PlatformViewsController2 implements PlatformViewsAccessibilityDeleg
|
||||
private final ArrayList<SurfaceControl.Transaction> pendingTransactions;
|
||||
private final ArrayList<SurfaceControl.Transaction> activeTransactions;
|
||||
private Surface overlayerSurface = null;
|
||||
private SurfaceControl overlaySurfaceControl = null;
|
||||
|
||||
public PlatformViewsController2() {
|
||||
accessibilityEventsDelegate = new AccessibilityEventsDelegate();
|
||||
@ -577,6 +578,7 @@ public class PlatformViewsController2 implements PlatformViewsAccessibilityDeleg
|
||||
tx.setLayer(surfaceControl, 1000);
|
||||
tx.apply();
|
||||
overlayerSurface = new Surface(surfaceControl);
|
||||
overlaySurfaceControl = surfaceControl;
|
||||
}
|
||||
|
||||
return new FlutterOverlaySurface(0, overlayerSurface);
|
||||
@ -586,9 +588,32 @@ public class PlatformViewsController2 implements PlatformViewsAccessibilityDeleg
|
||||
if (overlayerSurface != null) {
|
||||
overlayerSurface.release();
|
||||
overlayerSurface = null;
|
||||
overlaySurfaceControl = null;
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@RequiresApi(API_LEVELS.API_34)
|
||||
public void showOverlaySurface() {
|
||||
if (overlaySurfaceControl == null) {
|
||||
return;
|
||||
}
|
||||
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
|
||||
tx.setVisibility(overlaySurfaceControl, /*visible=*/ true);
|
||||
tx.apply();
|
||||
}
|
||||
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@RequiresApi(API_LEVELS.API_34)
|
||||
public void hideOverlaySurface() {
|
||||
if (overlaySurfaceControl == null) {
|
||||
return;
|
||||
}
|
||||
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
|
||||
tx.setVisibility(overlaySurfaceControl, /*visible=*/ false);
|
||||
tx.apply();
|
||||
}
|
||||
|
||||
//// Message Handler ///////
|
||||
|
||||
private final PlatformViewsChannel2.PlatformViewsHandler channelHandler =
|
||||
|
@ -140,6 +140,8 @@ class JNIMock final : public PlatformViewAndroidJNI {
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(void, onEndFrame2, (), (override));
|
||||
MOCK_METHOD(void, hideOverlaySurface2, (), (override));
|
||||
MOCK_METHOD(void, showOverlaySurface2, (), (override));
|
||||
|
||||
MOCK_METHOD(std::unique_ptr<std::vector<std::string>>,
|
||||
FlutterViewComputePlatformResolvedLocale,
|
||||
|
@ -239,6 +239,10 @@ class PlatformViewAndroidJNI {
|
||||
int32_t viewHeight,
|
||||
MutatorsStack mutators_stack) = 0;
|
||||
|
||||
virtual void showOverlaySurface2() = 0;
|
||||
|
||||
virtual void hideOverlaySurface2() = 0;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Computes the locale Android would select.
|
||||
///
|
||||
|
@ -160,6 +160,10 @@ static jmethodID g_on_display_platform_view2_method = nullptr;
|
||||
|
||||
static jmethodID g_on_end_frame2_method = nullptr;
|
||||
|
||||
static jmethodID g_show_overlay_surface2_method = nullptr;
|
||||
|
||||
static jmethodID g_hide_overlay_surface2_method = nullptr;
|
||||
|
||||
// Mutators
|
||||
static fml::jni::ScopedJavaGlobalRef<jclass>* g_mutators_stack_class = nullptr;
|
||||
static jmethodID g_mutators_stack_init_method = nullptr;
|
||||
@ -1052,6 +1056,20 @@ bool RegisterApi(JNIEnv* env) {
|
||||
FML_LOG(ERROR) << "Could not locate onEndFrame2 method";
|
||||
return false;
|
||||
}
|
||||
|
||||
g_show_overlay_surface2_method = env->GetMethodID(
|
||||
g_flutter_jni_class->obj(), "showOverlaySurface2", "()V");
|
||||
if (g_on_end_frame2_method == nullptr) {
|
||||
FML_LOG(ERROR) << "Could not locate showOverlaySurface2 method";
|
||||
return false;
|
||||
}
|
||||
|
||||
g_hide_overlay_surface2_method = env->GetMethodID(
|
||||
g_flutter_jni_class->obj(), "hideOverlaySurface2", "()V");
|
||||
if (g_on_end_frame2_method == nullptr) {
|
||||
FML_LOG(ERROR) << "Could not locate hideOverlaySurface2 method";
|
||||
return false;
|
||||
}
|
||||
//
|
||||
|
||||
fml::jni::ScopedJavaLocalRef<jclass> overlay_surface_class(
|
||||
@ -2184,4 +2202,28 @@ void PlatformViewAndroidJNIImpl::onEndFrame2() {
|
||||
FML_CHECK(fml::jni::CheckException(env));
|
||||
}
|
||||
|
||||
void PlatformViewAndroidJNIImpl::showOverlaySurface2() {
|
||||
JNIEnv* env = fml::jni::AttachCurrentThread();
|
||||
|
||||
auto java_object = java_object_.get(env);
|
||||
if (java_object.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
env->CallVoidMethod(java_object.obj(), g_show_overlay_surface2_method);
|
||||
FML_CHECK(fml::jni::CheckException(env));
|
||||
}
|
||||
|
||||
void PlatformViewAndroidJNIImpl::hideOverlaySurface2() {
|
||||
JNIEnv* env = fml::jni::AttachCurrentThread();
|
||||
|
||||
auto java_object = java_object_.get(env);
|
||||
if (java_object.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
env->CallVoidMethod(java_object.obj(), g_hide_overlay_surface2_method);
|
||||
FML_CHECK(fml::jni::CheckException(env));
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
@ -124,6 +124,10 @@ class PlatformViewAndroidJNIImpl final : public PlatformViewAndroidJNI {
|
||||
int32_t viewHeight,
|
||||
MutatorsStack mutators_stack) override;
|
||||
|
||||
void showOverlaySurface2() override;
|
||||
|
||||
void hideOverlaySurface2() override;
|
||||
|
||||
void onEndFrame2() override;
|
||||
|
||||
private:
|
||||
|
Loading…
Reference in New Issue
Block a user