Android 上的用户位置
在 Android 上获取用户的位置比在 iOS 上要简单一些。要开始混淆,有两种完全不同的方法可以做到这一点。第一个是使用来自android.location.LocationListener 的Android API,第二个是使用Google Play Services API com.google.android.gms.location.LocationListener。让我们来看看它们。
-
Android 的位置 API
Android 的位置 API 使用三个不同的提供程序来获取位置 -
-
LocationManager.GPS_PROVIDER — 该提供商使用卫星确定位置。根据具体情况,此提供程序可能需要一段时间才能返回定位信息。
-
LocationManager.NETWORK_PROVIDER — 该提供商根据蜂窝塔和 WiFi 接入点的可用性确定位置。通过网络查找来检索结果。
-
LocationManager.PASSIVE_PROVIDER — 此提供程序将返回其他提供程序生成的位置。当其他应用程序或服务请求位置更新时,您会被动地接收位置更新,而您自己并没有实际请求位置。
其要点是从系统中获取LocationManager的对象,实现LocationListener,并在LocationManager上调用requestLocationUpdates。
这是一个代码 sn-p:
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 很好地解释了代码。但他们也提到,在大多数情况下,通过使用Google Location Services API,您将获得更好的电池性能以及更合适的精度。现在混乱开始了!
-
Google 的位置服务 API
Google 的位置服务 API 是 Google Play 服务 APK (here’s how to set it up) 的一部分。它们建立在 Android 的 API 之上。这些 API 提供了一个“融合位置提供者”,而不是上面提到的提供者。该提供程序会根据准确性、电池使用情况等自动选择要使用的底层提供程序。速度很快,因为您可以从不断更新它的系统范围的服务中获取位置。您还可以使用更高级的功能,例如地理围栏。
要使用 Google 的定位服务,您的应用需要连接到 GooglePlayServicesClient。要连接到客户端,您的活动(或片段等)需要实现GooglePlayServicesClient.ConnectionCallbacks 和GooglePlayServicesClient.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 仅在至少有一个客户端连接到它时才会维护后台位置。一旦第一个客户端连接,它将立即尝试获取位置。如果您的活动是第一个连接的客户端,并且您立即在onConnected() 中调用getLastLocation(),则可能没有足够的时间让第一个位置进入。这将导致location 成为null。
要解决此问题,您必须(不确定地)等待,直到提供者获取位置,然后调用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 Play 服务客户端的情况下请求位置。
很多时候,用户会禁用位置服务(为了节省电池或隐私原因)。在这种情况下,上面的代码仍然会请求位置更新,但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.
}
}
}
});
链接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。