編寫:penkzhou - 原文:http://developer.android.com/training/location/display-address.html
獲取最后可知位置和獲取位置更新課程描述了如何以一個(gè)Location對(duì)象的形式獲取用戶的位置信息,這個(gè)位置信息包括了經(jīng)緯度。盡管經(jīng)緯度對(duì)計(jì)算地理距離和在地圖上顯示位置很有用,但是更多情況下位置的地址更有用。例如,如果我們想讓用戶知道他們?cè)谀睦?,那么一個(gè)街道地址比地理坐標(biāo)(經(jīng)度/緯度)更加有意義。
使用 Android 框架位置 APIs 的 Geocoder 類,我們可以將地址轉(zhuǎn)換成相應(yīng)的地理坐標(biāo)。這個(gè)過(guò)程叫做地理編碼?;蛘?,我們可以將地理位置轉(zhuǎn)換成相應(yīng)的地址。這種地址查找功能叫做反向地理編碼。
這節(jié)課介紹了如何用 getFromLocation() 方法將地理位置轉(zhuǎn)換成地址。這個(gè)方法返回與制定經(jīng)緯度相對(duì)應(yīng)的估計(jì)的街道地址。
設(shè)備的最后可知位置對(duì)于地址查找功能是很有用的基礎(chǔ)。獲取最后可知位置介紹了如何通過(guò)調(diào)用 fused location provider 提供的 getLastLocation() 方法找到設(shè)備的最后可知位置。
為了訪問(wèn) fused location provider,我們需要?jiǎng)?chuàng)建一個(gè) Google Play services API client 的實(shí)例。關(guān)于如何連接 client,請(qǐng)見(jiàn)連接 Google Play Services 。
為了讓 fused location provider 得到一個(gè)準(zhǔn)確的街道地址,在應(yīng)用的 manifest 文件添加位置權(quán)限 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>
Geocoder 類的 getFromLocation() 方法接收一個(gè)經(jīng)度和緯度,返回一個(gè)地址列表。這個(gè)方法是同步的,可能會(huì)花很長(zhǎng)時(shí)間來(lái)完成它的工作,所以我們不應(yīng)該在應(yīng)用的主線程和 UI 線程里調(diào)用這個(gè)方法。
IntentService 類提供了一種結(jié)構(gòu)使一個(gè)任務(wù)在后臺(tái)線程運(yùn)行。使用這個(gè)類,我們可以在不影響 UI 響應(yīng)速度的情況下處理一個(gè)長(zhǎng)時(shí)間運(yùn)行的操作。注意到,AsyncTask 類也可以執(zhí)行后臺(tái)操作,但是它被設(shè)計(jì)用于短時(shí)間運(yùn)行的操作。在 activity 重新創(chuàng)建時(shí)(例如當(dāng)設(shè)備旋轉(zhuǎn)時(shí)),AsyncTask 不應(yīng)該保存 UI 的引用。相反,當(dāng) activity 重建時(shí),不需要取消 IntentService。
定義一個(gè)繼承 IntentService 的類 FetchAddressIntentService。這個(gè)類是地址查找服務(wù)。這個(gè) Intent 服務(wù)在一個(gè)工作線程上異步地處理一個(gè) intent,并在它離開這個(gè)工作時(shí)自動(dòng)停止。Intent 外加的數(shù)據(jù)提供了服務(wù)需要的數(shù)據(jù),包括一個(gè)用于轉(zhuǎn)換成地址的 Location 對(duì)象和一個(gè)用于處理地址查找結(jié)果的 ResultReceiver 對(duì)象。這個(gè)服務(wù)用一個(gè) Geocoder 來(lái)獲取位置的地址,并且將結(jié)果發(fā)送給 ResultReceiver。
在 manifest 文件中添加一個(gè)節(jié)點(diǎn)以定義 intent 服務(wù):
<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>節(jié)點(diǎn)不需要包含一個(gè) intent filter,這是因?yàn)槲覀兊闹?activity 通過(guò)指定 intent 用到的類的名字來(lái)創(chuàng)建一個(gè)隱式的 intent。
將一個(gè)地理位置傳換成地址的過(guò)程叫做反向地理編碼。通過(guò)實(shí)現(xiàn) FetchAddressIntentService 類的 onHandleIntent() 來(lái)執(zhí)行 intent 服務(wù)的主要工作,即反向地理編碼請(qǐng)求。創(chuàng)建一個(gè) Geocoder 對(duì)象來(lái)處理反向地理編碼。
一個(gè)區(qū)域設(shè)置代表一個(gè)特定的地理上的或者語(yǔ)言上的區(qū)域。Locale 對(duì)象用于調(diào)整信息的呈現(xiàn)方式,例如數(shù)字或者日期,來(lái)適應(yīng)區(qū)域設(shè)置表示的區(qū)域的約定。傳一個(gè) Locale 對(duì)象到 Geocoder 對(duì)象,確保地址結(jié)果為用戶的地理區(qū)域作出了本地化。
@Override
protected void onHandleIntent(Intent intent) {
Geocoder geocoder = new Geocoder(this, Locale.getDefault());
...
}
下一步是從 geocoder 獲取街道地址,處理可能出現(xiàn)的錯(cuò)誤,和將結(jié)果返回給請(qǐng)求地址的 activity。我們需要兩個(gè)分別代表成功和失敗的數(shù)字常量來(lái)報(bào)告地理編碼過(guò)程的結(jié)果。定義一個(gè) Constants 類來(lái)包含這些值,如下所示:
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";
}
為了獲取與地理位置相對(duì)應(yīng)的街道地址,調(diào)用 getFromLocation(),傳入位置對(duì)象的經(jīng)度和緯度,以及我們想要返回的地址的最大數(shù)量。在這種情況下,我們只需要一個(gè)地址。geocoder 返回一個(gè)地址數(shù)組。如果沒(méi)有找到匹配指定位置的地址,那么它會(huì)返回空的列表。如果沒(méi)有可用的后臺(tái)地理編碼服務(wù),geocoder 會(huì)返回 null。
如下面代碼介紹來(lái)檢查下述這些錯(cuò)誤。如果出現(xiàn)錯(cuò)誤,就將相應(yīng)的錯(cuò)誤信息傳給變量 errorMessage,從而將錯(cuò)誤信息發(fā)送給發(fā)出請(qǐng)求的 activity:
使用 Address 類中的 getAddressLine() 方法來(lái)獲得地址對(duì)象的個(gè)別行。然后將這些行加入一個(gè)地址 fragment 列表當(dāng)中。其中,這個(gè)地址 fragment 列表準(zhǔn)備好返回到發(fā)出地址請(qǐng)求的 activity。
為了將結(jié)果返回給發(fā)出地址請(qǐng)求的 activity,需要調(diào)用 deliverResultToReceiver() 方法(定義于下面的把地址返回給請(qǐng)求端。結(jié)果由之前提到的成功/失敗數(shù)字代碼和一個(gè)字符串組成。在反向地理編碼成功的情況下,這個(gè)字符串包含著地址。在失敗的情況下,這個(gè)字符串包含錯(cuò)誤的信息。如下所示:
@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 服務(wù)最后要做的事情是將地址返回給啟動(dòng)服務(wù)的 activity 里的 ResultReceiver。這個(gè) ResultReceiver 類允許我們發(fā)送一個(gè)帶有結(jié)果的數(shù)字代碼和一個(gè)包含結(jié)果數(shù)據(jù)的消息。這個(gè)數(shù)字代碼說(shuō)明了地理編碼請(qǐng)求是成功還是失敗。在反向地理編碼成功的情況下,這個(gè)消息包含著地址。在失敗的情況下,這個(gè)消息包含一些描述失敗原因的文本。
我們已經(jīng)可以從 geocoder 取得地址,捕獲到可能出現(xiàn)的錯(cuò)誤,調(diào)用 deliverResultToReceiver() 方法?,F(xiàn)在我們需要定義 deliverResultToReceiver() 方法來(lái)將結(jié)果代碼和消息包發(fā)送給結(jié)果接收端。
對(duì)于結(jié)果代碼,使用已經(jīng)傳給 deliverResultToReceiver() 方法的 resultCode 參數(shù)的值。對(duì)于消息包的結(jié)構(gòu),連接 Constants 類的 RESULT_DATA_KEY 常量(定義與獲取街道地址數(shù)據(jù)和傳給 deliverResultToReceiver() 方法的 message 參數(shù)的值。如下所示:
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);
}
}
上節(jié)課定義的 intent 服務(wù)在后臺(tái)運(yùn)行,同時(shí),該服務(wù)負(fù)責(zé)提取與指定地理位置相對(duì)應(yīng)的地址。當(dāng)我們啟動(dòng)服務(wù),Android 框架會(huì)實(shí)例化并啟動(dòng)服務(wù)(如果該服務(wù)沒(méi)有運(yùn)行),并且如果需要的話,創(chuàng)建一個(gè)進(jìn)程。如果服務(wù)正在運(yùn)行,那么讓它保持運(yùn)行狀態(tài)。因?yàn)榉?wù)繼承于 IntentService,所以當(dāng)所有 intent 都被處理完之后,該服務(wù)會(huì)自動(dòng)停止。
在我們應(yīng)用的主 activity 中啟動(dòng)服務(wù),并且創(chuàng)建一個(gè) Intent 來(lái)把數(shù)據(jù)傳給服務(wù)。我們需要?jiǎng)?chuàng)建一個(gè)顯式的 intent,這是因?yàn)槲覀冎幌胛覀兊姆?wù)響應(yīng)該 intent。詳細(xì)請(qǐng)見(jiàn) Intent Types。
為了創(chuàng)建一個(gè)顯式的 intent,需要為服務(wù)指定要用到的類名:FetchAddressIntentService.class。在 intent 附加數(shù)據(jù)中傳入兩個(gè)信息:
下面的代碼介紹了如何啟動(dòng) intent 服務(wù):
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);
}
}
當(dāng)用戶請(qǐng)求查找地理地址時(shí),調(diào)用上述的 startIntentService() 方法。例如,用戶可能會(huì)在我們應(yīng)用的 UI 上面點(diǎn)擊提取地址按鈕。在啟動(dòng) intent 服務(wù)之前,我們需要檢查是否已經(jīng)連接到 Google Play services。下面的代碼片段介紹在一個(gè)按鈕 handler 中調(diào)用 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();
}
如果用戶點(diǎn)擊了應(yīng)用 UI 上面的提取地址按鈕,那么我們必須在 Google Play services 連接穩(wěn)定之后啟動(dòng) intent 服務(wù)。下面的代碼片段介紹了調(diào)用 Google API Client 提供的 onConnected() 回調(diào)函數(shù)中的 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 服務(wù)已經(jīng)處理完地理編碼請(qǐng)求,并用 ResultReceiver 將結(jié)果返回給發(fā)出請(qǐng)求的 activity。在發(fā)出請(qǐng)求的 activity 里,定義一個(gè)繼承于 ResultReceiver 的 AddressResultReceiver,用于處理在 FetchAddressIntentService 中的響應(yīng)。
結(jié)果包含一個(gè)數(shù)字代碼(resultCode)和一個(gè)包含結(jié)果數(shù)據(jù)(resultData)的消息。如果反向地理編碼成功的話,resultData 會(huì)包含地址。如果失敗,resultData 包含描述失敗原因的文本。關(guān)于錯(cuò)誤信息更詳細(xì)的內(nèi)容,請(qǐng)見(jiàn)把地址返回給請(qǐng)求端
重寫 onReceiveResult() 方法來(lái)處理發(fā)送給接收端的結(jié)果,如下所示:
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));
}
}
}
}