FusedLocationProviderClient с соплинами Kotlin - PullRequest
1 голос
/ 28 марта 2019

Я пытаюсь запросить новое местоположение с помощью FusedLocationProviderClient и Kotlin Coroutines. Это мои текущие настройки:

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()
        }
    }
}

Но при попытке запросить новое местоположение я получаю следующее исключение:

java.lang.IllegalStateException: Can't create handler inside thread that has not called Looper.prepare()

Однако, если бы я вызывал Looper.prepare () (и Looper.quit () в конце концов), разве это не означало бы, что я могу вызвать функцию только один раз?

Любая помощь приветствуется.

Ответы [ 2 ]

1 голос
/ 29 марта 2019

Вы неправильно настроили coroutineContext. Вместо этого вы должны иметь

override val coroutineContext = Dispatchers.MAIN + job

Если вам когда-нибудь понадобится диспетчер IO, тогда потребуйте его явно:

withContext(Dispatchers.IO) { ... blocking IO code ... }

Чтобы приостановить сопрограмму, позвоните suspendCancellableCoroutine, иначе вы не получите никакой выгоды от структурированного параллелизма.

Еще одна деталь: не пишите код после it.resume в блоке suspendCancellableCoroutine. Если диспетчер решит возобновить сопрограмму немедленно, в рамках вызова resume, этот код не будет выполнен, пока не будет выполнен весь код сопрограммы (или, по крайней мере, до следующей точки приостановки).

override fun onLocationResult(result: LocationResult) {
    fusedLocationProviderClient.removeLocationUpdates(this)
    it.resume(result.lastLocation.run { latitude at longitude })
}
1 голос
/ 28 марта 2019

Используя suspendCoroutine, вы вызываете предоставленный код в диспетчере, который вызвал приостановленную функцию во время выполнения. Поскольку большинство диспетчеров не работают в потоках Looper (в основном это делает только Dispatchers.MAIN), вызов Looper.myLooper() завершается неудачей.

В документации сказано, что вы можете заменить Looper.myLooper() на null, чтобы вызвать обратный вызов в неопределенном потоке. Затем встроенный диспетчер сопрограмм убедится, что он направлен в правильный поток для возобновления выполнения.

РЕДАКТИРОВАТЬ: Вам может потребоваться вызвать it.intercepted().resume(latLng), чтобы убедиться, что результат отправляется в правильный поток. Я не совсем уверен, что продолжение suspendCoroutine перехвачено по умолчанию.

Кроме того, вам не нужно звонить на fusedLocationProviderClient.removeLocationUpdates(this), потому что вы уже установили количество обновлений в LocationRequest на 1.

...