Android 顯示位置地址

2018-08-02 17:46 更新

編寫:penkzhou - 原文:http://developer.android.com/training/location/display-address.html

獲取最后可知位置獲取位置更新課程描述了如何以一個Location對象的形式獲取用戶的位置信息,這個位置信息包括了經(jīng)緯度。盡管經(jīng)緯度對計算地理距離和在地圖上顯示位置很有用,但是更多情況下位置的地址更有用。例如,如果我們想讓用戶知道他們在哪里,那么一個街道地址比地理坐標(biāo)(經(jīng)度/緯度)更加有意義。

使用 Android 框架位置 APIs 的 Geocoder 類,我們可以將地址轉(zhuǎn)換成相應(yīng)的地理坐標(biāo)。這個過程叫做地理編碼?;蛘撸覀兛梢詫⒌乩砦恢棉D(zhuǎn)換成相應(yīng)的地址。這種地址查找功能叫做反向地理編碼。

這節(jié)課介紹了如何用 getFromLocation() 方法將地理位置轉(zhuǎn)換成地址。這個方法返回與制定經(jīng)緯度相對應(yīng)的估計的街道地址。

獲取地理位置

設(shè)備的最后可知位置對于地址查找功能是很有用的基礎(chǔ)。獲取最后可知位置介紹了如何通過調(diào)用 fused location provider 提供的 getLastLocation()) 方法找到設(shè)備的最后可知位置。

為了訪問 fused location provider,我們需要創(chuàng)建一個 Google Play services API client 的實(shí)例。關(guān)于如何連接 client,請見連接 Google Play Services 。

為了讓 fused location provider 得到一個準(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>

定義一個 Intent 服務(wù)來取得地址

Geocoder 類的 getFromLocation() 方法接收一個經(jīng)度和緯度,返回一個地址列表。這個方法是同步的,可能會花很長時間來完成它的工作,所以我們不應(yīng)該在應(yīng)用的主線程和 UI 線程里調(diào)用這個方法。

IntentService 類提供了一種結(jié)構(gòu)使一個任務(wù)在后臺線程運(yùn)行。使用這個類,我們可以在不影響 UI 響應(yīng)速度的情況下處理一個長時間運(yùn)行的操作。注意到,AsyncTask 類也可以執(zhí)行后臺操作,但是它被設(shè)計用于短時間運(yùn)行的操作。在 activity 重新創(chuàng)建時(例如當(dāng)設(shè)備旋轉(zhuǎn)時),AsyncTask 不應(yīng)該保存 UI 的引用。相反,當(dāng) activity 重建時,不需要取消 IntentService

定義一個繼承 IntentService 的類 FetchAddressIntentService。這個類是地址查找服務(wù)。這個 Intent 服務(wù)在一個工作線程上異步地處理一個 intent,并在它離開這個工作時自動停止。Intent 外加的數(shù)據(jù)提供了服務(wù)需要的數(shù)據(jù),包括一個用于轉(zhuǎn)換成地址的 Location 對象和一個用于處理地址查找結(jié)果的 ResultReceiver 對象。這個服務(wù)用一個 Geocoder 來獲取位置的地址,并且將結(jié)果發(fā)送給 ResultReceiver

在應(yīng)用的 manifest 文件中定義 Intent 服務(wù)

在 manifest 文件中添加一個節(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)不需要包含一個 intent filter,這是因?yàn)槲覀兊闹?activity 通過指定 intent 用到的類的名字來創(chuàng)建一個隱式的 intent。

創(chuàng)建一個 Geocoder

將一個地理位置傳換成地址的過程叫做反向地理編碼。通過實(shí)現(xiàn) FetchAddressIntentService 類的 onHandleIntent()) 來執(zhí)行 intent 服務(wù)的主要工作,即反向地理編碼請求。創(chuàng)建一個 Geocoder 對象來處理反向地理編碼。

一個區(qū)域設(shè)置代表一個特定的地理上的或者語言上的區(qū)域。Locale 對象用于調(diào)整信息的呈現(xiàn)方式,例如數(shù)字或者日期,來適應(yīng)區(qū)域設(shè)置表示的區(qū)域的約定。傳一個 Locale 對象到 Geocoder 對象,確保地址結(jié)果為用戶的地理區(qū)域作出了本地化。

@Override
protected void onHandleIntent(Intent intent) {
    Geocoder geocoder = new Geocoder(this, Locale.getDefault());
    ...
}

獲取街道地址數(shù)據(jù)

下一步是從 geocoder 獲取街道地址,處理可能出現(xiàn)的錯誤,和將結(jié)果返回給請求地址的 activity。我們需要兩個分別代表成功和失敗的數(shù)字常量來報告地理編碼過程的結(jié)果。定義一個 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";
}

為了獲取與地理位置相對應(yīng)的街道地址,調(diào)用 getFromLocation(),傳入位置對象的經(jīng)度和緯度,以及我們想要返回的地址的最大數(shù)量。在這種情況下,我們只需要一個地址。geocoder 返回一個地址數(shù)組。如果沒有找到匹配指定位置的地址,那么它會返回空的列表。如果沒有可用的后臺地理編碼服務(wù),geocoder 會返回 null。

