Estoy tratando de solicitar una nueva ubicación con FusedLocationProviderClient y Kotlin Coroutines. Esta es mi configuración actual:
class LocationProviderImpl(context: Context) : LocationProvider, CoroutineScope { private val TAG = this::class.java.simpleName private val job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.IO private val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) private val locationRequest = LocationRequest().apply { numUpdates = 1 priority = LocationRequest.PRIORITY_HIGH_ACCURACY } override suspend fun getLocation(): LatLng = suspendCoroutine { val locationCallback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { result.lastLocation.run { val latLng = latitude at longitude it.resume(latLng) } fusedLocationProviderClient.removeLocationUpdates(this) } } try { fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper()) } catch (e: SecurityException) { throw NoLocationPermissionException() } } }Pero cuando intento solicitar una nueva ubicación, obtengo la siguiente excepción:
java.lang.IllegalStateException: Can't create handler inside thread that has not called Looper.prepare()Sin embargo, si llamara a Looper.prepare() (y Looper.quit() eventualmente) ¿no significaría que puedo llamar a la función solo una vez?
Cualquier ayuda es apreciada.
private val locationRequestGPS by lazy { LocationRequest.create() .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) .setNumUpdates(1) .setExpirationDuration(1000) } private val locationRequestNETWORK by lazy { LocationRequest.create() .setPriority(LocationRequest.PRIORITY_LOW_POWER) .setNumUpdates(1) .setExpirationDuration(1000) } suspend fun getLocation(context: Context, offsetMinutes: Int = 15): Location? = suspendCoroutine { task -> val ctx = context.applicationContext if (!ctx.isPermissionValid(Manifest.permission.ACCESS_COARSE_LOCATION) && !ctx.isPermissionValid(Manifest.permission.ACCESS_FINE_LOCATION)) { task.resume(null) } else { val manager = ctx.getSystemService(Context.LOCATION_SERVICE) as LocationManager if (!LocationManagerCompat.isLocationEnabled(manager)) { task.resume(null) } else { val service = LocationServices.getFusedLocationProviderClient(ctx) service.lastLocation .addOnCompleteListener { locTask -> if (locTask.result == null || System.currentTimeMillis() - locTask.result!!.time > offsetMinutes.minute) { GlobalScope.launch(Dispatchers.Main) { task.resume(locationRequest(manager, service)) } } else { task.resume(locTask.result) } } } } } suspend fun getLocationLast(context: Context): Location? = suspendCoroutine { task -> val ctx = context.applicationContext if (!ctx.isPermissionValid(Manifest.permission.ACCESS_COARSE_LOCATION) && !ctx.isPermissionValid(Manifest.permission.ACCESS_FINE_LOCATION)) { task.resume(null) } else { if (!LocationManagerCompat.isLocationEnabled(ctx.getSystemService(Context.LOCATION_SERVICE) as LocationManager)) { task.resume(null) } else { LocationServices.getFusedLocationProviderClient(ctx) .lastLocation .addOnCompleteListener { locTask -> task.resume(locTask.result) } } } } suspend fun locationRequest(locationManager: LocationManager, service: FusedLocationProviderClient): Location? = suspendCoroutine { task -> val callback = object : LocationCallback() { override fun onLocationResult(p0: LocationResult?) { service.removeLocationUpdates(this) task.resume(p0?.lastLocation) } } when { locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) -> { service.requestLocationUpdates(locationRequestGPS, callback, Looper.getMainLooper()) } locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) -> { service.requestLocationUpdates(locationRequestNETWORK, callback, Looper.getMainLooper()) } else -> { task.resume(null) } } }Has configurado mal tu coroutineContext . En su lugar, deberías tener
override val coroutineContext = Dispatchers.MAIN + job Si alguna vez necesita el despachador de IO , pídale explícitamente:
withContext(Dispatchers.IO) { ... blocking IO code ... } Para suspender la rutina, llama a suspendCancellableCoroutine , de lo contrario no obtendrás ningún beneficio de la concurrencia estructurada.
Otro detalle, no escriba ningún código después de él. it.resume en el bloque suspendCancellableCoroutine . Si el despachador elige reanudar la rutina inmediatamente, dentro de la llamada de resume , ese código no se ejecutará hasta que se haya ejecutado todo el código de la rutina (o al menos hasta el siguiente punto de suspensión).
override fun onLocationResult(result: LocationResult) { fusedLocationProviderClient.removeLocationUpdates(this) it.resume(result.lastLocation.run { latitude at longitude }) }Al usar suspendCoroutine , invoca el código proporcionado en el despachador que llamó a la función suspendida durante el tiempo de ejecución. Dado que la mayoría de los despachadores no se ejecutan en subprocesos de Looper (prácticamente solo lo hace Dispatchers.MAIN ), la llamada a Looper.myLooper() falla.
La documentación dice que puede reemplazar Looper.myLooper() con null para invocar la devolución de llamada en un hilo no especificado. El despachador de rutina incorporado se asegurará de que se enruta al subproceso correcto para reanudar la ejecución.
EDITAR: es posible que deba llamarlo it.intercepted().resume(latLng) para asegurarse de que el resultado se envíe al hilo correcto. No estoy del todo seguro de si la continuación de suspendCoroutine se intercepta de forma predeterminada.
Además, no necesita llamar a fusedLocationProviderClient.removeLocationUpdates(this) porque ya configuró la cantidad de actualizaciones en LocationRequest en 1 .