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+) 是,且需要单独弹窗
注意: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服务在国内不一定能稳定访问。

集成步骤其实不复杂:

  1. 在Google Cloud Console创建项目,启用Maps SDK for Android
  2. 生成API Key,限制Android应用的包名和SHA-1指纹
  3. 在AndroidManifest.xml里添加API Key
  4. 在布局文件里放一个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。

集成步骤类似,但多了个「申请密钥」的环节:

  1. 去百度地图开放平台注册应用,生成AK
  2. 下载SDK(或者用Maven依赖)
  3. 在AndroidManifest里配置AK和权限
  4. 初始化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))
核心区别:Google Maps用SupportMapFragment,百度用MapView。Google的API更简洁,百度的功能更本地化(比如支持实时路况、室内地图)。

地理编码:把地址变成坐标,反过来也行

地理编码(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
    // 拿到坐标了
}
注意:Geocoder的getFromLocationName是同步方法,不能在主线程调用。我建议用协程或AsyncTask包一层。另外,Google的Geocoder在国内有时会返回空结果,这时候可以降级到百度或高德的地理编码服务。

知识体系总览

下面这张图,把定位与地图的核心知识点串起来了。你可以把它当作一个快速索引:

定位与地图 定位权限 ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION ACCESS_BACKGROUND_LOCATION Google Maps SDK SupportMapFragment getMapAsync 回调 Marker / CameraUpdate 百度地图SDK MapView / BaiduMap SDKInitializer 初始化 Overlay / 路况 地理编码 反向地理编码 定位与地图知识体系总览

避坑指南:我踩过的那些坑

  • 定位权限弹窗被系统拦截:Android 11以后,如果用户连续两次拒绝权限,系统会不再弹窗。这时候你需要引导用户去设置里手动开启。我曾经有个用户反馈说「App一直要权限,但我点了允许啊」,后来发现是系统把弹窗静默了。
  • 地图瓦片加载失败:Google Maps在国内有时加载不出瓦片,显示空白。解决办法是加一个网络切换监听,检测到是国内网络时自动切换到百度地图。
  • 百度地图初始化必须在Application中:如果你在Activity里才调用SDKInitializer.initialize,会直接崩溃。嗯,这个坑我踩过两次才记住。
  • Geocoder在主线程调用:会导致ANR。一定要放到子线程或协程里执行。
我的建议:如果你的App只面向国内用户,直接用百度地图或高德地图。如果面向海外用户,用Google Maps。如果两者都要,那就做动态切换——根据用户IP或系统语言来决定加载哪个SDK。不过这样会增加包体积,你自己权衡。

好了,定位与地图的核心内容就这些。说白了,权限是门槛,SDK是工具,地理编码是桥梁。把这三块搞明白,大部分地图场景都能应付。


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