简体   繁体   English

FusedLocationProviderClient 与 Kotlin 协程

[英]FusedLocationProviderClient with Kotlin coroutines

I am trying to request a new location with FusedLocationProviderClient and Kotlin Coroutines.我正在尝试使用 FusedLocationProviderClient 和 Kotlin 协程请求一个新位置。 This is my current setup:这是我目前的设置:

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

But when trying to request a new location, I get the following exception:但是在尝试请求新位置时,出现以下异常:

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

However, if I would call Looper.prepare() (and Looper.quit() eventually) wouldn't it mean that I can call the function only once?但是,如果我调用 Looper.prepare()(最终调用 Looper.quit())是不是意味着我只能调用该函数一次?

Any help is appreciated.任何帮助表示赞赏。

You have set up your coroutineContext wrong.你的coroutineContext设置错了。 You should instead have你应该有

override val coroutineContext = Dispatchers.MAIN + job

If you ever need the IO dispatcher, then require it explicitly:如果您需要IO调度程序,则明确要求它:

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

To suspend the coroutine, call suspendCancellableCoroutine , otherwise you won't get any benefit from structured concurrency.要暂停协程,请调用suspendCancellableCoroutine ,否则您将无法从结构化并发中获得任何好处。

Another detail, don't write any code after it.resume in the suspendCancellableCoroutine block.另一个细节,不写后的任何代码it.resumesuspendCancellableCoroutine块。 If the dispatcher chooses to resume the coroutine immediately, within the resume call, that code won't execute until all the code of the coroutine has run (or at least until the next suspension point).如果调度程序选择立即恢复协程,在resume调用中,该代码将不会执行,直到协程的所有代码都运行(或至少直到下一个暂停点)。

override fun onLocationResult(result: LocationResult) {
    fusedLocationProviderClient.removeLocationUpdates(this)
    it.resume(result.lastLocation.run { latitude at longitude })
}
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)
        }
    }
}

By using suspendCoroutine you invoke the provided code on the dispatcher that called the suspended function during runtime.通过使用suspendCoroutine您可以调用在运行时调用挂起函数的调度程序上提供的代码。 Since most dispatchers don't run on Looper threads (pretty much only Dispatchers.MAIN does) the call to Looper.myLooper() fails.由于大多数调度程序不在 Looper 线程上运行(几乎只有Dispatchers.MAIN运行),因此对Looper.myLooper()的调用失败。

The documentation says you can replace Looper.myLooper() with null to invoke the callback on an unspecified thread.该文档说您可以用null替换Looper.myLooper()以在未指定的线程上调用回调。 The built-in coroutine dispatcher will then make sure it is routed to the correct thread for resuming the execution.然后内置的协程调度器将确保它被路由到正确的线程以恢复执行。

EDIT: You might need to call it.intercepted().resume(latLng) to ensure the result is dispatched to the correct thread.编辑:您可能需要调用it.intercepted().resume(latLng)以确保将结果分派到正确的线程。 I'm not entirely sure if the suspendCoroutine continuation is intercepted by default.我不完全确定是否默认拦截了suspendCoroutine延续。

In addition you don't need to call fusedLocationProviderClient.removeLocationUpdates(this) because you already set the number of updates in the LocationRequest to 1 .此外,您不需要调用fusedLocationProviderClient.removeLocationUpdates(this)因为您已经将LocationRequest的更新次数设置为1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM