Android MVVM下LocationCallback未触发 地图无法定位问题
问题根因
LocationCallback不触发、地图无法自动定位是4个代码逻辑错误导致的,和MVVM分层本身无关:
- 持续位置监听没有在页面进入时启动:注册连续位置更新的
trackUser()被你设为私有,只有点击开始跑步按钮触发startTracking()时才会调用,进入页面时仅调用了获取缓存位置的getUserLocation(),自然不会触发持续回调的LocationCallback - 初始位置获取存在空指针风险:
fusedLocationProviderClient.lastLocation在设备重启、位置服务刚开启、从未获取过位置的场景下会返回null,你代码里直接调用location.latitude没有判空,会直接静默崩溃,拿不到初始位置 - 计步器逻辑存在实例错误:每次调用
RPMLiveData()都会新建一个内部类实例,注册、注销监听器操作的不是同一个对象,后续会导致计步不准、内存泄漏 - 权限注册逻辑有缺陷:你用
activity?.registerForActivityResult注册权限回调,在Fragment附着状态变化时容易空指针,且仅在MainActivity申请权限无法覆盖Fragment内的权限校验场景
修复步骤
1. 修正ViewModel层位置逻辑
修改RunSessionViewModel.kt中对应代码,补全空判断、修复实例创建问题、增加生命周期兜底:
// 替换原有getUserLocation方法,增加空判断和无缓存位置的兜底请求 @SuppressLint("MissingPermission") fun getUserLocation() { mFusedLocationProviderClient.lastLocation.addOnSuccessListener { location -> location?.let { val latLng = LatLng(it.latitude, it.longitude) _locations.add(latLng) _liveLocation.postValue(latLng) } ?: run { // 无缓存位置时发起单次位置请求 val singleLocationRequest = LocationRequest.create().apply { priority = PRIORITY_HIGH_ACCURACY numUpdates = 1 } mFusedLocationProviderClient.requestLocationUpdates( singleLocationRequest, object : LocationCallback(){ override fun onLocationResult(result: LocationResult) { super.onLocationResult(result) result.lastLocation?.let {loc -> val latLng = LatLng(loc.latitude, loc.longitude) _locations.add(latLng) _liveLocation.postValue(latLng) } mFusedLocationProviderClient.removeLocationUpdates(this) } }, Looper.getMainLooper() ) } } .addOnFailureListener { failure -> Timber.d("lastLocation failure ${failure.message}") } } // 将trackUser方法改为公开,增加重复注册判断和空判断 @SuppressLint("MissingPermission") fun trackUser() { // 先移除已有回调,避免重复注册 mFusedLocationProviderClient.removeLocationUpdates(locationCallback) val locationRequest = LocationRequest.create().apply { priority = PRIORITY_HIGH_ACCURACY interval = 5000L fastestInterval = 2000L smallestDisplacement = 5f // 静止时不触发回调,减少耗电 } mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()) } // 修复RPMLiveData重复创建问题,改用懒加载单例 private val rpmLiveData by lazy { RPMLiveData() } fun setupStepCounter() = rpmLiveData.setupStepCounter() fun unloadStepCounter() = rpmLiveData.unloadStepCounter() // 给locationCallback增加空判断 private val locationCallback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { super.onLocationResult(result) val currentLocation = result.lastLocation ?: return val latLng = LatLng(currentLocation.latitude, currentLocation.longitude) val lastLocation = _locations.lastOrNull() if (lastLocation != null) { distance += SphericalUtil.computeDistanceBetween(lastLocation, latLng).roundToInt() _liveDistance.postValue(distance) } _locations.add(latLng) _liveLocation.postValue(latLng) _liveLocations.postValue(locations) } } // 重写生命周期方法,页面销毁时自动释放资源 override fun onCleared() { super.onCleared() mFusedLocationProviderClient.removeLocationUpdates(locationCallback) unloadStepCounter() }
记得同步修改stopTracking方法里的计步器注销逻辑,替换为unloadStepCounter(),不要直接新建RPMLiveData实例。
2. 修正Fragment层权限和调用逻辑
修改RunTrackerFragment.kt中权限注册相关代码:
// 直接在Fragment上注册权限回调,不要依赖activity的注册方法 private val locationPermissionProviderContract = registerForActivityResult( ActivityResultContracts.RequestPermission()) { granted -> if (granted) { viewModel.getUserLocation() // 权限通过时如果已经处于跑步状态,直接启动位置追踪 if (binding.btnStartStop.text == getString(R.string.stop_label)) { viewModel.trackUser() } } else { Toast.makeText(requireContext(), "需要位置权限才能记录跑步轨迹", Toast.LENGTH_SHORT).show() } } private val activityRecognitionPermissionProviderContract = registerForActivityResult( ActivityResultContracts.RequestPermission()) { granted -> if (granted) { viewModel.setupStepCounter() } } // 替换requestActivityRecognition里的实例化调用 fun requestActivityRecognition() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { activityRecognitionPermissionProviderContract.launch(Manifest.permission.ACTIVITY_RECOGNITION) } else { viewModel.setupStepCounter() } } // 修改stopTracking方法,调用ViewModel统一暴露的注销方法 private fun stopTracking() { viewModel.stopTracking() viewModel.unloadStepCounter() binding.container.txtTime.stop() }
3. 清单文件校验
确认AndroidManifest.xml中已经声明以下权限,否则动态申请无法生效:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <!-- Android10以上需要后台追踪时添加,前台跑步使用可省略 --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
测试时确认设备位置服务处于开启状态,模拟器需要手动在应用设置中授予位置权限,不要完全依赖MainActivity中的权限申请逻辑。
修复完成后进入页面会自动拉取当前位置并移动地图视角,点击开始跑步后LocationCallback会按配置间隔持续返回位置更新,自动绘制跑步轨迹。
内容的提问来源于stack exchange,提问作者Isaac Chen
相关产品推荐
相关产品推荐

