19、地图与导航:集成第三方地图 SDK、导航栏与 HUD 显示、路线规划

车机上的地图导航,说实话,是用户每天都会盯着看的功能。你想想看,一个 Android Automotive OS 的车机,如果导航做不好,用户的第一反应就是“这车机不行”。我在几个量产项目里都踩过地图集成的坑,今天就把这些经验掰开揉碎了讲给你听。

19.1 第三方地图 SDK 的集成策略

Android Automotive OS 本身没有内置地图引擎。我们得选一个第三方 SDK。国内主流是高德、百度,海外是 Google Maps、TomTom。我个人习惯在项目初期就定好 SDK 版本,因为后期换 SDK 的成本极高——我见过一个团队因为没提前确认 API 兼容性,导致导航模块重写了三遍。

19.1.1 集成前的关键检查项

检查项 说明 我的建议
API 密钥管理 SDK 需要 API Key 才能使用 用 Android Keystore 加密存储,别硬编码
权限声明 定位、网络、存储等权限 在 Automotive 的 manifest 里单独声明
SDK 大小 部分 SDK 超过 50MB 建议用动态 feature 模块按需下载
合规要求 数据隐私、地图数据离线存储 提前和法务确认,尤其是海外市场
⚠️ 我曾经踩过的坑: 有一次集成了某地图 SDK 的 4.x 版本,结果发现它在 Android 12 的车机上频繁崩溃。原因是 SDK 内部用了非 SDK 接口,被系统限制了。后来我们不得不紧急回退到 3.8 版本。所以,集成前一定要在目标 Android 版本上做充分测试。

19.1.2 集成步骤(以高德地图为例)

其实步骤都差不多。核心就三步:引入依赖、初始化、显示地图。

// build.gradle (Module)
dependencies {
    implementation 'com.amap.api:navi-3dmap:9.8.0'
    implementation 'com.amap.api:search:9.8.0'
}

// Application 中初始化
public class CarApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        AMapNavi.setApiKey("your_api_key_here");
        // 嗯,这里要注意:初始化最好放在主线程,但别阻塞太久
    }
}

// 在 Fragment 或 Activity 中显示地图
public class MapFragment extends Fragment {
    private MapView mapView;
    private AMap aMap;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        mapView = new MapView(getContext());
        mapView.onCreate(savedInstanceState);
        aMap = mapView.getMap();
        // 设置地图类型为导航模式
        aMap.setMapType(AMap.MAP_TYPE_NAVI);
        return mapView;
    }
}
💡 小技巧: 在 Automotive 上,地图的生命周期要跟车载的“显示窗口”绑定,而不是 Activity。我习惯用 CarAppDisplayonStart()onStop() 来控制 MapView 的可见性,避免后台浪费流量。

19.2 导航栏与 HUD 显示

车机上导航栏和 HUD(抬头显示)是两个完全不同的渲染层级。导航栏是给乘客看的,HUD 是给驾驶员看的。说白了,HUD 的信息要更精简、更聚焦。

19.2.1 导航栏的设计要点

导航栏通常位于屏幕左侧或底部。我个人习惯放在左侧,因为驾驶员视线从左到右扫视更自然。导航栏上需要显示:

  • 当前道路名称:实时更新,不要延迟超过 1 秒
  • 下一路口距离:用进度条或数字显示
  • 转向箭头:用矢量图,不要用位图,否则缩放会糊
  • 预计到达时间:结合实时路况计算

我在项目中遇到过一个问题:导航栏的更新频率太高,导致 UI 线程卡顿。后来我们用了 Choreographer 来同步帧率,只在垂直同步信号到来时才更新 UI,流畅度提升很明显。

19.2.2 HUD 显示的实现

HUD 显示在挡风玻璃上,所以它的背景必须是透明的。Android Automotive OS 提供了 CarHUDManager 来管理 HUD 窗口。注意,HUD 的显示内容不能太多,否则会干扰驾驶。

// 获取 HUD 管理器
CarHUDManager hudManager = (CarHUDManager) getSystemService(Context.CAR_HUD_SERVICE);

// 创建 HUD 显示内容
HudRender hudRender = new HudRender() {
    @Override
    public void onRender(Surface surface, int width, int height) {
        Canvas canvas = surface.lockCanvas(null);
        // 绘制简洁的导航信息
        Paint paint = new Paint();
        paint.setColor(Color.GREEN);
        paint.setTextSize(48);
        canvas.drawText("前方 200 米右转", 50, 100, paint);
        // 绘制速度、车道线等
        surface.unlockCanvasAndPost(canvas);
    }
};

