Android:LocationManager 与 Google Play 服务

Android : LocationManager vs Google Play Services

我想构建一个以获取用户当前位置为中心的应用程序,然后通过 找到靠近 him/her 的兴趣点(例如酒吧、餐馆等) Google 个地点 API

在网上搜索起点后,我发现了一些使用 LocationManager class 的教程和一些使用 Google Play 服务 为了找到用户的位置。

乍一看,他们都做同样的事情,但由于我是新手,所以我有点困惑,我不知道哪种方法最适合我的需要。所以,我想问你:

这两种查找位置的方法(如果有)有什么区别?

您应该使用 Google Play 服务位置 api 而不是 LocationManager。根据文档:

The Google Play services location APIs are preferred over the Android framework location APIs (android.location) as a way of adding location awareness to your app. If you are currently using the Android framework location APIs, you are strongly encouraged to switch to the Google Play services location APIs as soon as possible.

至于为什么要切换,Google是这样说的:

The Google Location Services API, part of Google Play Services, provides a more powerful, high-level framework that automatically handles location providers, user movement, and location accuracy. It also handles location update scheduling based on power consumption parameters you provide. In most cases, you'll get better battery performance, as well as more appropriate accuracy, by using the Location Services API.

用户位置 Android

在 Android 上获取用户的位置不如在 iOS 上直接。要开始混淆,有两种完全不同的方法可以做到这一点。第一个是使用 Android APIs 来自 android.location.LocationListener,第二个是使用 Google Play Services APIs com.google.android.gms.location.LocationListener。让我们来看看它们。

  1. Android的位置API

    Android 的位置 API 使用三个不同的提供商来获取位置 -

    • LocationManager.GPS_PROVIDER — 该提供商使用卫星确定位置。根据情况,此提供商可能需要一段时间才能 return 定位。
    • LocationManager.NETWORK_PROVIDER — 该提供商根据手机信号塔和 WiFi 接入点的可用性确定位置。通过网络查找检索结果。
    • LocationManager.PASSIVE_PROVIDER — 该提供商将 return 由其他提供商生成的位置。当其他应用程序或服务请求它们时,您被动地接收位置更新,而您自己实际上没有请求位置。

其要点是从系统获取LocationManager的对象,实现LocationListener,并在LocationManager上调用requestLocationUpdates

这是一个代码片段:

    LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
// Define a listener that responds to location updates
LocationListener locationListener = new LocationListener() {
    public void onLocationChanged(Location location) {
      // Called when a new location is found by the network location provider.
      makeUseOfNewLocation(location);
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {}

    public void onProviderEnabled(String provider) {}

    public void onProviderDisabled(String provider) {}
  };

// Register the listener with the Location Manager to receive location updates
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);

Google’s API Guide on Location Strategies explains the code pretty nicely. But they also mention that in most cases, you’ll get better battery performance, as well as more appropriate accuracy, by using the Google Location Services API 代替。现在开始混乱了!

  1. Google 的定位服务 API

Google 的位置服务 API 是 Google Play 服务 APK (here’s how to set it up) 的一部分。它们建立在 Android 的 API 之上。这些 API 提供了一个“Fused Location Provider”,而不是上面提到的提供者。该提供程序会根据准确性、电池使用情况等自动选择要使用的底层提供程序。速度很快,因为您可以从不断更新的系统范围服务中获取位置信息。您还可以使用更高级的功能,例如地理围栏。