如下面代碼介紹來檢查下述這些錯誤。如果出現(xiàn)錯誤,就將相應(yīng)的錯誤信息傳給變量 errorMessage,從而將錯誤信息發(fā)送給發(fā)出請求的 activity:

  • No location data provided - Intent 的附加數(shù)據(jù)沒有包含反向地理編碼需要用到的 Location 對象。
  • Invalid latitude or longitude used - Location 對象提供的緯度和/或者經(jīng)度無效。
  • No geocoder available - 由于網(wǎng)絡(luò)錯誤或者 IO 異常,導(dǎo)致后臺地理編碼服務(wù)不可用。
  • Sorry, no address found - geocoder 找不到指定緯度/經(jīng)度對應(yīng)的地址。

使用 Address 類中的 getAddressLine()) 方法來獲得地址對象的個別行。然后將這些行加入一個地址 fragment 列表當(dāng)中。其中,這個地址 fragment 列表準(zhǔn)備好返回到發(fā)出地址請求的 activity。

為了將結(jié)果返回給發(fā)出地址請求的 activity,需要調(diào)用 deliverResultToReceiver() 方法(定義于下面的把地址返回給請求端)。結(jié)果由之前提到的成功/失敗數(shù)字代碼和一個字符串組成。在反向地理編碼成功的情況下,這個字符串包含著地址。在失敗的情況下,這個字符串包含錯誤的信息。如下所示:

@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ù)最后要做的事情是將地址返回給啟動服務(wù)的 activity 里的 ResultReceiver。這個 ResultReceiver 類允許我們發(fā)送一個帶有結(jié)果的數(shù)字代碼和一個包含結(jié)果數(shù)據(jù)的消息。這個數(shù)字代碼說明了地理編碼請求是成功還是失敗。在反向地理編碼成功的情況下,這個消息包含著地址。在失敗的情況下,這個消息包含一些描述失敗原因的文本。

我們已經(jīng)可以從 geocoder 取得地址,捕獲到可能出現(xiàn)的錯誤,調(diào)用 deliverResultToReceiver() 方法?,F(xiàn)在我們需要定義 deliverResultToReceiver() 方法來將結(jié)果代碼和消息包發(fā)送給結(jié)果接收端。

對于結(jié)果代碼,使用已經(jīng)傳給 deliverResultToReceiver() 方法的 resultCode 參數(shù)的值。對于消息包的結(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);
    }
}

啟動 Intent 服務(wù)

上節(jié)課定義的 intent 服務(wù)在后臺運(yùn)行,同時,該服務(wù)負(fù)責(zé)提取與指定地理位置相對應(yīng)的地址。當(dāng)我們啟動服務(wù),Android 框架會實(shí)例化并啟動服務(wù)(如果該服務(wù)沒有運(yùn)行),并且如果需要的話,創(chuàng)建一個進(jìn)程。如果服務(wù)正在運(yùn)行,那么讓它保持運(yùn)行狀態(tài)。因?yàn)榉?wù)繼承于 IntentService,所以當(dāng)所有 intent 都被處理完之后,該服務(wù)會自動停止。

在我們應(yīng)用的主 activity 中啟動服務(wù),并且創(chuàng)建一個 Intent 來把數(shù)據(jù)傳給服務(wù)。我們需要創(chuàng)建一個顯式的 intent,這是因?yàn)槲覀冎幌胛覀兊姆?wù)響應(yīng)該 intent。詳細(xì)請見 Intent Types。

為了創(chuàng)建一個顯式的 intent,需要為服務(wù)指定要用到的類名:FetchAddressIntentService.class。在 intent 附加數(shù)據(jù)中傳入兩個信息:

  • 一個用于處理地址查找結(jié)果的 ResultReceiver。
  • 一個包含想要轉(zhuǎn)換成地址的緯度和經(jīng)度的 Location 對象。

下面的代碼介紹了如何啟動 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)用戶請求查找地理地址時,調(diào)用上述的 startIntentService() 方法。例如,用戶可能會在我們應(yīng)用的 UI 上面點(diǎn)擊提取地址按鈕。在啟動 intent 服務(wù)之前,我們需要檢查是否已經(jīng)連接到 Google Play services。下面的代碼片段介紹在一個按鈕 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)定之后啟動 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();
            }
        }
    }
}

獲取地理編碼結(jié)果

Intent 服務(wù)已經(jīng)處理完地理編碼請求,并用 ResultReceiver 將結(jié)果返回給發(fā)出請求的 activity。在發(fā)出請求的 activity 里,定義一個繼承于 ResultReceiver 的 AddressResultReceiver,用于處理在 FetchAddressIntentService 中的響應(yīng)。

結(jié)果包含一個數(shù)字代碼(resultCode)和一個包含結(jié)果數(shù)據(jù)(resultData)的消息。如果反向地理編碼成功的話,resultData 會包含地址。如果失敗,resultData 包含描述失敗原因的文本。關(guān)于錯誤信息更詳細(xì)的內(nèi)容,請見把地址返回給請求端

重寫 onReceiveResult() 方法來處理發(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));
            }

        }
    }
}


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號