위치 주소 표시

마지막으로 알려진 위치 가져오기위치 업데이트 받기 과정에서는 사용자의 위치를 위도와 경도 좌표가 포함된 Location 개체의 형태로 가져오는 방법을 설명합니다. 위도와 경도는 거리를 계산하고 지도 위치를 표시하는 데 유용하지만 많은 경우 위치의 주소가 더 유용합니다. 예를 들어 사용자에게 현재 위치 또는 가까이 있는 장소를 알려주려 할 때는 상세 주소가 위치의 지리적 좌표(위도/경도)보다 유용합니다.

Android 프레임워크 위치 API의 Geocoder 클래스를 사용하면 주소를 해당하는 지리적 좌표로 변환할 수 있습니다. 이 과정을 지오코딩이라고 합니다. 또는 지리적 위치를 주소로 변환할 수 있습니다. 주소 조회 기능은 역지오코딩이라고도 합니다.

이 과정에서는 getFromLocation() 메서드를 사용하여 지리적 위치를 주소로 변환하는 방법을 보여줍니다. 이 메서드는 주어진 위도와 경도에 해당하는 예상 상세 주소를 반환합니다.

지리적 위치 가져오기

기기의 마지막으로 알려진 위치는 주소 조회 기능의 시작점 역할을 합니다. 마지막으로 알려진 위치를 가져오는 방법에 관한 과정에서는 통합 위치 정보 제공업체에서 제공하는 getLastLocation()을 사용하여 기기의 최근 위치를 찾는 방법을 보여줍니다.

통합 위치 정보 제공업체에 액세스하려면 FusedLocationProviderClient의 인스턴스를 만드세요. 클라이언트를 만드는 방법을 알아보려면 위치 서비스 클라이언트 만들기를 참조하세요.

정확한 상세 주소를 가져오도록 통합 위치 정보 제공업체를 사용 설정하려면 다음 예와 같이 앱 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>
    

주소를 가져오기 위한 인텐트 서비스 정의

Geocoder 클래스에서 제공하는 getFromLocation() 메서드에서는 위도와 경도 정보를 받고 주소 목록을 반환합니다. 이 메서드는 동기식 메서드이며 작업 실행 시간이 오래 걸릴 수 있으므로 앱의 기본 사용자 인터페이스(UI) 스레드에서 호출하면 안 됩니다.

IntentService 클래스는 백그라운드 스레드에서 작업을 실행할 수 있는 구조입니다. 이 클래스를 사용하면 UI의 반응 능력에 영향을 미치지 않고 장기 실행 작업을 처리할 수 있습니다.

IntentService를 확장하는 FetchAddressIntentService 클래스를 정의하세요. 이 클래스는 주소 조회 서비스입니다. 인텐트 서비스는 작업자 스레드에서 비동기식으로 인텐트를 처리하며 작업을 완료하면 중단됩니다. 추가 인텐트에서는 주소로 전환할 Location 개체 및 주소 조회 결과를 처리하기 위한 ResultReceiver 개체를 포함하여 서비스에 필요한 데이터를 제공합니다. 서비스에서는 Geocoder를 사용하여 위치의 주소를 가져오고 결과를 ResultReceiver에 보냅니다.

앱 manifest에서 인텐트 서비스 정의

다음과 같이 인텐트 서비스를 정의하는 항목을 앱 manifest에 추가하세요.

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

참고: 기본 활동에서 인텐트에 사용할 클래스의 이름을 지정하여 명시적 인텐트를 생성하므로 manifest의 <service> 요소에 인텐트 필터를 포함할 필요가 없습니다.

지오코더 만들기

지리적 위치를 주소로 변환하는 과정을 역지오코딩이라고 합니다. 인텐트 서비스의 기본 작업(역지오코딩 요청)을 실행하려면 FetchAddressIntentService 클래스 내에서 onHandleIntent()를 구현하세요. 역지오코딩을 처리하려면 Geocoder 개체를 만드세요.

언어는 특정한 지리적 영역 또는 언어적 지역을 대표합니다. 언어 개체는 언어가 대표하는 지역의 규칙에 맞게 숫자 또는 날짜와 같은 정보를 표시합니다. 결과 주소를 사용자의 지리적 영역으로 현지화하려면 Locale 개체를 Geocoder 개체에 전달하세요. 예를 들면 다음과 같습니다.