要使用 Google 的定位服务,您的应用需要连接到 GooglePlayServicesClient。要连接到客户端,您的 activity(或片段等)需要实现 GooglePlayServicesClient.ConnectionCallbacksGooglePlayServicesClient.OnConnectionFailedListener 接口。 这是一个示例代码:

    public class MyActivity extends Activity implements ConnectionCallbacks, OnConnectionFailedListener {
    LocationClient locationClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        locationClient = new LocationClient(this, this, this);
    }

    @Override
    public void onConnected(Bundle bundle) {
    Location location = locationClient.getLastLocation() ;
        Toast.makeText(this, "Connected to Google Play Services", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onDisconnected() {
    Toast.makeText(this, "Connected from Google Play Services.", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        // code to handle failed connection
        // this code can be found here — http://developer.android.com/training/location/retrieve-current.html 
    }
  • 为什么 locationClient.getLastLocation() 为空?

locationClient.getLastLocation() 从客户端获取最后已知位置。但是,如果至少有一个客户端连接到 Fused Location Provider,则 Fused Location Provider 只会维护后台位置。一旦第一个客户端连接,它将立即尝试获取位置。如果您的 activity 是第一个连接的客户端并且您在 onConnected() 中立即调用 getLastLocation(),那么第一个位置可能没有足够的时间进入。这将导致 locationnull.

要解决此问题,您必须(不确定地)等待提供者获取位置然后调用 getLastLocation(),这是不可能知道的。另一个(更好的)选择是实现 com.google.android.gms.location.LocationListener 接口以接收定期位置更新(并在获得第一次更新后将其关闭)。

    public class MyActivity extends Activity implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener {
    // . . . . . . . . more stuff here 
    LocationRequest locationRequest;
    LocationClient locationClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // . . . . other initialization code
        locationClient = new LocationClient(this, this, this);
    locationRequest = new LocationRequest();
    // Use high accuracy
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        // Set the update interval to 5 seconds
    locationRequest.setInterval(UPDATE_INTERVAL);
        // Set the fastest update interval to 1 second
    locationRequest.setFastestInterval(FASTEST_INTERVAL);
    }
    // . . . . . . . . other methods 
    @Override
    public void onConnected(Bundle bundle) {
        Location location = locationClient.getLastLocation();
        if (location == null)
            locationClient.requestLocationUpdates(locationRequest, this);
        else
            Toast.makeText(getActivity(), "Location: " + location.getLatitude() + ", " + location.getLongitude(), Toast.LENGTH_SHORT).show();
    }
    // . . . . . . . . other methods
    @Override
    public void onLocationChanged(Location location) {
        locationClient.removeLocationUpdates(this);
        // Use the location here!!!
    }

在此代码中,您正在检查客户端是否已经拥有最后一个位置(在 onConnected 中)。如果没有,您将请求位置更新,并在收到更新后立即关闭请求(在 onLocationChanged() 回调中)。

请注意,locationClient.requestLocationUpdates(locationRequest, this); 必须在 onConnected 回调中,否则您将得到一个 IllegalStateException,因为您将尝试在未连接到 Google 播放服务客户端。

  • 用户已禁用定位服务

很多时候,用户会禁用位置服务(为了节省电池或隐私原因)。在这种情况下,上面的代码仍然会请求位置更新,但 onLocationChanged 永远不会被调用。您可以通过检查用户是否已禁用位置服务来停止请求。

如果您的应用需要他们启用定位服务,您可能希望显示消息或敬酒。不幸的是,无法检查用户是否在 Google 的位置服务 API 中禁用了位置服务。为此,您将不得不求助于 Android 的 API。

在您的 onCreate 方法中:

    LocationManager manager = (LocationManager) getActivity().getSystemService(Context.LOCATION_SERVICE);
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER) && !manager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
    locationEnabled = false;
    Toast.makeText(getActivity(), "Enable location services for accurate data", Toast.LENGTH_SHORT).show();
}
else locationEnabled = true;

并在您的 onConnected 方法中使用 locationEnabled 标志,如下所示:

    if (location != null) {
    Toast.makeText(getActivity(), "Location: " + location.getLatitude() + ", " + location.getLongitude(), Toast.LENGTH_SHORT).show();
}
else if (location == null && locationEnabled) {
    locationClient.requestLocationUpdates(locationRequest, this);
}

更新

文档已更新,LocationClient 已删除,api 支持通过对话框一键启用 GPS:

task.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
    // All location settings are satisfied. The client can initialize
    // location requests here.
    // ...
}
});

task.addOnFailureListener(this, new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        if (e instanceof ResolvableApiException) {
            // Location settings are not satisfied, but this can be fixed
            // by showing the user a dialog.
            try {
                // Show the dialog by calling startResolutionForResult(),
                // and check the result in onActivityResult().
                ResolvableApiException resolvable = (ResolvableApiException) e;
                resolvable.startResolutionForResult(MainActivity.this,
                        REQUEST_CHECK_SETTINGS);
            } catch (IntentSender.SendIntentException sendEx) {
                // Ignore the error.
            }
        }
    }
});

Link https://developer.android.com/training/location/change-location-settings#prompt

新位置客户端:FusedLocationProviderClient

  private FusedLocationProviderClient fusedLocationClient;

@Override
protected void onCreate(Bundle savedInstanceState) {
    fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
}

建议在执行任何定位任务之前先完成 https://developer.android.com/training/location

根据我的经验,"more appropriate accuracy" 并不意味着更好。除非我遗漏了什么,否则如果您想确保使用 GPS,LocationManager 是唯一的方法。我们使用我们的应用程序跟踪车辆,而且,除非我遗漏了什么,否则 Google Play 服务经常提供一些非常不准确的位置。

Google 位置服务 API,Google Play 服务的一部分,提供一个更强大的高级框架,可自动处理 位置提供程序 用户移动 位置准确性 .它还根据您提供的功耗参数处理位置更新调度。在大多数情况下,通过使用定位服务 API,您将获得 更好的电池性能,以及更合适的准确度。

