Android Google Map PlatformView 图层异常排查与修复
1. 问题概述
澳门 Android App 的换电地图页面在部分设备上出现图层异常:用户离开换电页面后,Google Map 仍然覆盖在车况、社区、我的等其他页面上。
已确认的复现设备:
- 设备:HUAWEI nova 9(NAM-LX9)
- 系统:EMUI 13.0.0.323
- Android:Android 12,API Level 31
- 内存:8 GB
该设备高于项目配置的 minSdkVersion 24,因此问题不是 Android 系统低于 App 最低运行版本导致的。
2. 项目页面结构
相关页面结构如下:
text
MainPage
-> PageView 主导航页面切换
-> EnergyPage 换电页,使用 KeepAlive
-> ExchangeEnergyPage
-> IndexedStack 地图/列表模式切换
-> GoogleMap Android PlatformView
主要代码位置:
lib/ui/main_page.dart:使用PageView管理底部导航页面。lib/ui/energy/energy_page.dart:换电页面使用AutomaticKeepAliveClientMixin保持状态。lib/ui/energy/exchange/exchange_energy_page.dart:使用IndexedStack切换列表和地图,地图由GoogleMap创建。lib/main.dart:Google Maps Android 合成模式的全局初始化位置。
PageView、IndexedStack 和 KeepAlive 会让离开当前页面的 Widget 保持状态。正常情况下 Flutter 会正确移动或隐藏其中的 PlatformView;本次问题是在特定 Android 图形环境下,原生地图图层没有跟随 Flutter 页面状态完成合成。
3. PlatformView 和 Surface
普通 Flutter 组件由 Flutter 统一绘制:
text
Text / Image / Container
-> Flutter Canvas
-> Flutter Surface
-> 屏幕
Google Map 使用 Google Maps Android SDK,是 Android 原生视图。Flutter 通过 PlatformView 将它嵌入 Widget 树:
text
Google Maps Android SDK
-> Android 原生 View / Surface
-> Flutter PlatformView
-> 与 Flutter 页面合成
Surface 可以理解为 Android 图形系统管理的一块绘图缓冲区或图层。Google Maps 原生渲染器会持续向其中绘制地图瓦片、道路和 Marker。
GoogleMap Widget 是 Flutter 侧的控制入口,但地图像素并不完全由 Flutter Canvas 直接绘制。页面移动、裁剪、隐藏时,Flutter Engine 还需要同步处理 Android 原生视图的位置和可见性。如果图形驱动、地图渲染器或 PlatformView 合成链路出现兼容问题,就可能发生 Flutter 页面已经切换、原生地图图层仍留在屏幕上的情况。
这里的"原生 Surface"是对 Android 原生绘图层的概括,具体实现可能涉及 SurfaceView、TextureView 或 PlatformView 的纹理合成机制。
4. 合成模式
google_maps_flutter_android 支持两种主要显示模式。
4.1 Texture Layer Hybrid Composition
当前插件默认使用该模式,对应:
dart
useAndroidViewSurface = false;
原生地图先转换为纹理,再由 Flutter 合成。优点是 Flutter 渲染性能较好,Widget 变换通常也能正常应用;缺点是涉及原生 Surface、Flutter Engine、Impeller/Skia 和设备图形驱动之间的兼容链路。
4.2 Hybrid Composition
对应:
dart
useAndroidViewSurface = true;
Google Map 作为 Android 原生 View 参与 Android View 层级合成。该方式对原生视图的层级、显示和隐藏通常更可靠,但可能带来一定渲染性能损耗,尤其是在 Android 10 以下设备上。
5. 本次原因判断
根据录屏和项目结构,可以确认:
- Flutter 的底部导航和页面切换逻辑正常执行。
- 只有 Google Map 原生画面残留,地图上的 Flutter 覆盖组件没有残留。
- 问题符合 Android PlatformView 图层未正确隐藏或合成的特征。
因此,当前判断是 Google Maps PlatformView 默认纹理合成模式与该设备的 Android 12 / EMUI 图形环境存在兼容问题。
这是根据现象和渲染链路作出的判断。最终仍需在问题设备上验证修复包,并结合 adb logcat 确认实际使用的 PlatformView 和地图渲染器模式。
6. 当前修复
在 lib/main.dart 中,确保 Flutter Binding 初始化后、任何 GoogleMap 创建前执行:
dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
_configureGoogleMaps();
// 其他初始化逻辑
}
void _configureGoogleMaps() {
if (defaultTargetPlatform != TargetPlatform.android) return;
// Avoid Android platform-view surfaces remaining above subsequent pages.
// ignore: deprecated_member_use
AndroidGoogleMapsFlutter.useAndroidViewSurface = true;
}
代码作用:
- 只修改 Android,iOS 地图实现不受影响。
- 在首个地图实例创建前,将 Google Maps 切换为 Hybrid Composition。
- 不修改地图接口、定位、Marker、相机位置和页面业务逻辑。
- 让 Google Map 参与 Android View 层级合成,规避原生地图图层残留在其他 Flutter 页面上方的问题。
AndroidGoogleMapsFlutter.useAndroidViewSurface 是兼容入口,目前已标记为 deprecated。废弃的是旧配置入口,不是 Hybrid Composition 功能。本次使用它是为了基于现有 google_maps_flutter 直接依赖保持改动最小。
后续升级地图依赖时,应改用 google_maps_flutter_android 和 google_maps_flutter_platform_interface 提供的新入口,并将它们声明为直接依赖:
dart
final GoogleMapsFlutterPlatform mapsImplementation =
GoogleMapsFlutterPlatform.instance;
if (mapsImplementation is GoogleMapsFlutterAndroid) {
mapsImplementation.useAndroidViewSurface = true;
}
7. 修复取舍
收益
- 针对 Android PlatformView 图层顺序进行修复。
- 无须改造换电页、主导航或地图业务状态。
- 能覆盖底部 Tab 切换和其他 Flutter 页面覆盖地图的场景。
- iOS 行为不变。
代价
- Hybrid Composition 可能降低部分 Android 设备上的地图帧率。
- Android 10 以下设备受到的性能影响通常更明显。
- 修改是 Google Maps Android 的全局配置,项目中的其他
GoogleMap页面也会使用该模式。
8. 验证情况
已完成:
dart format lib/main.dartflutter analyze lib/main.dartflutter build apk -t lib/main.dart --debug- Android debug APK 构建成功。
静态分析只报告 lib/main.dart 原有的两个 warning:未使用的局部变量和不必要的空安全操作,与本次地图改动无关。
由于问题与设备图形合成有关,编译和模拟器验证不能替代问题真机回归。
9. 真机回归清单
优先使用原问题设备 HUAWEI nova 9:
- 冷启动 App,进入换电地图页。
- 等待地图瓦片和 Marker 完整显示。
- 依次切换车况、社区、我的页面,确认地图完全消失。
- 连续快速切换底部 Tab 20 次,确认地图不会再次覆盖页面。
- 返回换电页,确认地图能够重新显示、拖动、缩放和点击 Marker。
- 在地图页进入站点详情等二级页面,确认地图不会覆盖新路由。
- 从地图页切换到列表模式,确认地图不会覆盖列表。
- 将 App 切到后台后恢复,重复上述操作。
- 在 Android 10 以下设备补充检查地图帧率和操作响应。
建议同时回归项目中其他 Google Map 页面,例如车辆位置、轨迹和电子围栏页面。
10. 日志建议
连接问题设备后,可以重点过滤以下日志:
bash
adb logcat | rg "PlatformViewsController|GoogleMapController|Google Maps Android API|Impeller|Surface"
重点关注:
- PlatformView 使用的是纹理模式、虚拟显示还是 Hybrid Composition。
- Google Maps 使用 latest renderer 还是 legacy renderer。
- 是否出现
No TextureView found、Surface 创建/销毁失败或 Impeller 相关错误。 - 切换页面时地图 PlatformView 是否收到销毁或隐藏操作。
11. 修复未生效时的后续方案
如果 Hybrid Composition 在问题真机上仍未解决:
- 页面不可见时从 Widget 树移除
GoogleMap,主动销毁GoogleMapController,返回地图页时重新创建。 - 同时处理主 Tab、地图/列表模式和二级路由的可见性,避免 KeepAlive 长期保留地图原生视图。
- 升级 Flutter、
google_maps_flutter和 Android 实现包后重新验证。 - 将关闭 Impeller 作为定位实验,比较 Skia 与 Impeller 的表现,不建议未验证性能和其他页面后直接作为全局生产修复。
- 收集最小复现工程、Flutter 版本、插件版本和设备日志,提交 Flutter PlatformView 或 Google Maps Flutter issue。