如何正确处理位置服务切换

How to properly handle location service toggling

在我的应用程序中,我有一个使用 FusedLocationProviderClient 获取用户当前位置的按钮。这可以正常工作,直到位置服务关闭然后再在设备上重新打开。发生这种情况时,FusedLocationProviderClient.getLastLocation() 总是 returns null。我应该如何处理这种情况?我需要以某种方式重新初始化 FusedLocationProviderClient 吗?

我从documentation that when location services are turned off, the last cached location is flushed. This tells me that I need to force a location request so the cache gets refreshed. However, I'm unsure of how to do this. I've also followed this Medium article了解到FusedLocationProviderClient的设置,但它所做的只是检查定位结果是否为空,当它为空时不做任何处理。

我正在用 Kotlin 编写应用程序。以下是我如何初始化 FusedLocationProviderClient 并在我的片段中获取用户的当前位置:

class LocationFragment : Fragment() {

    private lateinit var mFusedLocationProviderClient: FusedLocationProiderClient

    override fun onCreate(savedInstanceState: Bundle?) {
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(activity)
    }

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        // Initialize button on-click callback.
    }

    private fun getCurrentLocation() {
        // Check that the location service is enabled.
        // Check coarse and fine location permissions.

        mFusedLocationProviderClient.lastLocation.addOnSuccessListener {
            if (it != null) { // Always false if location services are toggled.
                print(it.latitude, it.longitude)
            }
        }
    }
}   

我没有对 FusedLocationProviderClient 做很多事情,但据我了解(文档似乎也同意),使用 lastLocation(getLastLocation() for Java 人)不会'不是真的出去 "fetch" 当前设备位置,而只是 return 设备获取的最后一个位置。通常,设备上的任何数量的服务都在请求位置,因此缓存中始终有一个相当准确的值供 lastLocation 使用。但是,如果您关闭并重新打开定位服务,然后立即调用 lastLocation,您很可能会收到 null returned。另请注意 lastLocation 状态的文档:

It is particularly well suited for applications that do not require an accurate location

并且来自 getLocationAvailability() 调用的文档:

Note it's always possible for getLastLocation() to return null even when this method returns true (e.g. location settings were disabled between calls).

这些也证实了该方法实际上并不进行位置查找,而只是 return 从缓存中进行最近的查找,当您切换服务时 off/on,缓存是空的。

如果您需要确保获取设备的当前位置并确保其相当准确,您可能需要使用 requestLocationUpdates() 让位置客户端实际计算出设备的当前位置和 return 它在你提供的回调中,然后在你完成后删除回调。

自从我与位置提供商合作以来已经有一段时间了,但我认为你可以做这样的事情,假设你只需要一个位置:

    private val locationRequest: LocationRequest by lazy {
        LocationRequest.create().apply {
            interval = (LOCATION_UPDATE_INTERVAL_SECONDS * 1000).toLong()
            fastestInterval = (LOCATION_FAST_INTERVAL_SECONDS * 1000).toLong()
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
            smallestDisplacement =
                MINIMUM_DISPLACEMENT_METERS
        }
    }

    private fun isValidLocation(location: Location): Boolean {
        /* TODO: validate that the location meets all of your
                 requirements for accuracy, etc, and return the
                 appropriate true/false value
         */
        return true  // or false, for example if the location accuracy is not good enough.    
    }    


    private val locationCallback = object: LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {

            Log.d(TAG, "Location Received: $locationResult")

            val location = locationResult.lastLocation

            if (isValidLocation(location)) {
                Log.d(TAG, "Valid Location dectected: $location")

                // we have a valid location, so stop receiving further location updates.
                mFusedLocationClient.removeLocationUpdates(this)

                //TODO: we have a valid location! Use it as you wish to update the
                //      view, or emit it via a LiveData, etc.
            } else {
                // nothing to do, wait for more results.
                Log.d(TAG, "Location received was not valid: $location")
            }

        }
    }    

    private fun watchLocation() {

        // Check Location Permissions
        // Check Google Play Services Version
        // Check Location Settings
        // If all three are good, then:

        mFusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())
    } 

上面的代码片段设置了一个 LocationCallback,它在收到位置时被调用。它检查位置(通过 isValidLocation 方法),如果它有效,它会从提供者中删除自己,这样您就不会获得额外的位置更新。在它自己删除的那一行 (mFusedLocationClient.removeLocationUpdates(this)) 之后,您可以对 location 对象做任何您需要做的工作。如果收到的位置无效,它会在收到位置时继续收到额外的回调,直到您收到一个有效的回调。要开始这一切,只需调用 watchLocation()