8、导航应用开发:CarNavigationManager、导航指令与路况、地图集成(Google Maps)、位置权限处理
导航,可以说是车载系统里最核心的应用之一。你想想看,用户开车时最离不开的就是导航。今天我们就来聊聊在 Android Automotive OS 上怎么把导航做好。
说实话,车载导航和手机导航差别挺大的。手机导航你随便装个 App 就能用,但在车上,你得考虑驾驶安全、屏幕交互、还有和车辆硬件的配合。我个人习惯把车载导航开发分成四个模块:地图集成、导航管理、路况处理、权限控制。咱们一个一个来。
核心要点:Android Automotive OS 的导航开发,本质上是把手机端的导航能力,安全地迁移到车载环境中。CarNavigationManager 就是连接你的导航 App 和车载系统的桥梁。
8.1 CarNavigationManager:导航的“总指挥”
CarNavigationManager 是 Android Automotive OS 提供的一个系统服务。它的作用,说白了就是让你的导航 App 能和车载系统“对话”。
我在项目中遇到过一个问题:导航 App 启动后,仪表盘上不显示导航信息。后来发现,就是没有正确注册 CarNavigationManager。嗯,这里要注意,这个注册过程是必须的。
// 获取 CarNavigationManager 实例
Car car = Car.createCar(context);
CarNavigationManager navManager = (CarNavigationManager) car.getCarManager(
android.car.Car.CAR_NAVIGATION_SERVICE
);
// 注册导航状态监听
navManager.registerNavigationStatusListener(new CarNavigationManager.CarNavigationStatusCallback() {
@Override
public void onNavigationStateChanged(int state) {
// state: 0=未导航, 1=导航中, 2=重新规划路线
if (state == 1) {
// 导航已开始
}
}
@Override
public void onInstrumentClusterNavigationInfoChanged(
CarNavigationInstrumentCluster clusterInfo) {
// 仪表盘需要显示导航信息时触发
// 这里可以发送导航指令到仪表盘
}
});
这段代码看起来简单,但有个坑。我曾经在项目里忘记在 onDestroy 里注销监听,结果导致内存泄漏。所以记得在生命周期结束时调用 navManager.unregisterNavigationStatusListener()。
个人经验:CarNavigationManager 的注册最好放在 Service 里,而不是 Activity。因为导航服务需要长时间运行,Activity 可能会被销毁重建。我习惯用一个前台 Service 来管理导航生命周期。
8.2 导航指令与路况:让用户知道怎么走
导航指令,就是告诉用户“前方300米右转”这样的信息。在 Automotive OS 上,这些指令不仅要显示在屏幕上,还要能发送到仪表盘和 HUD(抬头显示)。
我记得有一次,测试反馈说导航语音和仪表盘显示不一致。查了半天,原来是导航指令的发送时机不对。来看正确的做法:
// 创建导航指令
CarNavigationInstrumentCluster clusterInfo =
navManager.getInstrumentClusterInfo();
if (clusterInfo != null && clusterInfo.isSupported()) {
// 构建导航事件
NavigationState navState = new NavigationState.Builder()
.setDestinationName("北京南站")
.setRemainingDistance(5000) // 剩余距离,单位米
.setRemainingTime(300) // 剩余时间,单位秒
.setTurnEvent(new TurnEvent.Builder()
.setTurnAngle(90) // 转弯角度
.setTurnSide(TurnEvent.TURN_SIDE_RIGHT)
.setRoadName("建国路")
.build())
.build();
// 发送到仪表盘
navManager.sendNavigationState(navState);
}
路况信息怎么处理?我建议用 Google Maps 的路况数据,实时更新。但要注意,路况更新不要太频繁,否则会消耗大量流量和电量。我个人习惯每 30 秒更新一次。
| 导航指令类型 | 说明 | 更新频率建议 |
|---|---|---|
| 转弯提示 | 即将转弯的方向和距离 | 每次路线变化时 |
| 路况更新 | 前方道路拥堵情况 | 每 30 秒 |
| 到达信息 | 剩余距离和时间 | 每 5 秒 |
| 重新规划 | 路线变更通知 | 即时 |
避坑指南:我曾经在路况更新时直接在主线程解析 JSON,结果导致界面卡顿。路况数据解析一定要放在子线程,或者用 Kotlin 协程处理。另外,路况数据要缓存,网络不好时用缓存数据。
8.3 地图集成:Google Maps 的正确打开方式
Google Maps 在 Automotive OS 上的集成,和手机端不太一样。你不能直接用手机版的 Maps SDK,得用 Automotive 专用的版本。
为什么?因为车载屏幕的交互方式不同。手机你可以用手指缩放,但车上你得考虑驾驶安全,缩放操作要简化。
// 在 build.gradle 中添加依赖
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.google.android.libraries.maps:maps-android-utils:3.4.0'
// 布局文件中使用 MapFragment
<fragment
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
// 初始化地图
SupportMapFragment mapFragment = (SupportMapFragment)
getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
// 设置地图样式,适合驾驶场景
googleMap.setMapStyle(MapStyleOptions.loadRawResource(
getContext(), R.raw.driving_style));
// 禁用一些不适合驾驶时的操作
googleMap.getUiSettings().setZoomGesturesEnabled(false);
googleMap.getUiSettings().setScrollGesturesEnabled(false);
// 开启交通图层
googleMap.setTrafficEnabled(true);
}
});
这里有个细节:我建议把地图的缩放和滚动手势关掉。为什么?因为开车时用户不应该去操作地图。导航路线是自动跟随的,用户只需要看就行。如果非要操作,可以用语音或者方向盘上的按键。
关键点:车载地图的核心是“看”,不是“玩”。所有交互设计都要围绕驾驶安全来设计。我见过一些 App 把手机上的地图交互直接搬过来,结果被用户投诉说太危险。
8.4 位置权限处理:别让用户在路上抓狂
位置权限,是导航 App 的命门。没有位置权限,导航就是个摆设。但 Android 的权限机制越来越严格,你得处理好各种情况。
我记得有一次,用户反馈说导航 App 启动后一直显示“正在定位”。查了日志才发现,是权限请求被拒绝了。而且用户点了“不再询问”,这就麻烦了。
// 检查位置权限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// 判断是否需要显示解释说明
if (ActivityCompat.shouldShowRequestPermissionRationale(
this, Manifest.permission.ACCESS_FINE_LOCATION)) {
// 用户之前拒绝过,显示解释说明
showPermissionExplanationDialog();
} else {
// 第一次请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
LOCATION_PERMISSION_REQUEST_CODE);
}
} else {
// 权限已授予,开始定位
startLocationUpdates();
}
但这里有个坑:在 Android 12 以上,还有 ACCESS_BACKGROUND_LOCATION 权限。导航 App 需要在后台持续定位,所以这个权限也得申请。
我的做法:在 App 第一次启动时,先申请前台位置权限。等用户开始导航时,再申请后台位置权限。这样用户体验更好,不会一上来就被权限请求吓到。
权限被永久拒绝怎么办?我建议在界面上显示一个引导提示,告诉用户去设置里手动开启。但不要频繁弹窗,否则用户会烦。
// 引导用户去设置页面
private void showSettingsGuide() {
new AlertDialog.Builder(this)
.setTitle("需要位置权限")
.setMessage("导航功能需要位置权限,请在设置中开启")
.setPositiveButton("去设置", (dialog, which) -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
})
.setNegativeButton("取消", null)
.show();
}
避坑指南:我曾经在权限被拒绝后,直接退出了 App。结果用户投诉说“一打开就闪退”。正确的做法是:没有位置权限时,显示一个友好的提示页面,让用户知道为什么需要权限,而不是直接崩溃。
8.5 知识体系总览
说了这么多,我们来梳理一下导航应用开发的核心脉络。下面这张图可以帮你快速理解各个模块之间的关系:
这张图展示了导航应用的完整架构。从用户交互层到底层系统服务,每一层都有明确的职责。你在开发时,可以按照这个结构来组织代码。
好了,导航应用开发的核心内容就这些。记住一点:车载导航和手机导航最大的区别,就是安全第一。所有功能设计都要围绕“让用户更安全地驾驶”这个目标。代码写得好不好是其次,用户能不能安全到达目的地,才是最重要的。