// 注册 HUD 显示
hudManager.registerHudRender(hudRender, new Executor() {
    @Override
    public void execute(Runnable command) {
        command.run();
    }
});
⚠️ 注意: HUD 的刷新率建议控制在 15fps 以内,太高会消耗 GPU 资源,导致车机发热。我曾经在一个项目里把 HUD 刷到 30fps,结果半小时后中控屏温度报警。后来降到 10fps,驾驶员也感觉不到延迟。

19.3 路线规划

路线规划是导航的核心。用户输入目的地后,我们要在几秒内给出多条路线。这里涉及两个关键点:算路策略和路线展示。

19.3.1 算路策略

第三方 SDK 通常提供多种算路模式:

  • 最快路线:基于历史路况和限速
  • 最短路线:距离优先,可能走小路
  • 避开拥堵:实时路况加权
  • 避开高速:适合货车或新手司机

我个人习惯在算路前先获取当前车辆的“驾驶模式”。如果是运动模式,优先推荐最快路线;如果是经济模式,优先推荐最短路线。这个逻辑在代码里很好实现:

// 获取驾驶模式(假设有 VehicleProperty)
int drivingMode = getVehicleProperty(VehicleProperty.DRIVING_MODE);

AMapNaviRouteOption option = new AMapNaviRouteOption();
if (drivingMode == 1) { // 运动模式
    option.setRouteStrategy(AMapNaviRouteOption.ROUTE_STRATEGY_FASTEST);
} else if (drivingMode == 2) { // 经济模式
    option.setRouteStrategy(AMapNaviRouteOption.ROUTE_STRATEGY_SHORTEST);
} else {
    option.setRouteStrategy(AMapNaviRouteOption.ROUTE_STRATEGY_BALANCE);
}

// 发起算路
AMapNavi.getInstance(getContext()).calculateDriveRoute(
    startPoint, endPoint, wayPoints, option, new AMapNaviCallback() {
        @Override
        public void onCalculateRouteSuccess(AMapNaviRoute[] routes) {
            // 展示路线列表
            showRouteList(routes);
        }
    });
💡 避坑指南: 算路结果不要直接在主线程处理。我曾经在回调里直接更新 UI,结果算路时间稍长(比如 3 秒),UI 就卡住了。正确的做法是:在回调里把路线数据拷贝到 ViewModel,然后通过 LiveData 通知 UI 更新。

19.3.2 路线展示与交互

算路成功后,我们通常会给用户展示 2-3 条路线。每条路线用不同的颜色绘制在地图上。用户点击某条路线后,才开始正式导航。

展示路线时,我建议用 PolylineOptions 来绘制,并设置不同的宽度和透明度:

// 绘制推荐路线(蓝色,粗线)
PolylineOptions blueLine = new PolylineOptions();
blueLine.addAll(route.getCoordinates());
blueLine.color(Color.BLUE);
blueLine.width(20);
aMap.addPolyline(blueLine);

// 绘制备选路线(灰色,细线)
PolylineOptions grayLine = new PolylineOptions();
grayLine.addAll(alternativeRoute.getCoordinates());
grayLine.color(Color.GRAY);
grayLine.width(10);
aMap.addPolyline(grayLine);

嗯,这里要注意:如果路线经过隧道,GPS 信号会丢失。我建议在路线规划时提前标记隧道区域,并在进入隧道前提示用户“即将进入隧道,导航将使用惯性推算”。这样用户体验会好很多。

19.4 知识体系总览

下面这张图是我自己整理的,把本章的核心逻辑串起来了。你一看就明白:地图 SDK 是底座,导航栏和 HUD 是输出,路线规划是大脑。

地图与导航模块 第三方地图 SDK API 密钥管理 权限与合规 导航栏 & HUD 导航栏 UI 更新 HUD 渲染 路线规划引擎 算路策略 路线展示

你看,整个模块分三层:底层是 SDK 集成,中间是导航栏和 HUD 两个输出通道,上层是路线规划逻辑。每一层都有各自的坑,但只要按我上面说的来,基本能平稳落地。

📌 核心要点回顾:
  • 集成 SDK 前先检查 API 兼容性和权限,别等上线了才发现问题
  • 导航栏用 Choreographer 同步帧率,HUD 控制刷新率在 15fps 以内
  • 路线规划结合驾驶模式动态调整策略,算路结果用 ViewModel 传递
  • 隧道场景提前标记,用惯性推算弥补 GPS 丢失

好了,地图与导航这块就聊到这儿。下一章我们会讲语音交互,到时候再细聊怎么把导航和语音指令串起来。

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