两个 api 之间的更详细差异 Google 播放服务位置 APIAndroid 框架位置 API可以找到here

我使用 Google 定位服务 API 已经有一段时间了。它确实有优势,因为它封装了使用多个来源来确定位置的复杂性。但是封装太重,所以当你得到一个奇怪的位置时,你无法确定那个奇怪的位置是从哪里来的。

在现实生活中,我弹出了几个异常值,与实际位置相差 10 公里。唯一的解释是,这些疯狂的位置源于 Googles Wi-Fi 或 NWK 数据库中的错误——这些错误将永远存在,因为 Wi-Fi 和网络拓扑每天都在变化。但不幸的是(令人惊讶的是)API 没有提供有关个人职位是如何得出的信息。

这给您带来了必须根据速度、加速度、方位等的合理性检查过滤掉异常值的问题

... 或回到良好的旧框架 API 并仅使用 GPS,这是我决定做的,直到 Google 改进融合 API.

是的,Google Play Services Location Services API 可以提供极具误导性的位置信息。 WiFi 调制解调器移动,WiFi 调制解调器使用不正确的位置信息进行更新(即,如果位置被更新 WiFi 调制解调器位置的 Android 设备欺骗),还有许多其他情况可能导致来自不正确的位置数据WiFi调制解调器三角测量。在我们所有需要精确位置的应用程序中,我们仅使用 GPS。

两个 api 之间的差异 Google Play Service Location API 和 Android Framework Location API 基于 GPS 服务

FusedLocationProviderClient

  1. 对于第一次获取,位置不能为空(即:其他一些应用程序需要更新 GoogleplayService 数据库中的最后已知位置。如果为空,则需要变通)
  2. 对于下一个顺序提取,它使用requestLocationUpdates()方法来提取位置。
  3. 位置提取仅基于 locationRequest.setInterval(milliseconds)setFastestInterval(milliseconds),而不是基于 用户位置变化
  4. 返回的 LatLng 值包含仅 7 个小数值(例如:11.9557996、79.8234599),不够准确
  5. 推荐,当您的应用要求获取当前位置时距离可忽略不计(50 - 100 米精度)
  6. 对电池使用有效。

位置管理器Api

  1. 使用 locationManager.requestLocationUpdates()
  2. 调用用户位置提取
  3. 根据用户位置变化时间间隔获取位置locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, milliseconds, mindistance, Mylocationlistener)

  4. 返回的 LatLng 值包含 14 个十进制值 (例如:11.94574594963342 79.81166719458997) 准确的位置值

  5. 推荐用于基于位置的应用程序,当需要更高的精确度时甚至以米为单位。
  6. 电池使用情况基于抓取间隔和抓取距离。

Android 位置

位置管理器 - Context.getSystemService(Context.LOCATION_SERVICE)

  • ACCESS_COARSE_LOCATION - 提供不太准确的位置(城市街区)但速度更快并且不会耗尽电池电量

    • NETWORK_PROVIDER - 使用基站、wifi 接入点
    • PASSIVE_PROVIDER - 当系统中的其他人使用其他提供商时订阅位置更新
  • ACCESS_FINE_LOCATION - 提供更好更准确的位置(最多几米)。使用与 ACCESS_COARSE_LOCATION

    相同的提供商
    • GPS_PROVIDER - 使用卫星

Google API 定位服务 - GoogleApiClient 基于 Google Play 服务。高级 API 可以访问通过系统的位置事件。它具有更好的电池性能,但无法安装在某些设备上

  • Fused Location Provider - 根据您的需求和设备条件自动select 合适的提供商

通过 Google 提供的 AlertDialog 解决方案启用 GPS 时,添加到已接受的答案中。 ActivityResultContract的实现如下:

// Global Activity Scope

ActivityResultLauncher<IntentSenderRequest> gpsRequestLauncher = registerForActivityResult(
        new ActivityResultContracts.StartIntentSenderForResult(), callback -> {
        if(callback.getResultCode() == RESULT_CANCELED) {
            // Request Cancelled
        } else {
            //  GPS Enabled
        }
});

由于文档中的代码已过时并弃用 onActivityResults

task.addOnFailureListener(this, new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            if (e instanceof ResolvableApiException) {
                // Location settings are not satisfied, but this can be fixed
                // by showing the user a dialog.
                try {
                    // Show the dialog by calling startResolutionForResult(),
                    // and check the result in onActivityResult().
                    IntentSenderRequest request = new IntentSenderRequest.Builder(
                        e.resolution).build();
                    gpsRequestLauncher.launch(request);
                } catch (IntentSender.SendIntentException sendEx) {
                    // Ignore the error.
                }
            }
        }
    });