10、导航与地图集成:CarNavigationService,位置模拟与导航状态同步
各位同学,今天我们来聊聊车载系统里最“显眼”的一个模块——导航与地图集成。说实话,导航这个功能,用户每天上车都会盯着看。做得不好,用户会直接骂“这车机真垃圾”。我在做第一代AAOS项目时,就因为这个模块被测试团队怼过好几次。嗯,今天我把踩过的坑和沉淀下来的经验,一次性讲清楚。
10.1 CarNavigationService 是什么?
CarNavigationService,说白了就是AAOS里专门给导航应用提供的一个系统级服务。它不负责渲染地图,也不负责算路。它的核心职责就两个:位置模拟和导航状态同步。
你想想看,手机上的导航App可以直接用GPS。但在车上,GPS信号可能被隧道、高楼遮挡。这时候系统需要提供一个“模拟位置”的能力,让导航App以为它还在正常定位。这就是CarNavigationService存在的意义之一。
核心职责总结:
- 提供标准化的位置数据接口
- 管理导航状态的流转(开始导航、途中、结束)
- 支持位置模拟(用于测试或信号补偿)
- 向系统UI同步导航信息(下一路口、剩余距离等)
10.2 位置模拟:为什么需要它?
我在项目中遇到过这样一个场景:测试工程师在地下停车场里反复测试导航,GPS信号断断续续,根本没法复现线上bug。后来我给他们写了一个位置模拟工具,直接在adb shell里注入经纬度,问题一下子就定位了。
位置模拟有两种典型用途:
- 开发测试:模拟各种路况、隧道、高架桥场景
- 信号补偿:当GPS信号弱时,系统用IMU数据推算位置,再通过CarNavigationService喂给导航App
AAOS里位置模拟的入口是 CarNavigationManager 的 injectLocation() 方法。来看一段核心代码:
// 注入模拟位置
CarNavigationManager navManager =
(CarNavigationManager) car.getCarManager(Car.CAR_NAVIGATION_SERVICE);
Location mockLocation = new Location(LocationManager.GPS_PROVIDER);
mockLocation.setLatitude(39.9042);
mockLocation.setLongitude(116.4074);
mockLocation.setAccuracy(5.0f);
mockLocation.setTime(System.currentTimeMillis());
// 注意:需要 CAR_INJECT_LOCATION 权限
navManager.injectLocation(mockLocation);
我曾经踩过的坑:注入位置时忘记设置 setTime(),结果导航App一直用旧时间戳计算速度,显示的车速忽快忽慢。后来排查了半天才发现是时间戳没更新。记住,每次注入都要带上当前时间戳。
10.3 导航状态同步机制
导航状态同步,是CarNavigationService另一个核心能力。它负责把导航App内部的“状态”广播给系统UI。比如:
- 当前是否在导航中
- 下一个转弯路口还有多远
- 预计到达时间
- 车道引导信息
AAOS定义了一套标准的导航状态机。我画了一张图,帮你理解这个流转过程:
这个状态机,每个导航App都必须遵守。为什么?因为系统UI(比如仪表盘)需要根据状态来显示不同的信息。导航中显示剩余距离,暂停时显示“已暂停”提示,结束时就切回主界面。
10.4 如何实现导航状态同步?
导航App通过 CarNavigationManager 的 sendNavigationStatus() 方法来上报状态。系统UI则通过注册回调来接收。来看一个完整的示例:
// 导航App端:上报导航状态
CarNavigationManager.CarNavigationCallback callback =
new CarNavigationManager.CarNavigationCallback() {
@Override
public void onNavigationStateChanged(int state, Bundle data) {
// 系统UI会收到这个回调
// state 取值:STATE_IDLE, STATE_ACTIVE, STATE_PAUSED, STATE_STOPPED
// data 里可以携带:下一路口名称、剩余距离、预计时间等
}
};
// 注册回调
navManager.registerNavigationCallback(callback);
// 上报当前导航状态
Bundle navData = new Bundle();
navData.putString("next_turn", "朝阳路");
navData.putInt("distance_meters", 500);
navData.putInt("time_seconds", 30);
navManager.sendNavigationStatus(CarNavigationManager.STATE_ACTIVE, navData);
我的个人习惯:在 Bundle 里塞数据时,key 最好用常量定义,不要直接写字符串。我曾经因为 "next_turn" 和 "next_turn_name" 两个key混用,导致仪表盘上一直显示“未知路口”。后来统一用常量类管理,再也没出过问题。
10.5 位置模拟的权限与安全
位置模拟是个敏感能力。你想啊,如果任意App都能注入位置,那导航App收到的就是假数据,用户可能被误导。所以AAOS对位置模拟做了严格的权限控制。
| 权限名称 | 保护级别 | 说明 |
|---|---|---|
| android.car.permission.CAR_INJECT_LOCATION | signature|privileged | 只有系统应用或签名应用才能使用 |
| android.car.permission.CAR_NAVIGATION_MANAGER | signature | 访问CarNavigationService的基础权限 |
我记得有一次,第三方导航App想接入位置模拟功能,结果一直报权限错误。后来发现他们用的是debug签名,而系统要求的是platform签名。这个坑,很多刚接触AAOS的开发者都会遇到。
10.6 实战:搭建一个位置模拟测试工具
光说不练假把式。我带你写一个简单的命令行工具,用来模拟车辆行驶轨迹。这个工具我在项目里用了两年,非常实用。
// 模拟车辆沿直线行驶
public class LocationSimulator {
private CarNavigationManager navManager;
public void simulateRoute(double startLat, double startLng,
double endLat, double endLng, int steps) {
double latStep = (endLat - startLat) / steps;
double lngStep = (endLng - startLng) / steps;
for (int i = 0; i <= steps; i++) {
Location loc = new Location("simulator");
loc.setLatitude(startLat + latStep * i);
loc.setLongitude(startLng + lngStep * i);
loc.setAccuracy(3.0f);
loc.setTime(System.currentTimeMillis());
navManager.injectLocation(loc);
// 每步间隔1秒,模拟真实行驶
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 忽略中断
}
}
}
}
注意:这个工具只能在userdebug或eng版本上运行。正式版系统会拒绝非系统App的位置注入请求。另外,模拟时不要注入太快,否则导航App的路径平滑算法会出问题,显示的车身方向会乱转。
10.7 导航状态同步的最佳实践
最后,我总结几条我在实战中沉淀下来的经验:
- 状态上报要及时:导航状态变化后,必须在100ms内上报。否则仪表盘会显示旧信息,用户会困惑。
- 数据Bundle不要太大:系统UI和导航App是跨进程通信,Bundle过大会导致ANR。我一般控制在10个key以内。
- 处理异常状态:GPS信号丢失时,导航App应该主动上报STATE_PAUSED,而不是一直卡在STATE_ACTIVE。我曾经见过一个App,进隧道后导航状态还是“进行中”,结果用户出隧道时发现路线早就偏了。
- 测试要覆盖边界:比如导航刚启动就立即结束、连续快速切换状态等。这些场景最容易出bug。
好了,关于CarNavigationService的核心内容就这些。位置模拟和导航状态同步,说白了就是“喂数据”和“报状态”两件事。但就是这两件事,做不好会让整个导航体验崩塌。希望今天的分享能帮你少走一些弯路。