54、定位与地图:Google Maps SDK、百度地图SDK、定位权限、地理编码
地图和定位,几乎是现代移动应用的标配功能了。不管是外卖、打车,还是社交打卡,都离不开它。今天我们就来聊聊,在Android里怎么把地图和定位玩明白。
我个人习惯把地图SDK分成两类:Google Maps和百度地图。它们各有各的生态,也各有各的坑。嗯,咱们一个一个说。
定位权限:第一步,也是最容易翻车的一步
你想想看,用户打开你的App,第一件事就是弹窗问「是否允许获取位置」。如果处理不好,用户直接就把权限拒绝了。后面所有地图功能都白搭。
Android的定位权限,从6.0开始分成了两个级别:
| 权限 | 说明 | 是否需要运行时申请 |
|---|---|---|
| ACCESS_FINE_LOCATION | 精确定位,GPS + 网络 | 是(Android 6.0+) |
| ACCESS_COARSE_LOCATION | 粗略定位,仅网络 | 是(Android 6.0+) |
| ACCESS_BACKGROUND_LOCATION | 后台定位(Android 10+) | 是,且需要单独弹窗 |
代码里怎么申请?很简单,用ActivityResultLauncher就行:
private val locationPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val fineGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: false
val coarseGranted = permissions[Manifest.permission.ACCESS_COARSE_LOCATION] ?: false
if (fineGranted || coarseGranted) {
// 权限拿到了,开始定位
} else {
// 权限被拒绝,引导用户去设置
}
}
// 触发申请
locationPermissionLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
Google Maps SDK:全球通用,但国内有坑
Google Maps的优势很明显:全球数据统一,API设计优雅,文档齐全。但如果你面向的是国内用户,就得小心了——Google服务在国内不一定能稳定访问。
集成步骤其实不复杂:
- 在Google Cloud Console创建项目,启用Maps SDK for Android
- 生成API Key,限制Android应用的包名和SHA-1指纹
- 在AndroidManifest.xml里添加API Key
- 在布局文件里放一个MapView或SupportMapFragment
核心代码大概长这样:
// 在Activity或Fragment中
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync { googleMap ->
// 拿到GoogleMap对象,可以设置标记、移动相机等
val sydney = LatLng(-33.852, 151.211)
googleMap.addMarker(MarkerOptions()
.position(sydney)
.title("悉尼歌剧院"))
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 15f))
}
这里有个细节:getMapAsync是异步回调,因为地图底层需要加载瓦片数据。你不能在回调外面操作GoogleMap对象,否则会报空指针。嗯,我刚开始用的时候就被坑过。
百度地图SDK:国内首选,但包体积大
百度地图在国内的覆盖率比Google好很多。它的定位服务也更符合国内网络环境。不过,百度SDK的包体积确实有点大——动不动就几十MB。
集成步骤类似,但多了个「申请密钥」的环节:
- 去百度地图开放平台注册应用,生成AK
- 下载SDK(或者用Maven依赖)
- 在AndroidManifest里配置AK和权限
- 初始化SDK(在Application里调用SDKInitializer.initialize)
代码示例:
// 在Application的onCreate中初始化
SDKInitializer.initialize(this)
// 在Activity中使用MapView
val mapView = findViewById<MapView>(R.id.bmapView)
val baiduMap = mapView.map
// 添加标记
val point = LatLng(39.915, 116.404)
val overlay = baiduMap.addOverlay(MarkerOptions()
.position(point)
.title("天安门")
.icon(BitmapDescriptorFactory.fromResource(R.drawable.marker)))
// 移动地图
val status = MapStatus.Builder()
.target(point)
.zoom(15f)
.build()
baiduMap.animateMapStatus(MapStatusUpdateFactory.newMapStatus(status))
地理编码:把地址变成坐标,反过来也行
地理编码(Geocoding)就是把「北京市朝阳区」这样的文字地址,转成经纬度。反向地理编码则相反。
Google和百度都提供了对应的API:
| 功能 | Google Maps | 百度地图 |
|---|---|---|
| 正向地理编码 | Geocoder.getFromLocationName() | GeoCoder.geocode() |
| 反向地理编码 | Geocoder.getFromLocation() | GeoCoder.reverseGeocode() |
| 是否需要网络 | 是(在线) | 是(在线) |
| 离线支持 | 部分(Android自带Geocoder) | 否 |
用Google的Geocoder举个例子:
val geocoder = Geocoder(this, Locale.getDefault())
val addresses = geocoder.getFromLocationName("北京市朝阳区", 5)
if (addresses != null && addresses.isNotEmpty()) {
val address = addresses[0]
val lat = address.latitude
val lng = address.longitude
// 拿到坐标了
}
getFromLocationName是同步方法,不能在主线程调用。我建议用协程或AsyncTask包一层。另外,Google的Geocoder在国内有时会返回空结果,这时候可以降级到百度或高德的地理编码服务。
知识体系总览
下面这张图,把定位与地图的核心知识点串起来了。你可以把它当作一个快速索引:
避坑指南:我踩过的那些坑
- 定位权限弹窗被系统拦截:Android 11以后,如果用户连续两次拒绝权限,系统会不再弹窗。这时候你需要引导用户去设置里手动开启。我曾经有个用户反馈说「App一直要权限,但我点了允许啊」,后来发现是系统把弹窗静默了。
- 地图瓦片加载失败:Google Maps在国内有时加载不出瓦片,显示空白。解决办法是加一个网络切换监听,检测到是国内网络时自动切换到百度地图。
- 百度地图初始化必须在Application中:如果你在Activity里才调用SDKInitializer.initialize,会直接崩溃。嗯,这个坑我踩过两次才记住。
- Geocoder在主线程调用:会导致ANR。一定要放到子线程或协程里执行。
好了,定位与地图的核心内容就这些。说白了,权限是门槛,SDK是工具,地理编码是桥梁。把这三块搞明白,大部分地图场景都能应付。
公众号:蓝海资料掘金营,微信deep3321