![](/img/trans.png)
[英]Should i need to unregister locationcallback for fusedLocationProviderClient?
[英]FusedLocationProviderClient and LocationCallback() object queries
总结一下这个问题,我正在实现 google maps API 来制作运行跟踪器,同时也在实现 MVVM 架构。 但是,当我设法配置我的代码以将 LocationCallback 对象放置在我的 viewModel 和我的 fusedLocationProviderClient 中时,我的 LocationCallback 对象在我访问我试图实现它的片段之后根本无法被调用。 我试图 Log.d 对象内的任何内容,但什么都不会出现。 想知道我是否执行错了。 我已经在我的主要活动中请求了权限。
RunSessionViewModel.kt
package com.example.myfit_exercisecompanion.ui.viewModels
import android.Manifest
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Build
import android.os.Looper
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.myfit_exercisecompanion.repository.RunSessionRepository
import com.example.myfit_exercisecompanion.ui.MainActivity
import com.google.android.gms.location.*
import com.google.android.gms.location.Priority.PRIORITY_HIGH_ACCURACY
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.SphericalUtil
import dagger.hilt.android.lifecycle.HiltViewModel
import timber.log.Timber
import javax.inject.Inject
import kotlin.math.roundToInt
@HiltViewModel
class RunSessionViewModel @Inject constructor(
private val runSessionRepository: RunSessionRepository,
application: Application
): AndroidViewModel(application) {
private val _locationResults = MutableLiveData<LocationResult>()
val locationResults: LiveData<LocationResult>
get() = _locationResults
val context = application
val mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context.applicationContext)
val ui = MutableLiveData(Ui.EMPTY)
private val _locations = mutableListOf<LatLng>()
val locations: List<LatLng>
get() = _locations
private var distance = 0
private val _liveLocations = MutableLiveData<List<LatLng>>()
val liveLocations: MutableLiveData<List<LatLng>>
get() = _liveLocations
private val _liveDistance = MutableLiveData<Int>()
val liveDistance: MutableLiveData<Int>
get() = _liveDistance
private val _liveLocation = MutableLiveData<LatLng>()
val liveLocation: MutableLiveData<LatLng>
get() = _liveLocation
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
super.onLocationResult(result)
val currentLocation = result.lastLocation
val latLng = LatLng(currentLocation!!.latitude, currentLocation.longitude)
val lastLocation = _locations.lastOrNull()
if (lastLocation != null) {
distance += SphericalUtil.computeDistanceBetween(lastLocation, latLng).roundToInt()
_liveDistance.value = distance
}
_locations.add(latLng)
_liveLocations.value = locations
}
}
@SuppressLint("MissingPermission")
fun getUserLocation() {
mFusedLocationProviderClient.lastLocation.addOnSuccessListener { location ->
val latLng = LatLng(location.latitude, location.longitude)
_locations.add(latLng)
_liveLocation.value = latLng
}
.addOnFailureListener { failure ->
Timber.d("lastLocation failure ${failure.message}")
}
}
@SuppressLint("MissingPermission")
private fun trackUser() {
val locationRequest = LocationRequest.create().apply {
priority = PRIORITY_HIGH_ACCURACY
interval = 5000L
fastestInterval = 2000L
}
mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
}
fun stopTracking() {
mFusedLocationProviderClient.removeLocationUpdates(locationCallback)
_locations.clear()
distance = 0
RPMLiveData().unloadStepCounter()
}
fun startTracking() {
trackUser()
val currentUi = ui.value
ui.value = currentUi?.copy(
formattedPace = Ui.EMPTY.formattedPace,
formattedDistance = Ui.EMPTY.formattedDistance
)
}
private val _liveSteps = MutableLiveData<Int>()
val liveSteps:MutableLiveData<Int>
get() = _liveSteps
val rpmLiveData = RPMLiveData()
// inner class just to have access to application
inner class RPMLiveData : LiveData<String>(), SensorEventListener {
private val sensorManager by lazy {
context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
}
private val stepCounterSensor: Sensor? by lazy {
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
}
private var initialSteps = -1
fun setupStepCounter() {
if (stepCounterSensor != null) {
sensorManager.registerListener(this, stepCounterSensor,
SensorManager.SENSOR_DELAY_FASTEST
)
}
}
override fun onSensorChanged(event: SensorEvent) {
event.values.firstOrNull()?.toInt()?.let { newSteps ->
if (initialSteps == -1) {
initialSteps = newSteps
}
val currentSteps = newSteps - initialSteps
_liveSteps.value = currentSteps
}
}
fun unloadStepCounter() {
if (stepCounterSensor != null) {
sensorManager.unregisterListener(this)
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) = Unit
}
}
data class Ui(
val formattedPace: String,
val formattedDistance: String,
val currentLocation: LatLng?,
val userPath: List<LatLng>
) {
companion object {
val EMPTY = Ui(
formattedPace = "",
formattedDistance = "",
currentLocation = null,
userPath = emptyList()
)
}
}
RunTrackerFragment.kt
package com.example.myfit_exercisecompanion.ui.fragments
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.MutableLiveData
import com.example.myfit_exercisecompanion.R
import com.example.myfit_exercisecompanion.databinding.FragmentRunTrackerBinding
import com.example.myfit_exercisecompanion.ui.MainActivity
import com.example.myfit_exercisecompanion.ui.viewModels.RunSessionViewModel
import com.example.myfit_exercisecompanion.ui.viewModels.Ui
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.PolylineOptions
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class RunTrackerFragment : Fragment(R.layout.fragment_run_tracker), OnMapReadyCallback {
private lateinit var map: GoogleMap
private var _binding: FragmentRunTrackerBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private val viewModel: RunSessionViewModel by viewModels()
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
var initialSteps = -1
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentRunTrackerBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Timber.d("fusedLocationProviderClient not being a bitch ${viewModel.mFusedLocationProviderClient}")
val mapFragment = childFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
binding.btnStartStop.setOnClickListener {
if (binding.btnStartStop.text == getString(R.string.start_label)) {
startTracking()
binding.btnStartStop.setText(R.string.stop_label)
} else {
stopTracking()
binding.btnStartStop.setText(R.string.start_label)
}
}
viewModel.liveLocations.observe(viewLifecycleOwner) { locations ->
val current = viewModel.ui.value
viewModel.ui.value = current?.copy(userPath = locations)
}
viewModel.liveLocation.observe(viewLifecycleOwner) { currentLocation ->
val current = viewModel.ui.value
viewModel.ui.value = current?.copy(currentLocation = currentLocation)
}
viewModel.liveDistance.observe(viewLifecycleOwner) { distance ->
val current = viewModel.ui.value
val formattedDistance = requireContext().getString(R.string.distance_value, distance)
viewModel.ui.value = current?.copy(formattedDistance = formattedDistance)
}
viewModel.liveSteps.observe(viewLifecycleOwner) { steps ->
val current = viewModel.ui.value
viewModel.ui.value = current?.copy(formattedPace = "$steps")
}
}
private val locationPermissionProviderContract = activity?.registerForActivityResult(
ActivityResultContracts.RequestPermission()) { granted ->
if (granted) {
viewModel.getUserLocation()
Timber.d("Pass 1")
}
}
private val activityRecognitionPermissionProviderContract = activity?.registerForActivityResult(
ActivityResultContracts.RequestPermission()) { granted ->
if (granted) {
viewModel.RPMLiveData().setupStepCounter()
Timber.d("Pass 2")
}
}
fun requestUserLocation() {
locationPermissionProviderContract?.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
fun requestActivityRecognition() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
activityRecognitionPermissionProviderContract?.launch(Manifest.permission.ACTIVITY_RECOGNITION)
} else {
viewModel.RPMLiveData().setupStepCounter()
}
}
fun onMapLoaded(){
requestUserLocation()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
// presenter = (activity as MainActivity).presenter
viewModel.ui.observe(viewLifecycleOwner) { ui ->
Log.d("ui instantiated", viewModel.ui.value.toString())
updateUi(ui)
}
onMapLoaded()
map.uiSettings.isZoomControlsEnabled = true
}
private fun startTracking() {
binding.container.txtPace.text = ""
binding.container.txtDistance.text = ""
binding.container.txtTime.base = SystemClock.elapsedRealtime()
binding.container.txtTime.start()
map.clear()
Log.d("type:", "${binding.container.txtTime.base}")
viewModel.startTracking()
requestActivityRecognition()
}
private fun stopTracking() {
viewModel.stopTracking()
binding.container.txtTime.stop()
}
@SuppressLint("MissingPermission")
private fun updateUi(ui: Ui) {
if (ui.currentLocation != null && ui.currentLocation != map.cameraPosition.target) {
map.isMyLocationEnabled = true
map.animateCamera(CameraUpdateFactory.newLatLngZoom(ui.currentLocation, 14f))
}
binding.container.txtDistance.text = ui.formattedDistance
binding.container.txtPace.text = ui.formattedPace
drawRoute(ui.userPath)
}
private fun drawRoute(locations: List<LatLng>) {
val polylineOptions = PolylineOptions()
map.clear()
val points = polylineOptions.points
points.addAll(locations)
map.addPolyline(polylineOptions)
}
}
由于我的代码的结构方式,地图出现在我的片段上,但不在我当前的位置。 问题
我试图遵循的指南实际上是这里的链接,我想将其转换为 MVVM 架构。 https://www.raywenderlich.com/28767779-how-to-make-an-android-run-tracking-app
这个平台上的第一个问题,对于 android 开发来说相对较新,如果我发错了或者我的代码非常不一致,请原谅我。
我在我的应用程序中使用类 LocationLiveData,它扩展了 MutableLiveData 并且它工作得很好。 见下面的代码:
class LocationLiveData private constructor(context: Context) : MutableLiveData<Location>() {
private val client: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
private var minAccuracy: Float = 0.toFloat()
private var stopWhenCatch: Boolean = false
private var stopped: Boolean = false
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult?.lastLocation?.let { location ->
if (location.accuracy <= minAccuracy) {
checkLocationAccuracy()
value = location
if (stopWhenCatch) {
stopped(true)
stopWatch()
}
}
}
}
}
override fun onActive() {
if (value == null || stopped) {
startWatch()
}
}
override fun onInactive() {
stopWatch()
}
fun stopped(stopped: Boolean) {
this.stopped = stopped
}
@SuppressLint("MissingPermission")
private fun checkLocationAccuracy() {
if (minAccuracy == DEFAULT_LOW_ACCURACY) {
minAccuracy = DEFAULT_HIGH_ACCURACY
}
}
@SuppressLint("MissingPermission")
private fun startWatch() {
client.requestLocationUpdates(createLocationHighAccuracyRequest(), locationCallback, Looper.myLooper())
}
private fun stopWatch() {
client.removeLocationUpdates(locationCallback)
}
private fun createLocationHighAccuracyRequest() =
LocationRequest
.create()
.setInterval(INTERVAL)
.setFastestInterval(FASTEST_INTERVAL)
.setSmallestDisplacement(DISTANCE)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
class Builder(private val mContext: Context) {
private var minAccuracy: Float = DEFAULT_LOW_ACCURACY
private var stopWhenCatch: Boolean = false
fun minAccuracy(minAccuracy: Float): Builder {
this.minAccuracy = minAccuracy
return this
}
fun stopWhenCatch(stopWhenCatch: Boolean): Builder {
this.stopWhenCatch = stopWhenCatch
return this
}
fun build() = LocationLiveData(mContext).apply {
minAccuracy = this@Builder.minAccuracy
stopWhenCatch = this@Builder.stopWhenCatch
}
}
companion object {
private const val DEFAULT_HIGH_ACCURACY = 50f
private const val DEFAULT_LOW_ACCURACY = 10_000f // low accuracy for fast finding location on start application, 10km
private const val INTERVAL = 10_000L
private const val FASTEST_INTERVAL = 5_000L
private const val DISTANCE = 5f
}
}
来自 ViewModel 的代码:
private var locationLiveData: LocationLiveData? = null
fun subscribeLocationLiveData(context: Context): LocationLiveData {
locationLiveData = LocationLiveData.Builder(context)
.build()
return locationLiveData!!
}
// call where you need to stop getting location
fun unsubscribeLocationLiveData() {
locationLiveData?.stopped(true)
}
片段代码:
private fun subscribeLocationUpdate() {
viewModel.subscribeLocationLiveData(requireContext()).observe(
viewLifecycleOwner,
Observer { location ->
// here will be user location
}
)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.