资讯动态

uni-app App端高德地图定位:获取当前位置与经纬度避坑指南

发布时间:2026/10/29 21:30:20 来源:尧图企业网站定制
做 uni-app 的同学大概率都遇到过这个场景页面里要显示“当前位置”设计稿上还带着街道名结果uni.getLocation一调Android 真机上返回的经纬度放到地图上偏出去几百米address字段干脆是 undefined。这不是代码写错了而是 App 端定位这条链路本身就比 H5 长——系统定位服务、地图 SDK、坐标系、权限、打包证书任何一环没对上表现都是“定位失败”或“定位偏移”。这篇就围绕uniapp 获取当前位置和经纬度信息app 端高德地图这一件事把我这几年在真机上反复调试出来的配置方式、代码封装、错误码排查和踩坑经验完整摊开讲。适合已经能跑通 uni-app 基础页面、正准备给 App 加定位功能的中级开发者刚入门的新手也能照着做因为每个配置项我都会说明它为什么必须存在。内容覆盖 Key 申请、manifest 配置、权限申请、坐标系选择、逆地理编码、精度优化和上线合规基本是一份可以直接照抄的落地清单。1. 先把方案定下来App 端定位为什么绕不开高德动手写代码之前方案选型这件事必须先想清楚。uni-app 的定位能力在 H5、小程序、App 三端是完全不同的实现H5 靠浏览器 Geolocation API小程序走各家平台自己的接口而 App 端则是一个可以插拔的容器——你可以用系统自带的定位服务也可以挂第三方地图 SDK。这个差别决定了后面所有配置的走向一旦选错代码怎么写都不对。1.1 uni.getLocation 在 App 端的三条路径很多人以为uni.getLocation就是一个统一的原生接口其实它在 App 端背后有三种可能的执行路径取决于你在manifest.json里配置了什么。第一条是纯系统定位。不配置任何地图 SDK 时uni-app 会调用 Android 的LocationManager或 iOS 的CLLocationManager拿到的是设备原始 GPS 坐标。这条路径的好处是零依赖、包体小坏处是只能返回 WGS-84 坐标geocode: true也不会给你地址因为逆地理编码需要地图服务商的数据。第二条是接入高德定位 SDK。在manifest.json的定位模块里填上高德 Key 之后uni.getLocation底层会走高德定位 SDK能返回 GCJ-02 坐标geocode参数也会生效直接带回省市区、街道、POI 名称。这是国内 App 最主流的做法。第三条是百度定位 SDK机制和高德类似返回 BD-09 坐标。选哪家基本看你项目里地图组件用的是谁——地图和定位用同一家坐标才能直接对上不用来回转换。提示这三条路径是互斥的配置了高德就不要再配百度否则打包时容易出现 SDK 冲突表现为编译通过但运行时定位回调永远不触发。我一般建议只要项目里出现了显示当前位置附近的店打卡签到这类需求直接上高德别省那几 MB。系统定位返回的 WGS-84 坐标在国内地图上画出来是偏的你还得自己写转换算法反而更麻烦。1.2 坐标系不统一的坑wgs84、gcj02、bd09这是新手最容易懵的地方也是最容易造成定位偏移几百米的元凶。国内同时存在三套常用坐标系它们之间的关系必须搞明白。坐标系全称/来源谁在用特点WGS-84国际通用地理坐标GPS 原始输出、多数海外服务未做偏移国内地图上直接画会偏GCJ-02国测局制定的加密坐标高德、腾讯、多数国内地图在 WGS-84 基础上做非线性偏移BD-09百度在 GCJ-02 上二次加密百度地图及其 SDK与 GCJ-02 之间还需一次转换uni.getLocation的type参数就是用来指定返回哪套坐标的可选wgs84和gcj02。关键在于只有接入了高德或百度 SDKtype: gcj02才会真正生效。如果没配 SDK你传gcj02进去返回的其实还是 WGS-84 的原始值但代码不会报错于是你就得到了一个看起来正常、实际偏移的坐标。我踩过最典型的一次坑测试同学在城郊反馈定位点落在马路对面我一开始怀疑是精度问题调了半天isHighAccuracy最后发现是 manifest 里的 Key 填错了平台SDK 根本没加载成功退回系统定位返回的坐标是 WGS-84直接叠在高德地图上偏移量大约在 300 到 500 米之间。所以后面我养成一个习惯定位成功后第一件事是打日志确认res.type和实际坐标系别默认它一定对。1.3 什么场景该用高德什么场景系统定位就够了不是所有定位需求都值得引入第三方 SDK判断标准其实很简单就看你要不要地址和国内地图对齐这两样东西。需要高德的场景展示当前位于 XX 市 XX 区 XX 路、计算用户到门店的距离、在地图上打点、签到打卡需要 POI 名称、需要较高成功率高德会融合 GPS、WiFi、基站多种来源。这些场景里系统定位要么给不出地址要么精度不够。系统定位够用的场景只做粗略的城市级判断、做后台的定时位置上报且对精度要求不高、包体大小极度敏感的工具类应用。这类场景用系统定位再自己接一个 Web 服务的逆地理编码接口也能凑合。但说实话多写的那几十行转换和请求代码维护成本往往超过省下来的包体。我现在的默认选择就是App 端定位一律走高德除非产品明确要求控制包大小。2. Key 与工程配置90% 的定位失败都出在这一步代码本身其实很短真正吃掉时间的是配置。我统计过自己处理过的定位问题接近九成最后都定位到配置层——Key 平台填错、SHA1 不匹配、模块没勾选、权限没声明。这一章把配置链路拆细每一步都说明白它在校验什么。2.1 申请 Key 时那三个必填项到地图服务商的控制台创建应用、添加 Key会要求你选择服务平台。这里必须选Android 平台和iOS 平台分别创建而不是Web 服务。这两个 Key 是给客户端 SDK 用的校验的是应用身份Web 服务类型的 Key 是给服务端 HTTP 接口用的两者不能混用。Android Key 需要填两项关键信息应用包名必须和manifest.json里appid对应的 Android 包名完全一致包括大小写。云打包时这个包名在打包界面能看到离线打包则在build.gradle的applicationId里。SHA1 指纹来自签名证书。这里有个大坑——调试用的 debug 证书和正式发布的 release 证书 SHA1 完全不同。你要么申请两个 Key一个调试一个正式要么在控制台把两个 SHA1 都填进去同一个 Key 支持填多个 SHA1。用云打包的话可以在打包配置页面直接查到 DCloud 公共测试证书的 SHA1。iOS Key 则只需要填Bundle ID同样要和 manifest 里配置的一致。提示SHA1 获取命令是keytool -list -v -keystore 证书路径注意区分 SHA1 和 SHA256控制台要的是 SHA1。填错一个字符表现就是 SDK 初始化失败uni.getLocation直接走 fail 回调。2.2 manifest.json 里的模块与 SDK 配置打开manifest.json切到App 模块配置勾选Geolocation(定位)然后在它的子项里勾选高德填入 Android 和 iOS 两个 Key。如果你的页面里还要用map组件显示地图那Maps(地图)模块也要一起勾选并填 Key——这两个模块的配置是独立的很多人只配了定位没配地图结果地图一片空白。对应的 JSON 结构大概是这个样子以 HBuilderX 可视化界面勾选后自动生成的为准不同版本字段可能略有差异{ app-plus: { modules: { Geolocation: {}, Maps: {} }, distribute: { sdkConfigs: { geolocation: { amap: { appkey_android: 你的Android Key, appkey_ios: 你的iOS Key } }, maps: { amap: { appkey_android: 你的Android Key, appkey_ios: 你的iOS Key } } } } } }手改 JSON 有风险字段名写错一个字母HBuilderX 不会报错但打包出来的 App 就是不带 SDK。所以我的建议是能用可视化界面勾选就别手写改完再回来看一眼生成的 JSON 是否符合预期。2.3 Android 权限与 iOS 权限描述Android 侧需要在manifest.json的App 权限配置里确认这几个权限存在精确定位、粗略定位、网络访问、WiFi 状态读取。前两个是定位必需后两个是高德 SDK 用来做 WiFi 辅助定位的。{ android: { permissions: [ uses-permission android:name\android.permission.ACCESS_FINE_LOCATION\/, uses-permission android:name\android.permission.ACCESS_COARSE_LOCATION\/, uses-permission android:name\android.permission.ACCESS_NETWORK_STATE\/, uses-permission android:name\android.permission.ACCESS_WIFI_STATE\/, uses-permission android:name\android.permission.CHANGE_WIFI_STATE\/ ] } }iOS 侧则要配置定位用途描述也就是用户在弹窗里看到的那句话。在manifest.json的 iOS 隐私信息描述里填NSLocationWhenInUseUsageDescription文案要写清楚用途比如用于展示您附近的服务中心。这句话不是走过场App Store 审核会看写得太含糊会被打回。2.4 打包方式对 Key 的影响这一点必须单独说因为它是最隐蔽的坑。云打包和离线打包的证书不是同一个SHA1 自然不同。用云打包你用的是公共测试证书或者自己上传的证书SHA1 在打包页面能看到用离线打包你用的是本地 Android Studio 工程里的签名配置。如果你的 Key 里只填了其中一种 SHA1另一种打包方式出来的包定位一定失败而且失败得很安静——不崩溃、不报错就是拿不到数据。我处理过的一个案例本地真机调试一切正常打正式包发给测试全部定位失败。查了两小时代码最后发现是控制台只填了 debug 证书的 SHA1正式包用的是另一份证书。解决办法是把两种打包方式对应的 SHA1 都填进同一个 Key这样切换打包方式不用改配置。3. 代码落地从权限申请到经纬度回填配置通了之后代码其实不复杂但要把权限被拒定位超时首次拿不到地址这些分支都处理好才算能上线。这一章给出一套可以直接复用的写法。3.1 权限申请的两段式写法Android 6.0 之后定位属于危险权限必须在运行时动态申请。uni-app 里用plus.android.requestPermissions处理。写法上我习惯分成两段先查状态再申请。// 检查并申请定位权限仅 Android 需要 function ensureLocationPermission() { return new Promise((resolve, reject) { // #ifdef APP-PLUS const platform uni.getSystemInfoSync().platform; if (platform ! android) { // iOS 由系统在首次调用定位时自动弹窗 resolve(true); return; } const main plus.android.runtimeMainActivity(); const Context plus.android.importClass(android.content.Context); const locationManager main.getSystemService(Context.LOCATION_SERVICE); if (!locationManager.isProviderEnabled(gps) !locationManager.isProviderEnabled(network)) { reject({ code: SERVICE_OFF, msg: 系统定位服务未开启 }); return; } plus.android.requestPermissions( [android.permission.ACCESS_FINE_LOCATION, android.permission.ACCESS_COARSE_LOCATION], (res) { if (res.granted.length 0) { resolve(true); } else { reject({ code: DENIED, msg: 用户拒绝了定位权限 }); } }, (err) reject({ code: ERROR, msg: err.message }) ); // #endif // #ifndef APP-PLUS resolve(true); // #endif }); }这段代码里有两点值得注意。第一先判断系统定位服务开关再申请权限因为服务关着的时候权限给了也拿不到坐标报错信息还特别含糊。第二拒绝之后不要死循环重试应该弹一个引导框让用户去系统设置里手动打开——plus.runtime.openURL或原生跳转都可以但要给用户明确的路径提示不然他不知道该点哪里。3.2 一次完整的 getLocation 调用权限拿到之后核心就是这一句uni.getLocation。参数不多但每一个都有实际作用我逐个说明。uni.getLocation({ type: gcj02, // 关键返回国测局坐标能直接叠在高德地图上 altitude: false, // 不需要海拔就别开开了会拖慢首次返回 geocode: true, // 让 SDK 顺手做逆地理编码带回 address isHighAccuracy: true, // 开启高精度模式 highAccuracyExpireTime: 4000, // 高精度模式最多等 4 秒超时降级 success: (res) { console.log(经度, res.longitude); console.log(纬度, res.latitude); console.log(精度, res.accuracy, 米); console.log(地址, res.address); // 需要 geocode: true 且有 SDK console.log(坐标系, res.type); }, fail: (err) { console.error(定位失败, err.errCode, err.errMsg); } });几个参数的选择逻辑值得展开说。type: gcj02是必须的前面讲过只有它才能和高德地图对齐。isHighAccuracy: true会优先使用 GPS 而不是基站和 WiFi 混合定位精度能从几百米收到十米级代价是首次定位时间变长、耗电增加。highAccuracyExpireTime是给高精度模式设的超时超过 4 秒如果还没拿到高精度结果SDK 会退而求其次返回一个次优结果。这个组合我觉得是体验和成功的平衡点实测下来冷启动首次定位通常在 2 到 5 秒之间返回。geocode: true要不要开看你要不要address。如果只要经纬度打点关掉它能快一两百毫秒如果要显示文字地址开着最省事省掉一次额外的网络请求。3.3 逆地理编码内置 geocode 与服务端接口的取舍拿到经纬度变地址有两条路。一条是刚才说的geocode: true由客户端 SDK 直接返回另一条是自己调地图服务商的 Web 服务逆地理编码接口。对比项客户端 geocode服务端/Web 接口实现成本一个参数搞定需要额外请求封装网络依赖依赖 SDK 内部请求明确的 HTTP 请求返回信息省市区、街道、POI更全可返回周边 POI 列表费用/配额一般含在 SDK 内有独立配额限制适用场景只需展示当前位置需要周边检索、批量解析我的习惯是页面展示当前位置用 geocode简单直接如果是批量解析坐标、或者需要周边 POI 做推荐再走服务端接口。注意走服务端接口时用的是Web 服务类型的 Key和客户端 Key 不能混用混用会返回无效 Key 的错误。3.4 封装一个可复用的定位模块每次都写一遍getLocation加权限判断代码很快就臭了。我一般会封装成一个方法返回 Promise内部把权限、超时、重试、缓存全处理掉。// utils/location.js const CACHE_KEY last_location; const CACHE_TTL 60 * 1000; // 一分钟内的位置直接复用 export async function getCurrentLocation(options {}) { const { useCache true, retry 1 } options; if (useCache) { const cached uni.getStorageSync(CACHE_KEY); if (cached Date.now() - cached.ts CACHE_TTL) { return cached.data; } } await ensureLocationPermission(); const attempt () new Promise((resolve, reject) { uni.getLocation({ type: gcj02, geocode: true, isHighAccuracy: true, highAccuracyExpireTime: 4000, success: resolve, fail: reject }); }); let lastErr null; for (let i 0; i retry; i) { try { const res await attempt(); const data { longitude: Number(res.longitude.toFixed(6)), latitude: Number(res.latitude.toFixed(6)), accuracy: res.accuracy, address: res.address }; uni.setStorageSync(CACHE_KEY, { ts: Date.now(), data }); return data; } catch (e) { lastErr e; await new Promise(r setTimeout(r, 800)); } } throw lastErr; }封装里有三个我自己加的实用点。第一坐标统一保留六位小数大约对应 0.1 米精度既够用又避免一堆浮点尾巴影响传参和日志排查。第二一分钟缓存用户在前一个页面刚定位完跳到下一个页面又定位一次体验上会闪一下缓存能避免这种重复等待。第三重试一次冷启动时第一次定位偶发超时很常见等 800 毫秒再试一次成功率提升明显。4. 精度、速度与失败兜底定位功能做出来容易做好用很难。用户不会关心你用了什么 SDK他只会觉得这个点怎么在飘怎么转了十秒还没出来。这一章聊精度和兜底这两个体验层面的问题。4.1 定位漂移的真实原因和缓解手段先说一个事实手机定位本身就不可能绝对准确。GPS 在空旷地带能有 5 到 10 米精度进了室内基本靠 WiFi 和基站推算几十米到几百米都正常。所以产品层面必须先对齐预期别指望定位点每次都稳稳落在门口。但有些漂移是可以缓解的。第一用accuracy字段做过滤。uni.getLocation返回的accuracy是定位半径估计值单位米如果这个值大于 200说明结果很不可靠可以考虑提示用户当前定位信号较弱或者继续等待更优结果。我在签到类功能里就做过这个判断精度太差时不让提交避免用户误签到。第二不要在短时间内连续多次定位。每次定位都会重新唤醒 GPS 芯片连续调用不仅耗电还容易拿到互相矛盾的坐标看起来就像点在乱跳。用缓存加节流一分钟内复用结果体验反而更稳。第三室内场景考虑用 POI 辅助。如果用户在某商场里光是经纬度很难判断他在几楼这时候配合 geocode 返回的 POI 名称或者让用户手动从附近列表里选比死磕定位精度靠谱得多。4.2 超时、重试与缓存三段式兜底一个健壮的定位流程应该是这样的先给缓存兜底再走正式定位正式定位失败后重试重试还失败就降级。async function locateWithFallback() { // 第一层缓存 const cached uni.getStorageSync(CACHE_KEY); if (cached) { // 先把旧位置渲染出来页面不空白 renderPosition(cached.data); } try { // 第二层实时定位 const fresh await getCurrentLocation({ useCache: false, retry: 1 }); renderPosition(fresh); } catch (e) { // 第三层降级提示 if (cached) { uni.showToast({ title: 当前定位信号较弱已显示上次位置, icon: none }); } else { uni.showModal({ title: 定位失败, content: 请检查是否开启了定位权限或稍后重试, showCancel: false, confirmText: 去设置 }); } } }这个三段式的价值在于页面永远不会白屏等定位。用户点进来先看到上次的位置几秒后自动刷新成实时位置感知上快很多。哪怕最终失败也有明确的提示和下一步引导而不是一个转圈转到天荒地老。5. 真实排查记录错误码对照与常见问题定位出问题的时候光看errMsg是不够的得看errCode。下面这张表是我这几年攒下来的基本覆盖了能遇到的绝大多数情况。5.1 错误码速查表errCode常见 errMsg真实原因处理方式1500系统定位失败权限未授予或 SDK 初始化失败检查动态权限与 manifest 中的 Key1501网络错误设备无网络或请求被拦截检查网络状态确认请求未被安全软件拦截1502系统定位服务未开启用户关闭了系统 GPS 开关引导用户到系统设置打开定位1503定位超时信号弱或highAccuracyExpireTime太短放宽超时时间允许降级返回1000高德回调错误Key 校验不通过、SHA1 不匹配、包名不符逐项核对控制台配置实际排查的时候我一般按这个顺序走先看errCode是不是 1502这是最简单的系统开关而已再看 1500 和 1000 系列基本就是配置问题1501 看网络1503 看超时参数。5.2 若干个我踩过的坑坑一只在 Android 上测过iOS 上一直转圈。原因是 iOS 的定位权限描述没填系统弹窗不出现getLocation就一直等。补上NSLocationWhenInUseUsageDescription之后解决。这个坑的隐蔽点在于iOS 不会主动报错就是没响应。坑二模拟器上定位正常真机偏移。模拟器默认给的是固定坐标或者你手动设置的位置看起来一切正常。真机走真实 GPS坐标系的差异就暴露出来了。所以定位功能必须真机调试模拟器只能验证代码流程通不通。坑三address偶尔是 undefined。排查下来有两个原因一是没配 SDK 所以geocode: true无效二是网络不好时逆地理编码请求失败但它不会让整个getLocation失败只是 address 字段缺失。所以用到address的地方必须做判空不能直接res.address.city。坑四页面切后台回来定位一直失败。部分 Android 机型在应用切到后台后会回收定位资源回到前台需要重新初始化。解决办法是在onShow里重新走一遍定位流程而不是依赖onLoad时的那一次结果。坑五多个页面同时定位回调串了。早期我图省事在两个页面各自调uni.getLocation偶尔会出现 A 页面拿到 B 页面的结果。后来统一收口到封装的 Promise 方法同一个时刻只允许一个定位请求在跑问题消失。注意排查定位问题时务必打开真机的日志输出把res整个对象打出来包括type、accuracy、address每个字段。只看经纬度两个数字很难判断到底是坐标系错了还是精度不够。6. 上线前的合规与体验细节功能跑通只是第一步能过审、能用得舒服才是终点。这一章讲两件容易在上线前被忽略的事。6.1 隐私授权时序现在 App 上架对隐私授权的要求很严格必须在用户明确同意隐私政策之后才能初始化定位、获取位置信息。如果 App 一启动就调定位很容易在审核阶段被打回。正确的时序是启动页展示隐私政策弹窗用户点同意再去初始化业务模块包括定位。如果你用的是云打包DCloud 侧对这部分有默认处理但业务代码里的调用时机还是得自己把关——别在App.vue的onLaunch里直接调定位把它挪到隐私确认之后的回调里。另外定位权限的申请文案要说人话。系统弹窗里那句是否允许获取位置信息是系统写的改不了但你可以在申请之前先自己弹一个说明框讲清楚我们需要位置来给你推荐附近的网点用户理解的意愿会高很多。实测下来先说用途再申请权限通过率比直接申请高不少。6.2 多端一致性处理同一个 uni-app 项目往往要同时跑 App 和小程序。小程序端的定位接口和 App 端不完全一样比如小程序需要先在manifest里声明位置权限用途部分平台还需要单独申请接口权限。我的做法是用条件编译把差异隔离在一处// #ifdef APP-PLUS const opts { type: gcj02, geocode: true, isHighAccuracy: true }; // #endif // #ifdef MP-WEIXIN const opts { type: gcj02, isHighAccuracy: true }; // #endif uni.getLocation({ ...opts, success, fail });这样上层业务代码完全不用关心平台差异改起来也只改一个地方。要提醒的是小程序的geocode支持情况各平台不同别默认它在所有平台都能返回地址用到地址的地方仍然要做兜底。定位这个功能说难不难说简单也不简单。真正花时间的从来不是那几行uni.getLocation而是 Key 和证书对不对得上、坐标系选没选对、权限时序有没有问题、失败的时候用户看到的是什么。我个人的经验是先把配置链路走通并在真机上验证一次再写业务代码顺序反了会浪费大量时间在到底是配置问题还是代码问题上来回怀疑。上面这套配置加封装的组合我在几个项目里反复用过冷启动首次定位基本稳定在 3 秒左右返回精度在城市道路场景下够用。如果你的场景对精度要求更高可以考虑在拿到首次结果后再监听一次高精度回调做修正这个后面有机会再单独聊。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