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里位置模拟的入口是 CarNavigationManagerinjectLocation() 方法。来看一段核心代码:

// 注入模拟位置
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定义了一套标准的导航状态机。我画了一张图,帮你理解这个流转过程:

导航状态机流转图 STATE_IDLE 空闲状态 STATE_ACTIVE 导航进行中 STATE_PAUSED 导航暂停 STATE_STOPPED 导航结束 start() pause() resume() stop() reset() 主动触发 用户操作 异常结束

这个状态机,每个导航App都必须遵守。为什么?因为系统UI(比如仪表盘)需要根据状态来显示不同的信息。导航中显示剩余距离,暂停时显示“已暂停”提示,结束时就切回主界面。

10.4 如何实现导航状态同步?

导航App通过 CarNavigationManagersendNavigationStatus() 方法来上报状态。系统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的核心内容就这些。位置模拟和导航状态同步,说白了就是“喂数据”和“报状态”两件事。但就是这两件事,做不好会让整个导航体验崩塌。希望今天的分享能帮你少走一些弯路。

公众号:蓝海资料掘金营,微信deep3321