Android 顯示位置地址

2018-08-02 17:46 更新

編寫(xiě):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ò)程叫做地理編碼?;蛘撸覀兛梢詫⒌乩砦恢棉D(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>

定義一個(gè) Intent 服務(wù)來(lái)取得地址

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,并在它離開(kāi)這個(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

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

在 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。

創(chuàng)建一個(gè) Geocoder

將一個(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());
    ...
}

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

下一步是從 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:

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

使用 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));
    }
}

把地址返回給請(qǐng)求端

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() 方法。現(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);
    }
}

啟動(dòng) Intent 服務(wù)

上節(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è)信息:

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

下面的代碼介紹了如何啟動(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();
            }
        }
    }
}

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

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)求端

重寫(xiě) 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));
            }

        }
    }
}


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)