Android架构组件:ViewModel未随位置数据更新问题排查
解决LocationViewModel无法更新UI的问题
嘿,我来帮你排查这个问题!你的代码里有几个关键疏漏导致TextView始终没更新,咱们一步步理清楚:
你踩的几个坑
缺少位置权限处理
FusedLocationProvider需要ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION权限才能正常工作。没有权限的话,系统根本不会触发位置回调,currentLocation自然不会有任何更新。LocationCallback被局部变量持有,易被GC回收
你在init块里创建的locationCallback是局部变量,当init执行完后,这个回调很可能被垃圾回收,后续的位置更新就无法被接收了。位置设置检查成功的逻辑缺失
你只在checkLocationSettings的onFailureListener里处理了null值,但完全漏掉了设置满足条件时的逻辑——这时候才应该发起位置更新请求啊!ViewModel获取方式过时
ViewModelProviders已经被废弃了,现在应该用ViewModelProvider来获取ViewModel实例。
正确的实现方式
第一步:添加权限到AndroidManifest.xml
先在清单文件里声明必要的位置权限:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- 如果你的应用需要后台定位(Android 10+),再加上这个 --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
第二步:修复LocationViewModel
把locationCallback改成成员变量,补全位置设置成功的逻辑,同时在ViewModel销毁时移除位置更新避免内存泄漏:
class LocationViewModel(application: Application) : AndroidViewModel(application) { val currentLocation = MutableLiveData<Location?>() private lateinit var fusedLocationProvider: FusedLocationProviderClient private lateinit var locationCallback: LocationCallback init { val ctx = getApplication<Application>().applicationContext fusedLocationProvider = LocationServices.getFusedLocationProviderClient(ctx) val locationRequest = LocationRequest.create().apply { priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY interval = 5000 fastestInterval = 2000 } val settingsBuilder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest) LocationServices.getSettingsClient(ctx) .checkLocationSettings(settingsBuilder.build()) .addOnSuccessListener { // 位置设置符合要求,开始请求位置更新 startLocationUpdates(locationRequest) } .addOnFailureListener { exception -> when ((exception as ApiException).statusCode) { LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> { // 位置设置未开启,这里可以通过LiveData通知Activity引导用户开启 currentLocation.postValue(null) } else -> currentLocation.postValue(null) } } } private fun startLocationUpdates(locationRequest: LocationRequest) { locationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult?) { locationResult ?: return currentLocation.postValue(locationResult.lastLocation) } } // 检查是否有位置权限 val hasPermission = ContextCompat.checkSelfPermission( getApplication(), Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( getApplication(), Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED if (hasPermission) { fusedLocationProvider.requestLocationUpdates( locationRequest, locationCallback, Looper.getMainLooper() ) } else { currentLocation.postValue(null) } } override fun onCleared() { super.onCleared() // 页面销毁时移除位置更新,防止内存泄漏 fusedLocationProvider.removeLocationUpdates(locationCallback) } }
第三步:修复MainActivity
处理权限请求,用最新的ViewModelProvider获取ViewModel:
class MainActivity : AppCompatActivity() { private lateinit var locationText: TextView private val LOCATION_PERMISSION_REQUEST_CODE = 1001 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) locationText = findViewById(R.id.locationText) // 用新的ViewModelProvider获取实例 val locationViewModel = ViewModelProvider(this)[LocationViewModel::class.java] locationViewModel.currentLocation.observe(this) { resultLocation -> locationText.text = resultLocation?.let { "Lat: ${it.latitude} Long: ${it.longitude}" } ?: "无法获取位置,请检查权限和位置设置" } // 检查并请求位置权限 checkLocationPermission() } private fun checkLocationPermission() { val hasFinePerm = ContextCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED val hasCoarsePerm = ContextCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED if (!hasFinePerm && !hasCoarsePerm) { ActivityCompat.requestPermissions( this, arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ), LOCATION_PERMISSION_REQUEST_CODE ) } } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) { // 权限授予后,可以重新触发位置请求(比如给ViewModel加个refresh方法) val locationViewModel = ViewModelProvider(this)[LocationViewModel::class.java] // 这里可以调用ViewModel的刷新方法,或者因为ViewModel在init里已经尝试过,不过最好主动触发一次 } } }
关键修复总结
- 必须声明并请求位置权限,没有权限一切都是空谈
- 把
LocationCallback改为ViewModel成员变量,避免被GC回收 - 位置设置检查成功后才发起位置更新请求
- 使用最新的
ViewModelProvider替代过时的ViewModelProviders - 在
onCleared中移除位置更新,防止内存泄漏 - 处理位置设置未开启的场景,引导用户开启
内容的提问来源于stack exchange,提问作者Amol Borkar
相关产品推荐
相关产品推荐

