Android 步行导航:从接入到完整行程
本章面向第一次接入的 Android 开发者。目标是完成“取得真实位置 → 选择终点 → 规划步行路线 → 用户确认开始 → 实时引导 → 偏航重算 → 到达 / 退出”的完整流程。配套示例只调用公开接口,不需要修改 SDK。
下载完整步行案例源码 · JavaScript 步行教程 · 导航服务 API
1. 先选对接口
| 需求 | 使用接口 | 是否持续导航 |
|---|---|---|
| 查询步行距离、预计耗时、画预览路线 | RouteSearch.calculateWalkRouteAsyn |
否 |
| 真实步行、转向提示、偏航、到达、录音播报 | WalkingNavi |
是,前台控制器 |
| 驾车 GPS / 模拟导航 | NavigationClient / NavigationController |
属于驾车流程 |
| 自己绘制地图、路线、起终点 | MapView、SukhaMap、Polyline、Marker |
仅负责显示 |
WalkingNavi 不会创建 Activity、自动显示地图,也不负责申请权限或持续采集定位。宿主必须提供地图界面和原始定位,处理页面生命周期。不要把驾车的 GPSNaviMode、EmulatorNaviMode、前台服务连接或 startNavi(mode) 套到步行控制器上。
配套 WalkingGuideActivity.java 是可编译的完整前台页面:有地图选终点、定位权限、规划、开始、语音开关、停止、偏航状态、到达和资源释放。优先运行这个页面,再按业务 UI 调整。
2. 五分钟接入准备
- 从下载页取得配套 SDK。使用交付包
versions.json中同批模块和 Maven 仓库,保留 POM 与传递依赖。 - 工程至少需要
maps-core、maps-services、maps-location、maps-navigation-services;maps-navigation按该批 Maven 依赖解析。不要仅复制一个 AAR。 - Android Key 的包名、签名 SHA-1、调试 / 发布环境、数据集与实际 APK 一致。初始化成功不等于数据覆盖所有城市;终点应位于该数据集的可步行路网内。
- Java 11、minSdk 23;Gradle 和 desugaring 配置可直接沿用交付包
examples/general-android。完整版本见版本与交付。 - 将案例里的
WalkingGuideActivity.java放到app/src/main/java/example/walking/,在应用 Manifest 注册。无需新增布局 XML。
在 <manifest> 内添加权限,在 <application> 内添加元数据和 Activity:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 以下内容放入 application;用自己的 Android Key 和已授权数据集。 -->
<meta-data android:name="cn.sukha.maps.API_KEY" android:value="YOUR_ANDROID_KEY" />
<meta-data android:name="cn.sukha.maps.SERVICE_URL" android:value="https://map.sukha.cn" />
<meta-data android:name="cn.sukha.maps.DATASET_ID" android:value="YOUR_DATASET_ID" />
<activity android:name="example.walking.WalkingGuideActivity" android:exported="false" />
从自己已有的页面启动:
startActivity(new android.content.Intent(this, example.walking.WalkingGuideActivity.class));
首次进入先点“定位”并授权,等位置有效后在地图点击终点,再点“规划步行”和“开始”。初始地图中心是演示区域,不会被当成真实导航起点。如果已有地点搜索,选择结果后将其公开坐标作为终点即可;不要拿地图屏幕中心代替用户位置。
3. 坐标、时间、精度:最容易出错的三个地方
使用内置地图页面切换驾车 / 步行
内置 SukhaMapFragment 的目的地规划卡片提供“驾车 / 步行”选项。也可在主线程调用:
map.setTravelMode(SukhaMapFragment.TravelMode.WALKING);
map.planRoute(start, end, listener);
// 路线就绪且真实定位有效后,由用户点击开始。
map.startNavigation();
// 切换回驾车会取消旧请求,并重新规划当前起终点。
map.setTravelMode(SukhaMapFragment.TravelMode.DRIVING);
默认驾车以预计时间为首要目标,结合道路等级、通行速度和路口代价优选主路;“距离最短”驾车备选仍遵守车辆通行限制。步行明确请求 DISTANCE,使用步行路网,可走允许通行的步道、小路,不按车辆单行规则绕行,也不会进入禁止步行的高速道路。预计步行时间来自步行算路结果,不复用驾车时间。
内置步行导航复用 WalkingNavi 的前台生命周期。宿主在步行导航期间继续传入真实位置;不能像驾车服务接管定位后那样停止宿主定位。页面退到后台会暂停步行导航,返回后由用户重新开始并校验定位。它不提供后台或锁屏持续步行播报。
WalkingNavi.setVoiceLanguage(language) 可独立设置中文、英文、缅甸语。步行出发、直行、转向、掉头、路线更新、到达及定位不稳使用专用离线录音,与驾车录音区分;切换语言或静音会清理当前播报。旧接口保持可用。
| 数据 | 顺序 / 单位 | 要求 |
|---|---|---|
LatLonPoint / LatLng |
纬度、经度 | 例如 new LatLonPoint(21.67623,100.02228) |
calculateWalkRoute(start,end) |
与 SdkClient 公开坐标模式一致 |
案例显式使用 WGS84 |
updateLocation(android.location.Location) |
原始 WGS84 | 即便客户端公开模式为 GCJ02,此入口也不要手工转成 GCJ02 |
Location.getTime() |
Unix 毫秒时间戳 | 保留定位原时间,不用收到回调的时间替换 |
accuracy |
米 | 必须提供真实精度;未知时不伪造为 0 |
| speed / bearing | 米/秒、角度 | 可缺省;不要把 km/h 当米/秒 |
示例从 SukhaLocation 转为 Android Location 时保留经纬度、accuracy、time;只有 hasSpeed() / hasBearing() 为真才复制对应值。拿到错误定位时只展示错误,不调用 updateLocation。
4. 推荐状态流与按钮行为
未定位/未选终点 → 定位有效且终点已选 → PLANNING → READY → NAVIGATING
↓ 失败 ↓ 偏航
可重试 SDK 自动重算
↓ 到达
行程已结束
任意状态 → 用户停止 / 页面离开 → 停止定位与导航、清理待处理任务
| 状态 | 可以做什么 | 不应做什么 |
|---|---|---|
| 等待定位 | 申请权限、重试定位、选终点 | 用固定点开始真实导航 |
| 规划中 | 显示加载、允许取消 | 重复开始、连点创建多个控制器 |
| 路线就绪 | 展示距离 / 耗时、确认开始 | 在定位已过期或起点明显移动后继续使用旧路线 |
| 导航中 | 更新引导、静音、停止 | 每次定位重新算路、每次回调重复开始导航 |
| 偏航重算 | 展示“正在重新规划” | 在重算回调里再次发起同一次算路 |
| 到达 | 展示到达、停止宿主定位 | 将到达自动等同于业务订单完成 |
示例将“8 秒内、精度不大于 35 米”的位置作为开始前的保守检查,并在起点移动超过 30 米时要求重算。这是案例的产品策略,不是 SDK 可配置参数或定位精度承诺。开始按钮点击时仍要重新检查,不能只在按钮刚启用时检查一次。
5. 最小调用链
下面代码说明核心顺序;完整权限、按钮、地图和退出逻辑在下载案例里。
SdkClient client = AndroidSdkClient.create(activity, serviceUrl, datasetId,
"zh-CN", CoordinateMode.System.WGS84);
WalkingNavi walk = new WalkingNavi(activity, client);
walk.setListener(new WalkingNavi.Listener() {
public void onCalculateRouteSuccess(WalkRouteResult result) {
WalkPath path = result.getPaths().get(0);
// 首次规划:绘制 path.getPolyline(),启用“开始”。
// 导航中的重算也会触发此回调:替换旧线,不再 startNavi()。
}
public void onCalculateRouteFailure(SdkException error) {
// 展示 error.getCode()/getMessage()/getRequestId(),让用户重试。
}
public void onNaviInfoUpdate(WalkingProgress.Update u) {
// 使用 reliable / offRoute / cue / remainingMeters 更新 UI。
}
});
// 操作一:有真实起点、用户已选择终点之后。
walk.calculateWalkRoute(startPoint, endPoint);
// 操作二:成功回调后,用户点开始;无参数,不传 GPSNaviMode。
walk.startNavi();
// 操作三:导航中,每次有效真实定位回调交给 SDK。
walk.updateLocation(rawWgs84Location);
// 操作四:明确停止。
walk.stopNavi();
// 页面最终释放。
walk.close();
client.close();
上述分阶段语句不能不等异步回调就一次执行。步行控制器没有需要调用的 initialize() 或 onReady;不要从驾车示例复制这两个步骤。所有公开方法在 Android 主线程调用;SDK 内部处理耗时任务,不要在 UI 线程自己做 HTTP 算路。
6. 回调与返回字段逐项说明
WalkingNavi.Listener 回调 |
触发时机 | 推荐处理 |
|---|---|---|
onCalculateRouteSuccess(WalkRouteResult) |
初次规划、自动偏航重算成功 | 替换路线;用宿主状态区分两种情况 |
onCalculateRouteFailure(SdkException) |
规划或重算失败 | 展示错误码、说明、requestId;避免无间隔循环重试 |
onNaviInfoUpdate(WalkingProgress.Update) |
导航中的定位处理结果 | 更新引导状态和剩余米数 |
onRecalculateRequested() |
SDK 判断需要重新算路 | 只展示状态,SDK 已负责请求 |
onArriveDestination() |
满足到达判断 | 展示到达、停止自己的定位订阅 |
onVoiceStatus(String) |
语音状态反馈 | 单独显示语音状态,不覆盖整条路线 |
WalkingProgress.Update 是回调参数的公开类型,无需自行构造进度引擎:
| 字段 | 含义与 UI 用法 |
|---|---|
accepted |
此次定位是否被接纳;false 时不要把它显示成可靠的新进度 |
reliable |
本次进度是否可靠;false 时显示定位不稳定,别强行显示下一步转向 |
offRoute |
已偏离路线;不是“恰好不在折线上”的瞬间判断 |
requestReroute |
引擎认为需要重算;上层 WalkingNavi 已接管重算 |
arrived |
到达状态;对业务使用独立的到达事件并处理幂等 |
progressMeters / remainingMeters |
已行进 / 剩余距离,米 |
deviationMeters |
与所匹配路线的偏离距离,米 |
cue |
可能为 null;非空时读取 instruction、id、geometryIndex |
步行更新没有驾车的 progress.getInstruction()、remainingSeconds 或车速显示接口。预计总耗时从 WalkPath.getDuration() 读取(秒);如果自己估计动态剩余时间,请明确它是宿主估算。
7. 四个常用业务案例
案例 A:地图地点页“步行去这里”
地点页携带真实地点坐标进入步行页面;页面先取得当前位置,再规划。优先选择适合步行到达的入口,避免以巨大建筑几何中心作为唯一终点。已有业务地点的 ID、标题与终点应绑定保存,用户换地点时停止旧规划并清空旧路线。
案例 B:规划后继续浏览,稍后再开始
保留路线预览,但开始时重新检查定位时间、精度与规划起点。若用户已走远,提示重新规划,不修改旧定位时间来通过检查。示例 plannedOrigin 与 fresh() 演示这个检查。
案例 C:走错路后重新规划
持续向 updateLocation 喂真实位置即可。SDK 触发 onRecalculateRequested 后,UI 显示重算提示;成功后用新 getPolyline() 替换旧线。不要在成功回调中再次 startNavi(),否则会重置进度和语音去重。
案例 D:到达后返回地点页
收到到达事件后显示完成 UI、停止宿主位置采集,保留必要的路线摘要。SDK 会播到达语音;不要在到达回调里立即再 stopNavi(),否则可能截断这段播报。用户真正退出页面时仍要 close()。业务订单是否完成由自己的确认流程决定。
8. 生命周期、语音和网络
完整示例在 onStop 停止本次步行和定位,在 onDestroy 释放 SukhaLocationClient、WalkingNavi、MapView、SdkClient;MapView 的 create/start/resume/pause/stop/save/lowMemory/destroy 均有转发。旋转或页面重建后要求重新规划,不会偷偷恢复一条旧行程。
这是前台步行案例。不要因为驾车 SDK 存在前台服务,就假设 WalkingNavi 自动提供通知栏、锁屏续航或跨 Activity 恢复。确需后台步行时,先设计并验证宿主服务生命周期与目标系统限制;本例不声称已经提供这些能力。
语种随创建时的 SdkClient 语言,支持的语种以当前批次为准;示例使用 zh-CN,可换成 en-US 或 my-MM。setVoiceEnabled(false) 静音,不停止定位或步行进度。不要在每个进度回调自己再次播放提示。
首次规划和偏航重算都需要可用网络及有效授权。已有路线仍在内存时与“支持离线重算”不同;Android 步行控制器没有 JS 的 prepareOfflineNavigation(),不能照抄该方法。
9. 排错表
| 现象 | 先检查 | 修复方向 |
|---|---|---|
| 地图 / 算路授权失败 | Key 类型、包名、签名、数据集、错误 requestId | 配置匹配实际 APK,检查 debug/release 差异 |
| 显示路线但没有导航 | 是否只用了 RouteSearch |
用 WalkingNavi,成功后由用户开始并持续喂定位 |
开始报 Plan a walking route first |
是否等待成功回调 | 将开始按钮绑定 READY 状态 |
| 没有进度 | 是否 start、是否喂 Location、有无 accuracy、time 是否原始 | 按案例定位适配器排查 |
| 位置一直不可靠 | 粗略定位、旧缓存、差精度、时间倒退 | 到开阔处、检查精确位置权限;不要伪造 accuracy |
| 坐标偏移 | 公开坐标模式与原始定位是否混用 | 算路用公开模式;Location 一律原始 WGS84 |
| 重算后路线叠加 | 是否每次新增折线而没删除旧线 | 保存一个 Polyline 引用,替换时 remove |
| 没声音 | 静音状态、设备媒体音量、语音状态、录音资源 | 与路线错误分别显示,不重复自播 |
| 一到达就截断语音 | 到达回调是否立刻 stopNavi | 停宿主定位即可,用户退出时再 close |
| 切后台停止 | 是否采用本例的 onStop 策略 | 属于明确的前台示例行为 |
10. 开发验收清单
先做接口联调,再做户外真实步行:首次拒绝 / 再授权、无精度定位、旧定位、短路线、连续转弯、实际偏航、重算断网、原地停留、终点附近漂移、修改终点、算路中退出、重复点开始、到达后退出、三种语种、静音恢复、横竖屏切换。不得将人工修改时间戳或坐标后的实验数据当作真实轨迹。
本章完整 Activity 在文档构建时以配套交付 AAR 编译;编译通过证明接口签名匹配,不替代手机定位、扬声器听感和实际路网可达性验收。