Kotlin

    override fun onHandleIntent(intent: Intent?) {
        val geocoder = Geocoder(this, Locale.getDefault())
        // ...
    }
    

자바

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

상세 주소 가져오기

이제 지오코더에서 상세 주소를 가져오고 발생한 오류를 처리하고 주소를 요청한 활동에 결과를 돌려보낼 수 있습니다. 지오코딩 과정의 결과를 보고하려면 성공 또는 실패를 나타내는 상수 두 개가 필요합니다. 다음 코드 스니펫에 표시된 대로 값을 포함할 상수를 정의하세요.

Kotlin

    object Constants {
        const val SUCCESS_RESULT = 0
        const val FAILURE_RESULT = 1
        const val PACKAGE_NAME = "com.google.android.gms.location.sample.locationaddress"
        const val RECEIVER = "$PACKAGE_NAME.RECEIVER"
        const val RESULT_DATA_KEY = "${PACKAGE_NAME}.RESULT_DATA_KEY"
        const val LOCATION_DATA_EXTRA = "${PACKAGE_NAME}.LOCATION_DATA_EXTRA"
    }
    

자바

    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()을 호출하고 위치 개체의 위도와 경도 및 반환받고 싶은 최대 주소 수를 전달하세요. 이 경우에는 하나의 주소만 반환받습니다. 지오코더에서는 다수의 주소를 반환합니다. 주어진 위치와 일치하는 주소를 찾을 수 없으면 빈 목록을 반환합니다. 사용 가능한 백엔드 지오코딩 서비스가 없으면 null을 반환합니다.

아래 코드 샘플에 표시된 대로 다음과 같은 오류가 있는지 확인하세요.

  • 위치 데이터가 제공되지 않음 – 추가 인텐트에 역지오코딩에 필요한 Location 개체가 포함되어 있지 않습니다.
  • 잘못된 위도 또는 경도가 사용됨Location 개체에 제공된 위도 및 경도 값이 잘못되었습니다.
  • 지오코더를 사용할 수 없음 – 네트워크 오류 또는 IO 예외로 인해 백그라운드 지오코딩 서비스를 사용할 수 없습니다.
  • 주소를 찾을 수 없음 – 지오코더에서 주어진 위도/경도의 주소를 찾을 수 없습니다.

오류가 발생하면 주소를 요청한 활동에 돌려보낼 수 있도록 오류 메시지를 errorMessage 변수에 추가하세요.

주소 개체의 개별 행을 가져오려면 Address에 클래스에서 제공한 getAddressLine() 메서드를 사용하세요. 주소를 요청한 활동에 반환할 주소 프래그먼트의 목록에 행을 결합하세요.

주소를 요청한 활동에 결과를 돌려보내려면 (요청자에게 주소 반환에 정의된) deliverResultToReceiver() 메서드를 호출하세요. 결과는 이전에 언급된 숫자 성공/실패 코드 및 문자열로 구성됩니다. 역지오코딩에 성공하면 문자열에 주소가 포함됩니다. 실패하면 다음 코드 샘플에 표시된 대로 문자열에 오류 메시지가 포함됩니다.

Kotlin

    protected fun onHandleIntent(intent: Intent?) {
        intent ?: return

        var errorMessage = ""

        // Get the location passed to this service through an extra.
        val location = intent.getParcelableExtra(
                Constants.LOCATION_DATA_EXTRA)

        // ...

        var addresses: List<Address> = emptyList()

        try {
            addresses = geocoder.getFromLocation(
                    location.latitude,
                    location.longitude,
                    // In this sample, we 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.latitude , " +
                    "Longitude =  $location.longitude", illegalArgumentException)
        }

        // Handle case where no address was found.
        if (addresses.isEmpty()) {
            if (errorMessage.isEmpty()) {
                errorMessage = getString(R.string.no_address_found)
                Log.e(TAG, errorMessage)
            }
            deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage)
        } else {
            val address = addresses[0]
            // Fetch the address lines using getAddressLine,
            // join them, and send them to the thread.
            val addressFragments = with(address) {
                (0..maxAddressLineIndex).map { getAddressLine(it) }
            }
            Log.i(TAG, getString(R.string.address_found))
            deliverResultToReceiver(Constants.SUCCESS_RESULT,
                    addressFragments.joinToString(separator = "\n"))
        }
    }
    

