26、LiveData与Location:位置更新监听、使用LiveData管理位置数据、实战:地图定位
各位同学,今天我们来聊一个非常接地气的话题——位置更新。说实话,我在做Android开发的头两年,最怕的就是跟Location打交道。为什么?因为坑太多了。权限、生命周期、定位不准、耗电……随便一个都能让你调试到怀疑人生。
但有了LiveData之后,情况就大不一样了。LiveData天生就是为这种「持续变化的数据」而生的。位置信息说白了就是一个不断变化的值,你用LiveData去包装它,生命周期自动管理,数据自动分发,简直不要太爽。
好,我们直接进入正题。
为什么用LiveData管理位置数据?
你想想看,位置更新有几个特点:
- 持续变化——用户一走动,经纬度就变了
- 多个观察者——地图要显示、附近商家要刷新、轨迹要记录
- 生命周期敏感——Activity退到后台就别再浪费电去定位了
LiveData恰好完美匹配这些需求。它只在活跃生命周期内分发数据,不会出现「页面已经销毁了还在回调」的尴尬情况。我在项目中遇到过好几次,没用LiveData之前,定位回调里还在更新UI,结果Activity已经finish了,直接崩掉。嗯,那种感觉,谁崩谁知道。
核心架构:LocationLiveData
我们直接封装一个 LocationLiveData,把FusedLocationProviderClient的监听逻辑藏进去。对外暴露一个LiveData,谁用谁订阅。
核心思路:继承LiveData,在onActive()时注册定位监听,在onInactive()时取消监听。这样LiveData没有活跃观察者时,定位服务自动停止,省电又省心。
class LocationLiveData(context: Context) : LiveData<Location>() {
private val fusedLocationClient =
LocationServices.getFusedLocationProviderClient(context)
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let { location ->
postValue(location)
}
}
}
override fun onActive() {
super.onActive()
val request = LocationRequest.create().apply {
interval = 5000 // 5秒一次
fastestInterval = 2000 // 最快2秒
priority = Priority.PRIORITY_HIGH_ACCURACY
}
fusedLocationClient.requestLocationUpdates(
request,
locationCallback,
Looper.getMainLooper()
)
}
override fun onInactive() {
super.onInactive()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
}
这段代码看起来简单,但有几个细节我要强调一下:
postValue()而不是setValue()——因为回调可能不在主线程,postValue是线程安全的onActive()和onInactive()是LiveData的生命周期钩子,系统自动调用- 定位请求的interval别设太短,5秒一次已经够用了,太频繁只会让手机发烫
小技巧:如果你需要获取最后一次已知位置(不用实时更新),可以先用 fusedLocationClient.lastLocation 拿一个快照,再决定是否开启实时监听。我一般会先拿快照展示地图,再慢慢等实时更新。
在ViewModel中使用
ViewModel里怎么用?很简单,把LocationLiveData暴露出去就行。
class LocationViewModel(application: Application) : AndroidViewModel(application) {
private val locationLiveData = LocationLiveData(application)
fun getLocationUpdates(): LiveData<Location> = locationLiveData
fun refreshLocation() {
// 手动触发一次位置刷新
val fusedClient = LocationServices.getFusedLocationProviderClient(getApplication())
fusedClient.lastLocation.addOnSuccessListener { location ->
location?.let { locationLiveData.postValue(it) }
}
}
}
这里有个小坑——AndroidViewModel 需要传入Application,因为LocationLiveData需要Context来获取定位服务。我个人习惯把这种依赖通过构造器传进去,而不是在ViewModel内部硬编码,方便以后做单元测试。
实战:地图定位展示
好,理论说完了,我们来写一个实际的地图定位页面。假设你用的是百度地图或高德地图,核心逻辑是一样的:
- 在Activity/Fragment中观察LocationLiveData
- 每次位置更新时,移动地图中心点
- 添加一个定位图标标记
class MapActivity : AppCompatActivity() {
private lateinit var viewModel: LocationViewModel
private lateinit var mapView: MapView
private lateinit var baiduMap: BaiduMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
viewModel = ViewModelProvider(this).get(LocationViewModel::class.java)
mapView = findViewById(R.id.mapView)
baiduMap = mapView.map
// 观察位置变化
viewModel.getLocationUpdates().observe(this, Observer { location ->
location?.let { updateMapLocation(it) }
})
}
private fun updateMapLocation(location: Location) {
val latLng = LatLng(location.latitude, location.longitude)
val update = MapStatusUpdateFactory.newLatLngZoom(latLng, 18f)
baiduMap.animateMapStatus(update)
// 添加定位标记
val overlay = MyLocationOverlay(baiduMap)
overlay.setLocationData(location)
baiduMap.setMyLocationOverlay(overlay)
}
override fun onResume() {
super.onResume()
mapView.onResume()
}
override fun onPause() {
super.onPause()
mapView.onPause()
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
}
这段代码里,observe(this, Observer) 中的 this 就是LifecycleOwner。当Activity进入后台,LiveData自动停止分发;回到前台,自动恢复。你什么都不用管。
注意:地图SDK的MapView本身也有自己的生命周期(onResume/onPause/onDestroy),千万别忘了调用。我曾经因为忘了调onResume,地图一片空白,查了半天才发现是生命周期没同步。
SVG:位置更新核心流程
下面这张图展示了整个数据流的走向,从定位服务到UI更新,LiveData在中间起到了「生命周期感知的桥梁」作用。
避坑指南
最后,我把自己踩过的坑总结一下,你们遇到类似问题可以直接翻到这里:
- 权限问题:Android 6.0以上需要动态申请
ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION。别忘了在定位前检查权限,否则直接崩。 - 模拟器定位:模拟器默认位置在Google总部,你可以在Extended Controls里手动设置经纬度,或者用GPS模拟文件。
- 定位精度:室内定位用WiFi和基站,误差几十米很正常。别指望GPS在室内也能准,那是物理限制。
- 电量优化:如果只是展示用户当前位置,用
PRIORITY_BALANCED_POWER_ACCURACY就够了,别一上来就开高精度。
我曾经做过一个外卖App,定位间隔设成了1秒一次,结果用户反馈手机发烫、电量狂掉。后来改成10秒一次,配合最后已知位置缓存,体验反而更好。有时候「少即是多」。
好了,这一章的内容就到这里。位置更新用LiveData来管理,说白了就是「把复杂留给框架,把简单留给业务」。你只需要关注位置数据来了之后怎么用,至于什么时候注册、什么时候注销,LiveData帮你搞定。