【问题标题】:FusedLocationProviderClient with Kotlin coroutinesFusedLocationProviderClient 与 Kotlin 协程
【发布时间】:2019-08-19 10:44:52
【问题描述】:

我正在尝试使用 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() )是不是意味着我只能调用该函数一次?

感谢任何帮助。

【问题讨论】:

    标签: android kotlin fusedlocationproviderapi kotlin-coroutines


    【解决方案1】:

    您的coroutineContext 设置错误。你应该有

    override val coroutineContext = Dispatchers.MAIN + job
    

    如果您需要 IO 调度程序,请明确要求:

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

    要暂停协程,请调用suspendCancellableCoroutine,否则您将无法从结构化并发中获得任何好处。

    另外一个细节,不要在suspendCancellableCoroutine块的it.resume之后写任何代码。如果调度程序选择立即恢复协程,在 resume 调用中,该代码将不会执行,直到协程的所有代码都已运行(或至少直到下一个暂停点)。

    override fun onLocationResult(result: LocationResult) {
        fusedLocationProviderClient.removeLocationUpdates(this)
        it.resume(result.lastLocation.run { latitude at longitude })
    }
    

    【讨论】:

    • 最好使用Dispatchers.Default 而不是 IO 进行非阻塞操作,例如计算
    • 我看不出这条评论的相关性。 OP 使用了IO 调度器,答案明确指出它仅用于阻塞 IO 操作,没有人提到 CPU 密集型操作。
    【解决方案2】:
    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)
            }
        }
    }
    

    【讨论】:

    • 未找到 ctx.isPermissionValid 和 offsetMinutes.minute
    【解决方案3】:

    通过使用suspendCoroutine,您可以在运行时调用挂起函数的调度程序上调用提供的代码。由于大多数调度程序不在 Looper 线程上运行(几乎只有 Dispatchers.MAIN 运行),所以对 Looper.myLooper() 的调用失败。

    文档说您可以将 Looper.myLooper() 替换为 null 以在未指定的线程上调用回调。然后,内置的协程调度程序将确保将其路由到正确的线程以恢复执行。

    编辑:您可能需要调用it.intercepted().resume(latLng) 以确保将结果分派到正确的线程。我不完全确定 suspendCoroutine 延续是否默认被拦截。

    此外,您无需调用fusedLocationProviderClient.removeLocationUpdates(this),因为您已经将LocationRequest 中的更新次数设置为1。

    【讨论】:

    • 感谢您的回复!传入 null 时,我再次遇到相同的异常... it.intercept() 方法对我不可用?!
    • "IllegalStateException 如果 looper 为 null 并且此方法在未调用 Looper.prepare() 的线程中执行。"您需要将 requestLocationUpdates 调用包装在 withContext(Dispatchers.MAIN) { ... } 块内。
    猜你喜欢
    • 1970-01-01
    • 2019-09-30
    • 2019-07-17
    • 1970-01-01
    • 2019-06-22
    • 2019-06-28
    • 2020-04-12
    • 2021-06-18
    • 1970-01-01
    相关资源
    最近更新 更多