1.1 问题:
要在应用程序在使用设备的定位功能报告当前的物理位置。
1.2 解决方案
(API Level 9)
可利用Google在其Play Service 库中提供的混合位置提供程序。移动应用程序通常提供给用户的最强大优势之一就是能够包括基于用户目前所在位置的信息来添加上下文。应用程序可能要求位置服务基于如下标准提供设备位置的更新:
- 效率:在将另一个更新提供给应用程序之前经过的最小时间量,单位为毫秒。
- 距离:在提供另一个更新之前设备必须移动的最小距离。
- 计数:在关闭提供程序之前提供的更大更新数。
- 到期时间:启动请求之后和关闭提供程序之前的最大时间量。
注意:
Android也在LocationManager中直接提供了非Google位置服务。此API帮助不大,开发人员必须单独管理来自离散位置源的请求。
Android允许应用程序从多个来源获得位置数据。高精度(同时也是高能耗)的位置修正数据来自设备上的GPS,而低精度的数据是通过网络源获得的,如蜂窝网络基站塔和Wi-Fi热点。Google的混合位置提供程序因为如下原因而得名:将这些多个来源“混合”在一起,随时向开发人员提供最佳的结果。使用高级别的标准发出位置请求,由设备决定应该该调用哪些硬件接口:
- PRIORITY_BALANCED_POWER_ACCURACY:提供GPS和网络源的混合,以便通过较低的能耗获得较为精确的数据,并且更快速地进行修正。
- PRIORITY_HIGH_ACCURACY:虽然初步的修正可能来自其他地方,但要求最终的修正数据来自GPS(最精确)。这个选项也最为耗电,因为GPS通常一直保持打开。
- PRIORITY_LOW_POWER:
- PRIORITY_NO_POWER:使应用程序成为主动的观察者。仅在另一个应用程序触发时才提供更新。
要点:
本例中的位置服务是作为Google Play Serivices库的一部分进行分发的,它在任意平台级别都不是原生SDK的一部分。然而,目标平台为API Level 9或以后版本的应用程序以及Google Play体系内的设备都可以使用此绘图库。
1.3 实现机制
在下面代码清单中,注册一个Activity来监听对用户可见的位置更新,并且将位置信息显示在屏幕上。
注意:
本例使用来自AppCompat库的ActionBarActivity,从而可以使用API Level 11 之前的分段。
监控位置更新的Activity
public class MainActivity extends ActionBarActivity implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener,
com.google.android.gms.location.LocationListener {
private static final String TAG = "AndroidRecipes";
private static final int UPDATE_INTERVAL = 15 * 1000;
private static final int FASTEST_UPDATE_INTERVAL = 2 * 1000;
/* Play Services 的客户端接口*/
private LocationClient mLocationClient;
/* 我们想要接收的相关元数据*/
private LocationRequest mLocationRequest;
/* 最近已知的设备位置 */
private Location mCurrentLocation;
private TextView mLocationView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLocationView = new TextView(this);
setContentView(mLocationView);
//确认play services已激活并保持更新
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
switch (resultCode) {
case ConnectionResult.SUCCESS:
Log.d(TAG, "Google Play Services is ready to go!");
break;
default:
showPlayServicesError(resultCode);
return;
}
//添加位置更新监控
mLocationClient = new LocationClient(this, this, this);
mLocationRequest = LocationRequest.create()
//设置所需的精确级别
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
//设置所需的(不精确的) 的位置更新频率
.setInterval(UPDATE_INTERVAL)
//限制更新请求的最大速率
.setFastestInterval(FASTEST_UPDATE_INTERVAL);
}
@Override
public void onResume() {
super.onResume();
//在移入前台时,附加到 Play Services
mLocationClient.connect();
}
@Override
public void onPause() {
super.onPause();
//不在前台时禁止更新
if (mLocationClient.isConnected()) {
mLocationClient.removeLocationUpdates(this);
}
//从Play Services取消附加
mLocationClient.disconnect();
}
private void updateDisplay() {
if(mCurrentLocation == null) {
mLocationView.setText("Determining Your Location...");
} else {
mLocationView.setText(String.format("Your Location:\n%.2f, %.2f",
mCurrentLocation.getLatitude(),
mCurrentLocation.getLongitude()));
}
}
/** Play Services的位置 */
/*
* 当 Play Services 缺失或版本不准确时,
* 客户端将以对话框的形式帮助用户进行更新
*/
private void showPlayServicesError(int errorCode) {
// Get the error dialog from Google Play services
Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
errorCode,
this,
1000 /* RequestCode */);
//如果 Google Play services可以提供错误对话框
if (errorDialog != null) {
// 为错误对话框创建的DialogFragment
SupportErrorDialogFragment errorFragment = SupportErrorDialogFragment.newInstance(errorDialog);
// 在 DialogFragment中显示错误对话框
errorFragment.show(
getSupportFragmentManager(),
"Location Updates");
}
}
@Override
public void onConnected(Bundle bundle) {
Log.d(TAG, "Connected to Play Services");
//立即获得最近已知的位置
mCurrentLocation = mLocationClient.getLastLocation();
//注册更新
mLocationClient.requestLocationUpdates(mLocationRequest, this);
}
@Override
public void onDisconnected() { }
@Override
public void onConnectionFailed(ConnectionResult connectionResult) { }
/** LocationListener 回调 */
@Override
public void onLocationChanged(Location location) {
Log.d(TAG, "Received location update");
mCurrentLocation = location;
updateDisplay();
}
}
要点:
在应用程序中使用位置服务时,记住需要在应用程序的清单中声明android.permission.ACCESS_COARSE_LOCATION或android.permission.ACCESS_FINE_LOCATION权限。如果声明了android.permission.ACCESS_FINE_LOCATION权限,就不必声明另一个权限,因为它已经包含了模糊位置服务的权限。
这个示例构造一个LocationRequest,其中包含希望应用于所接收更新的所有标准。我们指示该服务以15秒的间隔仅返回高度精确的结果。这种间隔是不准确的,即Android大约每隔15秒传递更新,这个时间可多可少。唯一的保证是,在此间隔内最多只会收到一个更新。未设置此间隔的上限,我们还设置最快间隔为两秒。这会限制更新,确保我们不会在快于两秒的间隔内收到两个更新。
收集位置数据是一个资源密集型操作,因此要确保仅在Activity位于前台时才执行此操作。我们的示例会等待直到onResume()连接位置服务,并在onPause()中立即断开连接。Google Play Services是异步的,这意味着必须连接并等待回调,然后才可以执行真正的设置工作。直到建立指向Play Services的连接之后,才可以访问LocationClient。
如果LocationClient.connect()在后面尝试触发onConnected(),我们就可以使用前面通过requestLocationUpdates()构造的请求来开始请求更新。如果我们需要在获得另一个更新之前引用数据,则此时也可以通过getLastLocation()获得最近已知的位置。如果最近没有进行修正,则该值可以返回null。
模拟位置改变
如果使用Android模拟器测试应用程序的话,应用程序将不能从任何系统提供程序中接收真实的位置数据信息。但是,通过SDK的Monitor工具,可以手动地注入位置改变事件。
在激活DDMS的情况下,选择Emulator Control选项卡并查找Location Controls部分。在选项卡式的界面中可以直接输入经度/纬度对,或者通过命令行文件格式读取一系列经度/纬度对。
在手动输入单个值时,必须在文本框中输入有效的经度和纬度。然后可以单击Send按钮,作为所选模拟器的事件注入该位置。注册以侦听位置改变的任何应用程序也会收到包含此位置值的更新。
当位置更新到达时,会调用已注册侦听器的onLocationChanged()方法。该例持续引用其所接收的最新位置,对于每个传入的更新,重置位置值,并且更新用户界面显示以反映新的更改。
注意:
如果在服务或其他后台操作中接收位置更新,Google建议最小时间间隔应不小于60 000 (60秒)。
为使此应用程序正在构建和运行,我们需要额外关联一些文件。以下两段清单代码描述了这些需求。
build.gradle的部分代码
apply plugin: 'com.android.application'
android {
...
}
...
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.android.gms:play-services:6.1.+'
compile 'com.android.support:appcompat-v7:21.0.+'
}
AndroidManifest.xml的部分代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.examples.mylocation">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application ...>
<!-- 需要样板文件以启动 Play Services -->
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
在项目build.gradle文件内,必须将Google Play Services添加为依赖项。在此我们使用加号来确保始终通过最新的客户端库进行构建。Play Services也要要求向具有客户端版本的应用程序添加元素,客户端使用此元素确定设备上的Play Services版本目前是满足要求。版本号资源内置于库中,因此无须进行定义。
我们正在访问设备的位置服务,因此还需要请求ACCESS_FINE_LOCATION权限,这是访问GPS位置服务的必需权限。如果应用程序使用较低精确度的值,就可以不需要请求ACCESS_FINE_LOCATION权限。
Google Play Services不可用时的情况
在此例的顶部(见代码清单:监控位置更新的Activity)有一些样板代码,用于使用GooglePlayServicesUtil的isGooglePlayServicesAvailable()检测设备上的Google Play Services 正在运行且保持更新。该方法将确认Play Services正在设备上运行,并且最少为客户端应用程序中指定的版本。
如果此检查失败,Play Services也以ErrorDialogFragment的形式提供了UI,向用户显示此UI以更新 Play Services。此对话框将触发适当的设置UI,从而自动将 Play Services更新到最新版本。在我们的示例中,showPlayServicesError()方法负责处理此工作。