FusedLocationProviderClient 与 Kotlin 协程
FusedLocationProviderClient with Kotlin coroutines
我正在尝试使用 FusedLocationProviderClient 和 Kotlin 协程请求新位置。这是我当前的设置:
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()),这是否意味着我只能调用该函数一次?
感谢任何帮助。
通过使用 suspendCoroutine
,您可以在 运行 时间内调用调用挂起函数的调度程序上提供的代码。由于大多数调度程序不会 运行 在 Looper 线程上(几乎只有 Dispatchers.MAIN
会),所以对 Looper.myLooper()
的调用失败。
文档说您可以将 Looper.myLooper()
替换为 null
以在未指定的线程上调用回调。内置协程调度程序将确保它被路由到正确的线程以恢复执行。
编辑:您可能需要调用 it.intercepted().resume(latLng)
以确保将结果分派到正确的线程。我不完全确定默认情况下 suspendCoroutine
延续是否被拦截。
此外你不需要调用fusedLocationProviderClient.removeLocationUpdates(this)
因为你已经在LocationRequest
中设置了更新次数为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 })
}
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)
}
}
}
我正在尝试使用 FusedLocationProviderClient 和 Kotlin 协程请求新位置。这是我当前的设置:
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()),这是否意味着我只能调用该函数一次?
感谢任何帮助。
通过使用 suspendCoroutine
,您可以在 运行 时间内调用调用挂起函数的调度程序上提供的代码。由于大多数调度程序不会 运行 在 Looper 线程上(几乎只有 Dispatchers.MAIN
会),所以对 Looper.myLooper()
的调用失败。
文档说您可以将 Looper.myLooper()
替换为 null
以在未指定的线程上调用回调。内置协程调度程序将确保它被路由到正确的线程以恢复执行。
编辑:您可能需要调用 it.intercepted().resume(latLng)
以确保将结果分派到正确的线程。我不完全确定默认情况下 suspendCoroutine
延续是否被拦截。
此外你不需要调用fusedLocationProviderClient.removeLocationUpdates(this)
因为你已经在LocationRequest
中设置了更新次数为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 })
}
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)
}
}
}