Appearance
抢占 GL 上下文根因突破与审图包 so 化改造
高德审图包要在无车机 ROM 的模拟器上跑通「高德 SDK + Unity SR」双渲染引擎,路上踩了两类根因级问题:启动时两个引擎抢同一个 GL 上下文导致花屏,生产 native so 依赖车机 VHAL 根本加载不起来。前者靠时序串行化,后者靠一次正式的编译变体改造。
启动延迟1500ms
冷启动验证20 次 0 花屏
审图仓瘦身≈4000 文件
背景
审图是高德地图上车前的合规环节:备审材料加走查约 1 周,正式提交后 20–25 个工作日,被打回来时间就没谱了。为了能随时给高德出一个「能跑的审图包」,我们维护了 gblmapreview 这个宿主仓,用来在模拟器/平板上验证 AutoSDK/GBL、XData 路由和 Unity/Napa SR 渲染。
这个仓历史上背着两座大山:一是双渲染引擎共存的稳定性问题,二是仓内维护着一整份 module_xdata 的 C++ 源码,上游 guideengine 每改一次 native,审图仓就要跟迁、跟编、跟解冲突。
问题
问题一:GL 上下文抢占
一张图集成高德 SDK 后,启动画面时好时坏——时而黑屏、时而花屏、时而正常。排查路径很典型:
- 怀疑 Unity 渲染问题 → Profiler 里渲染帧正常,排除;
- 怀疑 GPU 驱动 → 换多台设备问题一致,排除;
- 加 GL 调用日志 → 抓到现行:Unity 和高德 SDK 在同一帧内交替调用 GL API。
根因:两个引擎各自创建 GL Context,启动期同一时刻对同一个 EGL Surface 做 swapBuffer,渲染管线直接乱掉。谁抢到什么时序完全随机,所以现象才时有时无。
问题二:生产 so 离不开车机 ROM
直接拷生产 guideengine 的 so 进审图 APK,dlopen 阶段就死在 libXpVehiclHalClient.so not found——生产 so 链接了车机 VHAL 客户端,模拟器上没有这个 ROM 服务。于是「能跑的审图包」和「最新的 guideengine 真相源」长期脱节,每次 Wave 迁移都重新烧一遍时间。
方案
GL 抢占:串行初始化 + 互斥唤醒
地图首帧就绪(高德 GL Context 稳定)
↓
postDelay 1500ms
↓
拉起 Unity(此时高德已过活跃期)关键认知:两个引擎可以共存,但不能同时初始化。把启动从并行改成串行,1500ms 的延迟藏在启动画面里,用户无感知。
so 化:真相源出变体,审图仓只消费
mermaid
flowchart TB
subgraph GE["guideengine(真相源)"]
SRC["module_xdata C++ / CMake"] --> FLAG["XDATA_REVIEW_DEMO<br>-PreviewDemoEnable=true"]
FLAG --> BUILD["assembleDebug<br>裁剪 VHAL / CarService"]
BUILD --> OUT["jniPreLibs 三件套<br>xdata_sdk / xdata_native / RenderExtend"]
end
OUT --> PS["sync 脚本"]
PS --> PRE["gblmapreview<br>jniPreLibs + libs(含 libprotobuf)"]
PRE --> APK["审图 APK"]
APK --> RT["运行时:高德模拟导航 → GBL → XData → RenderExtend → Napa"]选型的关键不是「能不能用 so」,而是「能不能编出一份不依赖 VHAL 的 so」:审图真正需要的是 GBL 帧路由、RenderExtend 和 Unity 回调契约,VHAL 只是车机信号入口。把它从编译图里裁掉,so 就能在无 ROM 环境跑起来。
关键实现
编译变体
在 XDataPlatform.cmake 增加 XDATA_REVIEW_DEMO option,Gradle 属性 -PreviewDemoEnable=true 映射为 -DreviewDemoEnable=ON,开启后注入 XDATA_REVIEW_DEMO=1 和 XDATA_REVIEW_NO_CARSERVICE_SOURCE=1 两个宏:不链接 XpVehiclHalClient、不编译依赖 CarService 的源路径、管理器侧跳过注册。
只裁链接不够,还得把审图仓已验证的行为回灌真相源:gbl_ready_ 门控(render 未就绪不推帧,避免启动竞态)、gaode-skip 日志(预期跳过可观测,避免误判数据断链)、ParsePolicyGD 解析分支补齐、TilePBMetaInfo 的 ABI 对齐 static_assert、SetMVPMatrix 空实现 stub。
审图仓改造
- 删除本地可编译的
src/main/cpp/、jni/、CMakeLists.txt,约 4000 个文件,只留 Java/Kotlin 桥、assets、libs 和 sync 脚本; EnableCompileXData=true直接 fail-fast,提示去 guideengine 编 so;jniLibs.srcDirs同时包含jniPreLibs和libs——后者提供libprotobuf.so,只拷三件套会在运行时炸UnsatisfiedLinkError;- 日常流水线固化成一键脚本:编 so → sync 三件套 → 打 APK → 装模拟器跑模拟导航 smoke。
验证手段
- ELF 自检:
llvm-readelf -d查不到XpVehiclHalClient,llvm-nm --undefined-only查不到XpVehicleHalApi; - 启动日志按序出现
GblMapDataRouter initialized→SetGblUnityFramePtrCallback: registered→render_ready=1 -> RESUME; - GL 修复后台架 20 次冷启动,0 花屏/黑屏,路测通过。
指标
| 指标 | 结果 |
|---|---|
| Unity 拉起延迟 | 1500ms(启动画面内不可见) |
| 冷启动花屏/黑屏 | 20 次台架测试 0 复现 |
| 审图仓 native 源树 | 删除约 4000 文件,改 prebuilt 消费 |
| 审图包构建 | 从「跟迁跟编跟解冲突」变为「sync + assembleDebug」 |
复盘
- 多渲染引擎共存时,GL 上下文所有权是第一优先级——不要假设第三方 SDK 会让出上下文,启动时序要么串行,要么显式互斥。
- 时序类 bug 每次验证都要重启等启动,加 GL 调用日志是唯一性价比高的手段,猜是猜不出来的。
- 「删 4000 个文件」不是清理癖,是把真相源收敛到一个仓:native 只在 guideengine 演进,审图仓 fail-fast 禁止本地编译,脱节问题从根上消失。
- 裁依赖只改链接图不够,行为差异(门控、解析分支、stub)必须随变体一起回灌真相源,否则 prebuilt 和本地编出来的 so 行为不一致,排查时两边对不上。