자바

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null) {
            return;
        }
        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));
        }
    }
    

요청자에게 주소 반환

인텐트에서 완료해야 하는 마지막 작업은 서비스를 시작한 활동의 ResultReceiver에 주소를 돌려보내는 것입니다. ResultReceiver 클래스를 사용하면 숫자 결과 코드 및 결과 데이터가 포함된 메시지를 보낼 수 있습니다. 숫자 코드는 지오코딩 요청의 성공 또는 실패를 보고하는 데 유용합니다. 역지오코딩에 성공하면 메시지에 주소가 포함됩니다. 실패한 경우 실패 이유를 설명하는 텍스트가 메시지에 포함됩니다.

이미 지오코더에서 주소를 가져오고, 발생할 수 있는 모든 오류를 포착하고, deliverResultToReceiver() 메서드를 호출했으므로 이제 결과 코드 및 메시지 번들을 결과 수신자에게 보내는 deliverResultToReceiver() 메서드를 정의해야 합니다.

결과 코드의 경우 resultCode 매개변수의 deliverResultToReceiver() 메서드에 전달한 값을 사용하세요. 메시지 번들을 구성하려면 다음 샘플에 표시된 대로 상세 주소 데이터 가져오기에 정의된 Constants 클래스의 RESULT_DATA_KEY 상수와 deliverResultToReceiver() 메서드에 전달된 message 매개변수의 값을 연결하세요.

Kotlin

    class FetchAddressIntentService : IntentService() {
        private var receiver: ResultReceiver? = null

        // ...

        private fun deliverResultToReceiver(resultCode: Int, message: String) {
            val bundle = Bundle().apply { putString(Constants.RESULT_DATA_KEY, message) }
            receiver?.send(resultCode, bundle)
        }

    }
    

자바

    public class FetchAddressIntentService extends IntentService {
        protected ResultReceiver receiver;
        // ...
        private void deliverResultToReceiver(int resultCode, String message) {
            Bundle bundle = new Bundle();
            bundle.putString(Constants.RESULT_DATA_KEY, message);
            receiver.send(resultCode, bundle);
        }
    }
    

인텐트 서비스 시작

인텐트 서비스는 이전 섹션에 정의된 대로 백그라운드에서 실행되며 주어진 지리적 위치에 해당하는 주소를 가져옵니다. 서비스를 시작하면 서비스가 아직 실행되고 있지 않은 경우 Android 프레임워크에서 서비스를 인스턴스화하여 시작하고 필요한 경우 프로세스를 만듭니다. 서비스가 이미 실행 중인 경우 계속 실행됩니다. 서비스가 IntentService를 확장하므로 모든 인텐트가 처리된 후 자동으로 종료됩니다.

앱의 기본 활동에서 서비스를 시작하고 서비스에 데이터를 전달할 Intent를 만드세요. 내 서비스만 인텐트에 응답하기를 원하므로 명시적 인텐트가 필요합니다. 자세한 내용은 인텐트 유형을 참조하세요.

명시적 인텐트를 만들려면 서비스에 사용할 클래스의 이름(FetchAddressIntentService.class)을 지정하세요. 다음 정보를 추가 인텐트로 전달하세요.

  • 주소 조회 결과를 처리하기 위한 ResultReceiver
  • 주소로 변환할 위도와 경도가 포함된 Location 개체

다음 코드 샘플은 인텐트 서비스를 시작하는 방법을 보여줍니다.

Kotlin

    class MainActivity : AppCompatActivity(), ConnectionCallbacks, OnConnectionFailedListener {

        private var lastLocation: Location? = null
        private lateinit var resultReceiver: AddressResultReceiver

        // ...

        private fun startIntentService() {

            val intent = Intent(this, FetchAddressIntentService::class.java).apply {
                putExtra(Constants.RECEIVER, resultReceiver)
                putExtra(Constants.LOCATION_DATA_EXTRA, lastLocation)
            }
            startService(intent)
        }
    }
    

자바

    public class MainActivity extends AppCompatActivity implements
            ConnectionCallbacks, OnConnectionFailedListener {

        protected Location lastLocation;
        private AddressResultReceiver resultReceiver;

        // ...

        protected void startIntentService() {
            Intent intent = new Intent(this, FetchAddressIntentService.class);
            intent.putExtra(Constants.RECEIVER, resultReceiver);
            intent.putExtra(Constants.LOCATION_DATA_EXTRA, lastLocation);
            startService(intent);
        }
    }
    

