> 编写:[penkzhou](https://github.com/penkzhou) - 原文:[http://developer.android.com/training/location/display-address.html](http://developer.android.com/training/location/display-address.html)
[获取最后可知位置](#)和[获取位置更新](#)课程描述了如何以一个[Location](http://developer.android.com/reference/android/location/Location.html)对象的形式获取用户的位置信息,这个位置信息包括了经纬度。尽管经纬度对计算地理距离和在地图上显示位置很有用,但是更多情况下位置的地址更有用。例如,如果我们想让用户知道他们在哪里,那么一个街道地址比地理坐标(经度/纬度)更加有意义。
使用 Android 框架位置 APIs 的 [Geocoder](http://developer.android.com/reference/android/location/Geocoder.html) 类,我们可以将地址转换成相应的地理坐标。这个过程叫做_地理编码_。或者,我们可以将地理位置转换成相应的地址。这种地址查找功能叫做_反向地理编码_。
这节课介绍了如何用 [getFromLocation()](http://developer.android.com/reference/android/location/Geocoder.html#getFromLocation(double, double, int)) 方法将地理位置转换成地址。这个方法返回与制定经纬度相对应的估计的街道地址。
### 获取地理位置
设备的最后可知位置对于地址查找功能是很有用的基础。[获取最后可知位置](#)介绍了如何通过调用 [fused location provider](http://developer.android.com/reference/com/google/android/gms/location/FusedLocationProviderApi.html) 提供的 [getLastLocation()](http://developer.android.com/reference/com/google/android/gms/location/FusedLocationProviderApi.html#getLastLocation(com.google.android.gms.common.api.GoogleApiClient)) 方法找到设备的最后可知位置。
为了访问 fused location provider,我们需要创建一个 Google Play services API client 的实例。关于如何连接 client,请见[连接 Google Play Services](#) 。
为了让 fused location provider 得到一个准确的街道地址,在应用的 manifest 文件添加位置权限 `ACCESS_FINE_LOCATION`,如下所示:
~~~
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.gms.location.sample.locationupdates" >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
</manifest>
~~~
### 定义一个 Intent 服务来取得地址
[Geocoder](http://developer.android.com/reference/android/location/Geocoder.html) 类的 [getFromLocation()](http://developer.android.com/reference/android/location/Geocoder.html#getFromLocation(double, double, int)) 方法接收一个经度和纬度,返回一个地址列表。这个方法是同步的,可能会花很长时间来完成它的工作,所以我们不应该在应用的主线程和 UI 线程里调用这个方法。
[IntentService](http://developer.android.com/reference/android/app/IntentService.html) 类提供了一种结构使一个任务在后台线程运行。使用这个类,我们可以在不影响 UI 响应速度的情况下处理一个长时间运行的操作。注意到,[AsyncTask](http://developer.android.com/reference/android/os/AsyncTask.html) 类也可以执行后台操作,但是它被设计用于短时间运行的操作。在 [activity](# "An activity represents a single screen with a user interface.") 重新创建时(例如当设备旋转时),[AsyncTask](http://developer.android.com/reference/android/os/AsyncTask.html) 不应该保存 UI 的引用。相反,当 [activity](# "An activity represents a single screen with a user interface.") 重建时,不需要取消 [IntentService](http://developer.android.com/reference/android/app/IntentService.html)。
定义一个继承 [IntentService](http://developer.android.com/reference/android/app/IntentService.html) 的类 `FetchAddressIntentService`。这个类是地址查找服务。这个 Intent 服务在一个工作线程上异步地处理一个 intent,并在它离开这个工作时自动停止。Intent 外加的数据提供了服务需要的数据,包括一个用于转换成地址的 [Location](http://developer.android.com/reference/android/location/Location.html) 对象和一个用于处理地址查找结果的 [ResultReceiver](http://developer.android.com/reference/android/os/ResultReceiver.html) 对象。这个服务用一个 [Geocoder](http://developer.android.com/reference/android/location/Geocoder.html) 来获取位置的地址,并且将结果发送给 [ResultReceiver](http://developer.android.com/reference/android/os/ResultReceiver.html)。
### 在应用的 manifest 文件中定义 Intent 服务
在 manifest 文件中添加一个节点以定义 intent 服务:
~~~
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.gms.location.sample.locationaddress" >
<application
...
<service
android:name=".FetchAddressIntentService"
android:exported="false"/>
</application>
...
</manifest>
~~~
> **Note:**manifest 文件里的 `<service>` 节点不需要包含一个 intent filter,这是因为我们的主 [activity](# "An activity represents a single screen with a user interface.") 通过指定 intent 用到的类的名字来创建一个隐式的 intent。
### 创建一个 Geocoder
将一个地理位置传换成地址的过程叫做_反向地理编码_。通过实现 `FetchAddressIntentService` 类的 [onHandleIntent()](http://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent)) 来执行 intent 服务的主要工作,即反向地理编码请求。创建一个 [Geocoder](http://developer.android.com/reference/android/location/Geocoder.html) 对象来处理反向地理编码。
一个区域设置代表一个特定的地理上的或者语言上的区域。Locale 对象用于调整信息的呈现方式,例如数字或者日期,来适应区域设置表示的区域的约定。传一个 [Locale](http://developer.android.com/reference/java/util/Locale.html) 对象到 [Geocoder](http://developer.android.com/reference/android/location/Geocoder.html) 对象,确保地址结果为用户的地理区域作出了本地化。
~~~
@Override
protected void onHandleIntent(Intent intent) {
Geocoder geocoder = new Geocoder(this, Locale.getDefault());
...
}
~~~
### 获取街道地址数据
下一步是从 geocoder 获取街道地址,处理可能出现的错误,和将结果返回给请求地址的 [activity](# "An activity represents a single screen with a user interface.")。我们需要两个分别代表成功和失败的数字常量来报告地理编码过程的结果。定义一个 `Constants` 类来包含这些值,如下所示:
~~~
public final class Constants {
public static final int SUCCESS_RESULT = 0;
public static final int FAILURE_RESULT = 1;
public static final String PACKAGE_NAME =
"com.google.android.gms.location.sample.locationaddress";
public static final String RECEIVER = PACKAGE_NAME + ".RECEIVER";
public static final String RESULT_DATA_KEY = PACKAGE_NAME +
".RESULT_DATA_KEY";
public static final String LOCATION_DATA_EXTRA = PACKAGE_NAME +
".LOCATION_DATA_EXTRA";
}
~~~
为了获取与地理位置相对应的街道地址,调用 [getFromLocation()](http://developer.android.com/reference/android/location/Geocoder.html#getFromLocation(double, double, int)),传入位置对象的经度和纬度,以及我们想要返回的地址的最大数量。在这种情况下,我们只需要一个地址。geocoder 返回一个地址数组。如果没有找到匹配指定位置的地址,那么它会返回空的列表。如果没有可用的后台地理编码服务,geocoder 会返回 null。
如下面代码介绍来检查下述这些错误。如果出现错误,就将相应的错误信息传给变量 `errorMessage`,从而将错误信息发送给发出请求的 [activity](# "An activity represents a single screen with a user interface."):
- **No location data provided** - Intent 的附加数据没有包含反向地理编码需要用到的 [Location](http://developer.android.com/reference/android/location/Location.html) 对象。
- **Invalid latitude or longitude used** - [Location](http://developer.android.com/reference/android/location/Location.html) 对象提供的纬度和/或者经度无效。
- **No geocoder available** - 由于网络错误或者 IO 异常,导致后台地理编码服务不可用。
- **Sorry, no address found** - geocoder 找不到指定纬度/经度对应的地址。
使用 [Address](http://developer.android.com/reference/android/location/Address.html) 类中的 [getAddressLine()](http://developer.android.com/reference/android/location/Address.html#getAddressLine(int)) 方法来获得地址对象的个别行。然后将这些行加入一个地址 fragment 列表当中。其中,这个地址 fragment 列表准备好返回到发出地址请求的 [activity](# "An activity represents a single screen with a user interface.")。
为了将结果返回给发出地址请求的 [activity](# "An activity represents a single screen with a user interface."),需要调用 `deliverResultToReceiver()` 方法(定义于下面的[把地址返回给请求端]())。结果由之前提到的成功/失败数字代码和一个字符串组成。在反向地理编码成功的情况下,这个字符串包含着地址。在失败的情况下,这个字符串包含错误的信息。如下所示:
~~~
@Override
protected void onHandleIntent(Intent intent) {
String errorMessage = "";
// Get the location passed to this service through an extra.
Location location = intent.getParcelableExtra(
Constants.LOCATION_DATA_EXTRA);
...
List<Address> addresses = null;
try {
addresses = geocoder.getFromLocation(
location.getLatitude(),
location.getLongitude(),
// In this sample, get just a single address.
1);
} catch (IOException ioException) {
// Catch network or other I/O problems.
errorMessage = getString(R.string.service_not_available);
Log.e(TAG, errorMessage, ioException);
} catch (IllegalArgumentException illegalArgumentException) {
// Catch invalid latitude or longitude values.
errorMessage = getString(R.string.invalid_lat_long_used);
Log.e(TAG, errorMessage + ". " +
"Latitude = " + location.getLatitude() +
", Longitude = " +
location.getLongitude(), illegalArgumentException);
}
// Handle case where no address was found.
if (addresses == null || addresses.size() == 0) {
if (errorMessage.isEmpty()) {
errorMessage = getString(R.string.no_address_found);
Log.e(TAG, errorMessage);
}
deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage);
} else {
Address address = addresses.get(0);
ArrayList<String> addressFragments = new ArrayList<String>();
// Fetch the address lines using getAddressLine,
// join them, and send them to the thread.
for(int i = 0; i < address.getMaxAddressLineIndex(); i++) {
addressFragments.add(address.getAddressLine(i));
}
Log.i(TAG, getString(R.string.address_found));
deliverResultToReceiver(Constants.SUCCESS_RESULT,
TextUtils.join(System.getProperty("line.separator"),
addressFragments));
}
}
~~~
### 把地址返回给请求端
Intent 服务最后要做的事情是将地址返回给启动服务的 [activity](# "An activity represents a single screen with a user interface.") 里的 [ResultReceiver](http://developer.android.com/reference/android/os/ResultReceiver.html)。这个 [ResultReceiver](http://developer.android.com/reference/android/os/ResultReceiver.html) 类允许我们发送一个带有结果的数字代码和一个包含结果数据的消息。这个数字代码说明了地理编码请求是成功还是失败。在反向地理编码成功的情况下,这个消息包含着地址。在失败的情况下,这个消息包含一些描述失败原因的文本。
我们已经可以从 geocoder 取得地址,捕获到可能出现的错误,调用 `deliverResultToReceiver()` 方法。现在我们需要定义 `deliverResultToReceiver()` 方法来将结果代码和消息包发送给结果接收端。
对于结果代码,使用已经传给 `deliverResultToReceiver()` 方法的 `resultCode` 参数的值。对于消息包的结构,连接 `Constants` 类的 `RESULT_DATA_KEY` 常量(定义与[获取街道地址数据]())和传给 `deliverResultToReceiver()` 方法的 `message` 参数的值。如下所示:
~~~
public class FetchAddressIntentService extends IntentService {
protected ResultReceiver mReceiver;
...
private void deliverResultToReceiver(int resultCode, String message) {
Bundle bundle = new Bundle();
bundle.putString(Constants.RESULT_DATA_KEY, message);
mReceiver.send(resultCode, bundle);
}
}
~~~
### 启动 Intent 服务
上节课定义的 intent 服务在后台运行,同时,该服务负责提取与指定地理位置相对应的地址。当我们启动服务,Android 框架会实例化并启动服务(如果该服务没有运行),并且如果需要的话,创建一个进程。如果服务正在运行,那么让它保持运行状态。因为服务继承于 [IntentService](http://developer.android.com/reference/android/app/IntentService.html),所以当所有 intent 都被处理完之后,该服务会自动停止。
在我们应用的主 [activity](# "An activity represents a single screen with a user interface.") 中启动服务,并且创建一个 [Intent](http://developer.android.com/reference/android/content/Intent.html) 来把数据传给服务。我们需要创建一个_显式的_ intent,这是因为我们只想我们的服务响应该 intent。详细请见 [Intent Types](http://developer.android.com/guide/components/intents-filters.html#Types)。
为了创建一个显式的 intent,需要为服务指定要用到的类名:`FetchAddressIntentService.class`。在 intent 附加数据中传入两个信息:
- 一个用于处理地址查找结果的 [ResultReceiver](http://developer.android.com/reference/android/os/ResultReceiver.html)。
- 一个包含想要转换成地址的纬度和经度的 [Location](http://developer.android.com/reference/android/location/Location.html) 对象。
下面的代码介绍了如何启动 intent 服务:
~~~
public class MainActivity extends ActionBarActivity implements
ConnectionCallbacks, OnConnectionFailedListener {
protected Location mLastLocation;
private AddressResultReceiver mResultReceiver;
...
protected void startIntentService() {
Intent intent = new Intent(this, FetchAddressIntentService.class);
intent.putExtra(Constants.RECEIVER, mResultReceiver);
intent.putExtra(Constants.LOCATION_DATA_EXTRA, mLastLocation);
startService(intent);
}
}
~~~
当用户请求查找地理地址时,调用上述的 `startIntentService()` 方法。例如,用户可能会在我们应用的 UI 上面点击_提取地址_按钮。在启动 intent 服务之前,我们需要检查是否已经连接到 Google Play services。下面的代码片段介绍在一个按钮 handler 中调用 `startIntentService()` 方法。
~~~
public void fetchAddressButtonHandler(View view) {
// Only start the service to fetch the address if GoogleApiClient is
// connected.
if (mGoogleApiClient.isConnected() && mLastLocation != null) {
startIntentService();
}
// If GoogleApiClient isn't connected, process the user's request by
// setting mAddressRequested to true. Later, when GoogleApiClient connects,
// launch the service to fetch the address. As far as the user is
// concerned, pressing the Fetch Address button
// immediately kicks off the process of getting the address.
mAddressRequested = true;
updateUIWidgets();
}
~~~
如果用户点击了应用 UI 上面的_提取地址_按钮,那么我们必须在 Google Play services 连接稳定之后启动 intent 服务。下面的代码片段介绍了调用 Google API Client 提供的 [onConnected()](http://developer.android.com/reference/com/google/android/gms/common/api/GoogleApiClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)) 回调函数中的 `startIntentService()` 方法。
~~~
public class MainActivity extends ActionBarActivity implements
ConnectionCallbacks, OnConnectionFailedListener {
...
@Override
public void onConnected(Bundle connectionHint) {
// Gets the best and most recent location currently available,
// which may be null in rare cases when a location is not available.
mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
mGoogleApiClient);
if (mLastLocation != null) {
// Determine whether a Geocoder is available.
if (!Geocoder.isPresent()) {
Toast.makeText(this, R.string.no_geocoder_available,
Toast.LENGTH_LONG).show();
return;
}
if (mAddressRequested) {
startIntentService();
}
}
}
}
~~~
### 获取地理编码结果
Intent 服务已经处理完地理编码请求,并用 [ResultReceiver](http://developer.android.com/reference/android/os/ResultReceiver.html) 将结果返回给发出请求的 [activity](# "An activity represents a single screen with a user interface.")。在发出请求的 [activity](# "An activity represents a single screen with a user interface.") 里,定义一个继承于 [ResultReceiver](http://developer.android.com/reference/android/os/ResultReceiver.html) 的 `AddressResultReceiver`,用于处理在 `FetchAddressIntentService` 中的响应。
结果包含一个数字代码(`resultCode`)和一个包含结果数据(`resultData`)的消息。如果反向地理编码成功的话,`resultData` 会包含地址。如果失败,`resultData` 包含描述失败原因的文本。关于错误信息更详细的内容,请见[把地址返回给请求端]()
重写 [onReceiveResult()](http://developer.android.com/reference/android/os/ResultReceiver.html#onReceiveResult(int, android.os.Bundle)) 方法来处理发送给接收端的结果,如下所示:
~~~
public class MainActivity extends ActionBarActivity implements
ConnectionCallbacks, OnConnectionFailedListener {
...
class AddressResultReceiver extends ResultReceiver {
public AddressResultReceiver(Handler handler) {
super(handler);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
// Display the address string
// or an error message sent from the intent service.
mAddressOutput = resultData.getString(Constants.RESULT_DATA_KEY);
displayAddressOutput();
// Show a toast message if an address was found.
if (resultCode == Constants.SUCCESS_RESULT) {
showToast(getString(R.string.address_found));
}
}
}
}
~~~
- 序言
- Android入门基础:从这里开始
- 建立第一个App
- 创建Android项目
- 执行Android程序
- 建立简单的用户界面
- 启动其他的Activity
- 添加ActionBar
- 建立ActionBar
- 添加Action按钮
- 自定义ActionBar的风格
- ActionBar的覆盖层叠
- 兼容不同的设备
- 适配不同的语言
- 适配不同的屏幕
- 适配不同的系统版本
- 管理Activity的生命周期
- 启动与销毁Activity
- 暂停与恢复Activity
- 停止与重启Activity
- 重新创建Activity
- 使用Fragment建立动态的UI
- 创建一个Fragment
- 建立灵活动态的UI
- Fragments之间的交互
- 数据保存
- 保存到Preference
- 保存到文件
- 保存到数据库
- 与其他应用的交互
- Intent的发送
- 接收Activity返回的结果
- Intent过滤
- Android分享操作
- 分享简单的数据
- 给其他App发送简单的数据
- 接收从其他App返回的数据
- 给ActionBar增加分享功能
- 分享文件
- 建立文件分享
- 分享文件
- 请求分享一个文件
- 获取文件信息
- 使用NFC分享文件
- 发送文件给其他设备
- 接收其他设备的文件
- Android多媒体
- 管理音频播放
- 控制音量与音频播放
- 管理音频焦点
- 兼容音频输出设备
- 拍照
- 简单的拍照
- 简单的录像
- 控制相机硬件
- 打印
- 打印照片
- 打印HTML文档
- 打印自定义文档
- Android图像与动画
- 高效显示Bitmap
- 高效加载大图
- 非UI线程处理Bitmap
- 缓存Bitmap
- 管理Bitmap的内存
- 在UI上显示Bitmap
- 使用OpenGL ES显示图像
- 建立OpenGL ES的环境
- 定义Shapes
- 绘制Shapes
- 运用投影与相机视图
- 添加移动
- 响应触摸事件
- 添加动画
- View间渐变
- 使用ViewPager实现屏幕侧滑
- 展示卡片翻转动画
- 缩放View
- 布局变更动画
- Android网络连接与云服务
- 无线连接设备
- 使得网络服务可发现
- 使用WiFi建立P2P连接
- 使用WiFi P2P服务
- 执行网络操作
- 连接到网络
- 管理网络
- 解析XML数据
- 高效下载
- 为网络访问更加高效而优化下载
- 最小化更新操作的影响
- 避免下载多余的数据
- 根据网络类型改变下载模式
- 云同步
- 使用备份API
- 使用Google Cloud Messaging
- 解决云同步的保存冲突
- 使用Sync Adapter传输数据
- 创建Stub授权器
- 创建Stub Content Provider
- 创建Sync Adpater
- 执行Sync Adpater
- 使用Volley执行网络数据传输
- 发送简单的网络请求
- 建立请求队列
- 创建标准的网络请求
- 实现自定义的网络请求
- Android联系人与位置信息
- Android联系人信息
- 获取联系人列表
- 获取联系人详情
- 使用Intents修改联系人信息
- 显示联系人头像
- Android位置信息
- 获取最后可知位置
- 获取位置更新
- 显示位置地址
- 创建和监视地理围栏
- Android可穿戴应用
- 赋予Notification可穿戴特性
- 创建Notification
- 在Notifcation中接收语音输入
- 为Notification添加显示页面
- 以Stack的方式显示Notifications
- 创建可穿戴的应用
- 创建并运行可穿戴应用
- 创建自定义的布局
- 添加语音功能
- 打包可穿戴应用
- 通过蓝牙进行调试
- 创建自定义的UI
- 定义Layouts
- 创建Cards
- 创建Lists
- 创建2D-Picker
- 创建确认界面
- 退出全屏的Activity
- 发送并同步数据
- 访问可穿戴数据层
- 同步数据单元
- 传输资源
- 发送与接收消息
- 处理数据层的事件
- Android TV应用
- 创建TV应用
- 创建TV应用的第一步
- 处理TV硬件部分
- 创建TV的布局文件
- 创建TV的导航栏
- 创建TV播放应用
- 创建目录浏览器
- 提供一个Card视图
- 创建详情页
- 显示正在播放卡片
- 帮助用户在TV上探索内容
- TV上的推荐内容
- 使得TV App能够被搜索
- 使用TV应用进行搜索
- 创建TV游戏应用
- 创建TV直播应用
- TV Apps Checklist
- Android企业级应用
- Ensuring Compatibility with Managed Profiles
- Implementing App Restrictions
- Building a Work Policy Controller
- Android交互设计
- 设计高效的导航
- 规划屏幕界面与他们之间的关系
- 为多种大小的屏幕进行规划
- 提供向下和横向导航
- 提供向上和历史导航
- 综合:设计样例 App
- 实现高效的导航
- 使用Tabs创建Swipe视图
- 创建抽屉导航
- 提供向上的导航
- 提供向后的导航
- 实现向下的导航
- 通知提示用户
- 建立Notification
- 当启动Activity时保留导航
- 更新Notification
- 使用BigView风格
- 显示Notification进度
- 增加搜索功能
- 建立搜索界面
- 保存并搜索数据
- 保持向下兼容
- 使得你的App内容可被Google搜索
- 为App内容开启深度链接
- 为索引指定App内容
- Android界面设计
- 为多屏幕设计
- 兼容不同的屏幕大小
- 兼容不同的屏幕密度
- 实现可适应的UI
- 创建自定义View
- 创建自定义的View类
- 实现自定义View的绘制
- 使得View可交互
- 优化自定义View
- 创建向后兼容的UI
- 抽象新的APIs
- 代理至新的APIs
- 使用旧的APIs实现新API的效果
- 使用版本敏感的组件
- 实现辅助功能
- 开发辅助程序
- 开发辅助服务
- 管理系统UI
- 淡化系统Bar
- 隐藏系统Bar
- 隐藏导航Bar
- 全屏沉浸式应用
- 响应UI可见性的变化
- 创建使用Material Design的应用
- 开始使用Material Design
- 使用Material的主题
- 创建Lists与Cards
- 定义Shadows与Clipping视图
- 使用Drawables
- 自定义动画
- 维护兼容性
- Android用户输入
- 使用触摸手势
- 检测常用的手势
- 跟踪手势移动
- Scroll手势动画
- 处理多触摸手势
- 拖拽与缩放
- 管理ViewGroup中的触摸事件
- 处理键盘输入
- 指定输入法类型
- 处理输入法可见性
- 兼容键盘导航
- 处理按键动作
- 兼容游戏控制器
- 处理控制器输入动作
- 支持不同的Android系统版本
- 支持多个控制器
- Android后台任务
- 在IntentService中执行后台任务
- 创建IntentService
- 发送工作任务到IntentService
- 报告后台任务执行状态
- 使用CursorLoader在后台加载数据
- 使用CursorLoader执行查询任务
- 处理查询的结果
- 管理设备的唤醒状态
- 保持设备的唤醒
- 制定重复定时的任务
- Android性能优化
- 管理应用的内存
- 代码性能优化建议
- 提升Layout的性能
- 优化layout的层级
- 使用include标签重用layouts
- 按需加载视图
- 使得ListView滑动顺畅
- 优化电池寿命
- 监测电量与充电状态
- 判断与监测Docking状态
- 判断与监测网络连接状态
- 根据需要操作Broadcast接受者
- 多线程操作
- 在一个线程中执行一段特定的代码
- 为多线程创建线程池
- 启动与停止线程池中的线程
- 与UI线程通信
- 避免出现程序无响应ANR
- JNI使用指南
- 优化多核处理器(SMP)下的Android程序
- Android安全与隐私
- Security Tips
- 使用HTTPS与SSL
- 为防止SSL漏洞而更新Security
- 使用设备管理条例增强安全性
- Android测试程序
- 测试你的Activity
- 建立测试环境
- 创建与执行测试用例
- 测试UI组件
- 创建单元测试
- 创建功能测试
- 術語表