Android Google Map PlatformView 图层异常排查与修复

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 合成模式的全局初始化位置。

PageViewIndexedStack 和 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 原生绘图层的概括,具体实现可能涉及 SurfaceViewTextureView 或 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;
}

代码作用:

  1. 只修改 Android,iOS 地图实现不受影响。
  2. 在首个地图实例创建前,将 Google Maps 切换为 Hybrid Composition。
  3. 不修改地图接口、定位、Marker、相机位置和页面业务逻辑。
  4. 让 Google Map 参与 Android View 层级合成,规避原生地图图层残留在其他 Flutter 页面上方的问题。

AndroidGoogleMapsFlutter.useAndroidViewSurface 是兼容入口,目前已标记为 deprecated。废弃的是旧配置入口,不是 Hybrid Composition 功能。本次使用它是为了基于现有 google_maps_flutter 直接依赖保持改动最小。

后续升级地图依赖时,应改用 google_maps_flutter_androidgoogle_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.dart
  • flutter analyze lib/main.dart
  • flutter build apk -t lib/main.dart --debug
  • Android debug APK 构建成功。

静态分析只报告 lib/main.dart 原有的两个 warning:未使用的局部变量和不必要的空安全操作,与本次地图改动无关。

由于问题与设备图形合成有关,编译和模拟器验证不能替代问题真机回归。

9. 真机回归清单

优先使用原问题设备 HUAWEI nova 9:

  1. 冷启动 App,进入换电地图页。
  2. 等待地图瓦片和 Marker 完整显示。
  3. 依次切换车况、社区、我的页面,确认地图完全消失。
  4. 连续快速切换底部 Tab 20 次,确认地图不会再次覆盖页面。
  5. 返回换电页,确认地图能够重新显示、拖动、缩放和点击 Marker。
  6. 在地图页进入站点详情等二级页面,确认地图不会覆盖新路由。
  7. 从地图页切换到列表模式,确认地图不会覆盖列表。
  8. 将 App 切到后台后恢复,重复上述操作。
  9. 在 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 在问题真机上仍未解决:

  1. 页面不可见时从 Widget 树移除 GoogleMap,主动销毁 GoogleMapController,返回地图页时重新创建。
  2. 同时处理主 Tab、地图/列表模式和二级路由的可见性,避免 KeepAlive 长期保留地图原生视图。
  3. 升级 Flutter、google_maps_flutter 和 Android 实现包后重新验证。
  4. 将关闭 Impeller 作为定位实验,比较 Skia 与 Impeller 的表现,不建议未验证性能和其他页面后直接作为全局生产修复。
  5. 收集最小复现工程、Flutter 版本、插件版本和设备日志,提交 Flutter PlatformView 或 Google Maps Flutter issue。

12. 参考资料

相关推荐
哈__1 小时前
Flutter 3.44.9 + OpenHarmony7:home_widget 三方库桌面服务卡片(FormKit)的应用
flutter·华为·harmonyos
哈__3 小时前
Flutter 3.44.9 + OpenHarmony7:image_cropper三方库 图片裁剪的应用
flutter·openharmony
淡写成灰17 小时前
「Flutter 文件保存太难了?」一个插件打通 7 大平台,我把方案开源了 🎉
flutter·harmonyos
坚果的博客1 天前
Flutter 三方库 phone_state 的 OpenHarmony 适配实战
flutter
ljt27249606611 天前
Flutter笔记--本地通知
笔记·flutter
lqj_本人1 天前
Flutter 三方库 Pedometer 的鸿蒙化适配指南
flutter·华为·harmonyos
Android-Flutter2 天前
flutter GetX 详解
android·flutter
坚果的博客2 天前
Flutter OHOS 环境搭建实战:oh-3.44.9-dev 从 0 到 1 完整记录
flutter·华为·harmonyos
Crazy_MT2 天前
Android Studio 运行 Flutter iOS 真机白屏,但 Xcode 和命令行正常的排查记录
flutter·android studio