주의: 앱을 안전하게 유지하려면 Service를 시작할 때 항상 명시적 인텐트를 사용하고 서비스에 인텐트 필터를 선언하지 마세요. 암시적 인텐트를 사용하여 서비스를 시작하면 어떤 서비스가 인텐트에 응답할지 확신할 수 없고 어떤 서비스가 시작하는지 사용자가 알 수 없으므로 보안 위험이 있습니다.

사용자가 지오코딩 주소 조회가 필요한 작업을 실행할 때 위의 startIntentService() 메서드를 호출하세요. 예를 들어 사용자가 앱의 UI에서 주소 가져오기 버튼을 누를 수도 있습니다. 다음 코드 스니펫에서는 버튼 핸들러에서 startIntentService() 메서드 호출을 보여줍니다.

Kotlin

    fun fetchAddressButtonHander(view: View) {
        fusedLocationClient?.lastLocation?.addOnSuccessListener { location: Location? ->
            lastKnownLocation = location

            if (lastKnownLocation == null) return@addOnSuccessListener

            if (!Geocoder.isPresent()) {
                Toast.makeText(this@MainActivity,
                        R.string.no_geocoder_available,
                        Toast.LENGTH_LONG).show()
                return@addOnSuccessListener
            }

            // Start service and update UI to reflect new location
            startIntentService()
            updateUI()
        }
    }
    

자바

    private void fetchAddressButtonHander(View view) {
        fusedLocationClient.getLastLocation()
                .addOnSuccessListener(this, new OnSuccessListener<Location>() {
                    @Override
                    public void onSuccess(Location location) {
                        lastKnownLocation = location;

                        // In some rare cases the location returned can be null
                        if (lastKnownLocation == null) {
                            return;
                        }

                        if (!Geocoder.isPresent()) {
                            Toast.makeText(MainActivity.this,
                                    R.string.no_geocoder_available,
                                    Toast.LENGTH_LONG).show();
                            return;
                        }

                        // Start service and update UI to reflect new location
                        startIntentService();
                        updateUI();
                    }
                });
        }
    

지오코딩 결과 받기

인텐트 서비스에서는 지오코딩 결과를 처리한 후, ResultReceiver를 사용하여 지리적 좌표를 요청한 활동에 결과를 반환합니다. 이 활동에서 FetchAddressIntentService의 응답을 처리하기 위해 ResultReceiver를 확장하는 AddressResultReceiver를 정의하세요.

결과에는 숫자 결과 코드(resultCode) 및 결과 데이터가 포함된 메시지(resultData)가 포함됩니다. 역지오코딩 프로세스에 성공한 경우 resultData에 주소가 포함됩니다. 실패한 경우 실패 이유를 설명하는 텍스트가 resultData에 포함됩니다. 가능한 오류의 자세한 내용은 요청자에게 주소 반환을 참조하세요.

다음 코드 샘플에 표시된 대로 결과 수신자에게 전달된 결과를 처리하도록 onReceiveResult() 메서드를 재정의하세요.

Kotlin

    class MainActivity : AppCompatActivity() {
        // ...
        internal inner class AddressResultReceiver(handler: Handler) : ResultReceiver(handler) {

            override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {

                // Display the address string
                // or an error message sent from the intent service.
                addressOutput = 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))
                }

            }
        }
    }
    

자바

    public class MainActivity extends AppCompatActivity {

        // ...

        class AddressResultReceiver extends ResultReceiver {
            public AddressResultReceiver(Handler handler) {
                super(handler);
            }

            @Override
            protected void onReceiveResult(int resultCode, Bundle resultData) {

                if (resultData == null) {
                    return;
                }

                // Display the address string
                // or an error message sent from the intent service.
                addressOutput = resultData.getString(Constants.RESULT_DATA_KEY);
                if (addressOutput == null) {
                    addressOutput = "";
                }
                displayAddressOutput();

                // Show a toast message if an address was found.
                if (resultCode == Constants.SUCCESS_RESULT) {
                    showToast(getString(R.string.address_found));
                }

            }
        }
    }