6월 3일의 ⁠#Android11: 베타 버전 출시 행사에 참여하세요.

위치 전략

참고: 이 가이드에서 설명하는 전략은 android.location의 플랫폼 위치 API에 적용됩니다. Google Play 서비스의 일부인 Google 위치 서비스 API는 위치 제공자, 사용자 이동, 위치 정확성을 자동으로 처리하는 더 강력하고 수준 높은 프레임워크를 제공합니다. 또한 제공되는 전력 소모량 매개변수에 따라 위치 업데이트 일정 예약을 처리합니다. 대부분의 경우 위치 서비스 API를 사용하면 배터리 성능과 정확성이 향상됩니다.

위치 서비스 API에 관해 자세히 알아보려면 Android용 Google 위치 서비스를 참조하세요.

사용자의 위치를 알면 애플리케이션이 더 스마트해져서 사용자에게 더 나은 정보를 제공할 수 있습니다. Android용 위치 인식 애플리케이션을 개발할 때 GPS와 Android의 네트워크 위치 제공자를 활용하여 사용자의 위치를 확인할 수 있습니다. GPS는 가장 정확하지만 실외에서만 작동하며 배터리 전력을 빠르게 소모하고 사용자가 원하는 만큼 빠르게 위치를 반환하지 않습니다. Android의 네트워크 위치 제공자는 기지국과 Wi-Fi 신호로 사용자 위치를 파악하여 실내 및 실외에서 작동하며 더 빠르게 응답하고 배터리 전력을 덜 사용하면서 위치 정보를 제공합니다. 애플리케이션에서 사용자 위치를 가져오려면 GPS와 네트워크 위치 제공자를 모두 사용하거나 하나만 사용해도 됩니다.

사용자 위치 확인의 문제점

휴대기기에서 사용자의 위치를 가져오는 일은 복잡합니다. 소스와 상관없이 위치 읽기에 오류가 포함되고 정확하지 않을 수 있는 이유에는 여러 가지가 있습니다. 사용자 위치의 오류와 관련된 몇 가지 소스는 다음과 같습니다.

  • 다양한 위치 소스

    GPS, Cell-ID, Wi-Fi에서 각각 사용자 위치에 대한 단서를 제공할 수 있습니다. 어떤 정보를 이용하고 신뢰할지는 정확성, 속도, 배터리 효율성을 모두 고려해서 결정해야 합니다.

  • 사용자 이동

    사용자의 위치는 변경되므로 사용자 위치를 자주 다시 예측하여 이동을 고려해야 합니다.

  • 변경되는 정확성

    위치 소스에 따라 제공되는 위치 예측의 정확성은 일관되지 않습니다. 한 소스에서 10초 전에 가져온 위치가 다른 소스나 동일한 소스의 최신 위치보다 더 정확할 수 있습니다.

이러한 문제로 인해 신뢰할 수 있는 사용자 위치 정보를 가져오기가 더 어려울 수 있습니다. 이 문서에서는 신뢰할 수 있는 위치 정보를 가져올 때 발생하는 문제를 해결하는 데 도움이 되는 정보를 제공합니다. 또한 애플리케이션에서 사용자에게 정확한 반응형 위치 정보 환경을 제공하는 데 사용할 수 있는 아이디어를 제공합니다.

위치 업데이트 요청

위에 설명된 일부 위치 오류를 해결하기 전에 먼저 Android에서 사용자 위치를 가져오는 방법을 소개합니다.

Android에서는 콜백을 사용해 사용자 위치를 가져옵니다. requestLocationUpdates()를 호출하고 LocationListener로 전달하여 LocationManager에서 위치 업데이트를 받고자 함을 나타냅니다. LocationListener는 사용자 위치가 변경되거나 서비스 상태가 변경될 때 LocationManager가 호출하는 여러 콜백 메서드를 구현해야 합니다.

참고: Android 8.0(API 레벨 26) 이상에서 앱이 현재 위치를 요청할 때 백그라운드에서 실행 중인 경우 기기에서는 매시간 몇 번만 위치를 계산합니다. 이러한 계산 한도에 앱을 맞추는 방법을 알아보려면 백그라운드 위치 제한을 참조하세요.

다음 코드에서는 LocationListener를 정의하고 위치 업데이트를 요청하는 방법을 보여 줍니다.

Kotlin

    // Acquire a reference to the system Location Manager
    val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager

    // Define a listener that responds to location updates
    val locationListener = object : LocationListener {

        override fun onLocationChanged(location: Location) {
            // Called when a new location is found by the network location provider.
            makeUseOfNewLocation(location)
        }

        override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {
        }

        override fun onProviderEnabled(provider: String) {
        }

        override fun onProviderDisabled(provider: String) {
        }
    }

    // Register the listener with the Location Manager to receive location updates
    locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0f,locationListener)
    

자바

    // Acquire a reference to the system Location Manager
    LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

    // Define a listener that responds to location updates
    LocationListener locationListener = new LocationListener() {
        public void onLocationChanged(Location location) {
          // Called when a new location is found by the network location provider.
          makeUseOfNewLocation(location);
        }

        public void onStatusChanged(String provider, int status, Bundle extras) {}

        public void onProviderEnabled(String provider) {}

        public void onProviderDisabled(String provider) {}
      };

    // Register the listener with the Location Manager to receive location updates
    locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
    

requestLocationUpdates()의 첫 번째 매개변수는 사용할 위치 제공자 유형입니다(이 경우 기지국 및 Wi-Fi 기반 위치에 관한 네트워크 위치 제공자). 두 번째와 세 번째 매개변수를 사용해 리스너가 업데이트를 수신하는 빈도를 제어할 수 있습니다. 여기에서 두 번째 매개변수는 알림 간 최소 시간 간격이고 세 번째 매개변수는 알림 간 최소 거리 변경사항입니다. 두 매개변수를 0으로 설정하면 가능한 한 자주 위치 알림을 요청합니다. 마지막 매개변수는 위치 업데이트에 관한 콜백을 수신하는 LocationListener입니다.

GPS 제공자로부터 위치 업데이트를 요청하려면 NETWORK_PROVIDER 대신 GPS_PROVIDER를 사용하세요. requestLocationUpdates()를 두 번 호출하여(각각 NETWORK_PROVIDERGPS_PROVIDER에 한 번씩) GPS와 네트워크 위치 제공자로부터 모두 위치 업데이트를 요청할 수도 있습니다.

사용자 권한 요청

NETWORK_PROVIDER 또는 GPS_PROVIDER에서 위치 업데이트를 받으려면 Android manifest 파일에서 각각 ACCESS_COARSE_LOCATION 또는 ACCESS_FINE_LOCATION 권한을 선언하여 사용자의 권한을 요청해야 합니다. 이러한 권한이 없으면 애플리케이션에서 런타임 시 위치 업데이트를 요청할 때 실패합니다.

NETWORK_PROVIDERGPS_PROVIDER를 모두 사용하는 경우 ACCESS_FINE_LOCATION 권한에 두 제공자의 권한이 포함되어 있으므로 이 권한만 요청해야 합니다. ACCESS_COARSE_LOCATION 권한은 NETWORK_PROVIDER에 대한 액세스만 허용합니다.

주의: 앱이 Android 5.0(API 레벨 21) 이상을 타겟팅하는 경우, 업데이트를 받는 곳이 NETWORK_PROVIDER인지 GPS_PROVIDER인지에 따라 manifest 파일에서 앱이 android.hardware.location.network 또는 android.hardware.location.gps 하드웨어 기능을 사용함을 선언해야 합니다. 앱이 이러한 위치 제공자 소스 중 하나에서 위치 정보를 수신하는 경우 앱 manifest에서 앱이 이러한 하드웨어 기능을 사용함을 선언해야 합니다. Android 5.0(API 21) 이전 버전을 실행 중인 기기에서 ACCESS_FINE_LOCATION 또는 ACCESS_COARSE_LOCATION 권한을 요청하면 위치 하드웨어 기능에 대한 암시적 요청이 포함됩니다. 그러나 이러한 권한을 요청하는 것이 Android 5.0(API 레벨 21) 이상에서 위치 하드웨어 기능을 자동으로 요청하는 것은 아닙니다.

다음 코드 샘플은 기기의 GPS에서 데이터를 읽는 앱의 매니페스트 파일에 권한과 하드웨어 기능을 선언하는 방법을 보여 줍니다.

    <manifest ... >
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        ...
        <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
        <uses-feature android:name="android.hardware.location.gps" />
        ...
    </manifest>
    

최적의 성능을 위한 모델 정의

위치 기반 애플리케이션은 이제 널리 사용되지만 정확성이 떨어지거나 사용자 이동, 위치를 가져오는 다양한 방법, 배터리를 절약할 필요성으로 인해 사용자의 위치를 가져오는 일은 복잡합니다. 배터리 전력을 절약하면서 정확한 사용자 위치를 가져오는 데 따르는 장애를 극복하려면 애플리케이션에서 사용자 위치를 가져오는 방법을 지정하는 일관된 모델을 정의해야 합니다. 이 모델에는 업데이트 수신을 시작할 때와 중지할 때, 캐시된 위치 데이터를 사용할 때가 포함됩니다.

사용자 위치를 가져오기 위한 과정

사용자 위치를 가져오기 위한 일반적인 과정은 다음과 같습니다.

  1. 애플리케이션을 시작합니다.
  2. 얼마 후 원하는 위치 제공자에서 업데이트 수신을 시작합니다.
  3. 새롭지만 정확성이 낮은 수정사항을 필터링하여 위치의 '현재 최선의 예측'을 유지합니다.
  4. 위치 업데이트 수신을 중지합니다.
  5. 마지막 최선의 위치 추정을 활용합니다.

그림 1에서는 이 모델을 애플리케이션에서 위치 업데이트를 수신하는 기간과 그 기간에 발생하는 이벤트를 시각화한 타임라인에서 보여 줍니다.

그림 1. 애플리케이션에서 위치 업데이트를 수신하는 기간을 나타내는 타임라인

이 기간(위치 업데이트가 수신되는 기간) 모델은 애플리케이션에 위치 기반 서비스를 추가할 때 필요한 다양한 결정을 나타냅니다.

업데이트 수신을 시작할 시기 결정

애플리케이션이 시작되는 즉시 또는 사용자가 특정 기능을 활성화한 후에만 위치 업데이트 수신을 시작하고 싶을 수 있습니다. 위치 수정사항을 수신하는 기간이 길면 배터리 전력 소모가 클 수 있습니다. 하지만 기간이 짧으면 정확성이 충분히 보장되지 않을 수 있습니다.

위에서 설명한 대로 requestLocationUpdates()를 호출하여 업데이트 수신을 시작할 수 있습니다.

Kotlin

    val locationProvider: String = LocationManager.NETWORK_PROVIDER
    // Or, use GPS location data:
    // val locationProvider: String = LocationManager.GPS_PROVIDER;

    locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener)
    

자바

    String locationProvider = LocationManager.NETWORK_PROVIDER;
    // Or, use GPS location data:
    // String locationProvider = LocationManager.GPS_PROVIDER;

    locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener);
    

마지막으로 알려진 위치로 빠르게 수정하기

위치 리스너가 첫 번째 위치 수정사항을 받는 데 걸리는 시간은 사용자가 기다리기에 너무 긴 경우가 많습니다. 다음과 같이 위치 리스너에 더 정확한 위치가 제공될 때까지 getLastKnownLocation(String)을 호출하여 캐시된 위치를 활용해야 합니다.

Kotlin

    val locationProvider: String = LocationManager.NETWORK_PROVIDER
    // Or use LocationManager.GPS_PROVIDER

    val lastKnownLocation: Location = locationManager.getLastKnownLocation(locationProvider)
    

자바

    String locationProvider = LocationManager.NETWORK_PROVIDER;
    // Or use LocationManager.GPS_PROVIDER

    Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);
    

업데이트 수신을 중지할 시기 결정

새로운 수정사항이 더 이상 필요하지 않을 때를 결정하는 논리는 애플리케이션에 따라 매우 단순한 논리에서 매우 복잡한 논리까지 다양합니다. 위치 정보를 얻었을 때와 그 정보가 사용되는 때 사이의 차이가 짧으면 예상치의 정확성이 향상됩니다. 수신 기간이 길어지면 배터리 전력이 많이 소모되므로 다음과 같이 필요한 정보를 얻는 즉시 removeUpdates(PendingIntent)를 호출하여 업데이트 수신을 중지해야 합니다.

Kotlin

    // Remove the listener you previously added
    locationManager.removeUpdates(locationListener)
    

자바

    // Remove the listener you previously added
    locationManager.removeUpdates(locationListener);
    

현재 최선의 예상치 유지

최근의 위치 수정사항이 가장 정확하다고 예상할 수 있습니다. 하지만 위치 수정사항의 정확성은 다양하므로 최근 수정사항이 항상 최선은 아닙니다. 여러 기준을 근거로 위치 수정사항을 선택하기 위한 논리를 포함해야 합니다. 애플리케이션과 필드 테스트의 사용 사례에 따라 기준도 달라집니다.

위치 수정사항의 정확성을 확인하는 데 활용할 수 있는 몇 가지 단계는 다음과 같습니다.

  • 가져온 위치가 이전 예상치보다 현저히 최신 정보인지 확인합니다.
  • 위치의 정확성이 이전 예상치보다 더 나은지 또는 그렇지 않은지 확인합니다.
  • 새로운 위치가 어느 제공자로부터 제공되었는지 확인하고 그 제공자를 더 신뢰하는지 판단합니다.

이 논리의 자세한 예는 다음과 같을 수 있습니다.

Kotlin

    private const val TWO_MINUTES: Long = 1000 * 60 * 2

    /** Determines whether one Location reading is better than the current Location fix
     * @param location The new Location that you want to evaluate
     * @param currentBestLocation The current Location fix, to which you want to compare the new one
     */
    fun isBetterLocation(location: Location, currentBestLocation: Location?): Boolean {
        if (currentBestLocation == null) {
            // A new location is always better than no location
            return true
        }

        // Check whether the new location fix is newer or older
        val timeDelta: Long = location.time - currentBestLocation.time
        val isSignificantlyNewer: Boolean = timeDelta > TWO_MINUTES
        val isSignificantlyOlder:Boolean = timeDelta < -TWO_MINUTES

        when {
            // If it's been more than two minutes since the current location, use the new location
            // because the user has likely moved
            isSignificantlyNewer -> return true
            // If the new location is more than two minutes older, it must be worse
            isSignificantlyOlder -> return false
        }

        // Check whether the new location fix is more or less accurate
        val isNewer: Boolean = timeDelta > 0L
        val accuracyDelta: Float = location.accuracy - currentBestLocation.accuracy
        val isLessAccurate: Boolean = accuracyDelta > 0f
        val isMoreAccurate: Boolean = accuracyDelta < 0f
        val isSignificantlyLessAccurate: Boolean = accuracyDelta > 200f

        // Check if the old and new location are from the same provider
        val isFromSameProvider: Boolean = location.provider == currentBestLocation.provider

        // Determine location quality using a combination of timeliness and accuracy
        return when {
            isMoreAccurate -> true
            isNewer && !isLessAccurate -> true
            isNewer && !isSignificantlyLessAccurate && isFromSameProvider -> true
            else -> false
        }
    }
    

자바

    private static final int TWO_MINUTES = 1000 * 60 * 2;

    /** Determines whether one Location reading is better than the current Location fix
      * @param location  The new Location that you want to evaluate
      * @param currentBestLocation  The current Location fix, to which you want to compare the new one
      */
    protected boolean isBetterLocation(Location location, Location currentBestLocation) {
        if (currentBestLocation == null) {
            // A new location is always better than no location
            return true;
        }

        // Check whether the new location fix is newer or older
        long timeDelta = location.getTime() - currentBestLocation.getTime();
        boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
        boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
        boolean isNewer = timeDelta > 0;

        // If it's been more than two minutes since the current location, use the new location
        // because the user has likely moved
        if (isSignificantlyNewer) {
            return true;
        // If the new location is more than two minutes older, it must be worse
        } else if (isSignificantlyOlder) {
            return false;
        }

        // Check whether the new location fix is more or less accurate
        int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
        boolean isLessAccurate = accuracyDelta > 0;
        boolean isMoreAccurate = accuracyDelta < 0;
        boolean isSignificantlyLessAccurate = accuracyDelta > 200;

        // Check if the old and new location are from the same provider
        boolean isFromSameProvider = isSameProvider(location.getProvider(),
                currentBestLocation.getProvider());

        // Determine location quality using a combination of timeliness and accuracy
        if (isMoreAccurate) {
            return true;
        } else if (isNewer && !isLessAccurate) {
            return true;
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            return true;
        }
        return false;
    }

    /** Checks whether two providers are the same */
    private boolean isSameProvider(String provider1, String provider2) {
        if (provider1 == null) {
          return provider2 == null;
        }
        return provider1.equals(provider2);
    }
    

배터리 및 데이터 교환을 절약하도록 모델 조정

애플리케이션을 테스트할 때 정확한 위치와 우수한 성능을 제공하려면 모델을 약간 조정해야 한다는 사실을 알 수 있습니다. 둘 사이의 균형을 잘 맞추기 위해 변경할 수 있는 몇 가지 사항은 다음과 같습니다.

기간 줄이기

위치 업데이트를 수신하는 기간이 짧으면 GPS 및 네트워크 위치 서비스와의 상호작용이 적으므로 배터리 수명이 절약됩니다. 그러나 최선의 예상치를 선택할 수 있는 위치의 개수도 적어질 수 있습니다.

업데이트를 덜 자주 반환하도록 위치 제공자 설정

기간 동안 새로운 업데이트가 표시되는 속도를 줄여도 배터리 효율성을 향상할 수 있지만 정확성은 저하될 수 있습니다. 절충의 가치는 애플리케이션이 사용되는 방식에 따라 다릅니다. requestLocationUpdates()에서 간격 시간과 최소 거리 변경을 지정하는 매개변수를 늘려서 업데이트 속도를 줄일 수 있습니다.

제공자 집합 제한

애플리케이션이 사용되는 환경이나 원하는 정확성 수준에 따라 네트워크 위치 제공자와 GPS를 모두 사용하는 대신 둘 중 하나만 사용하도록 선택할 수도 있습니다. 서비스 중 하나와만 상호작용하면 배터리 사용량은 줄지만 정확성이 저하될 수 있습니다.

일반적인 애플리케이션 사례

애플리케이션에서 사용자 위치를 가져오려는 데에는 여러 이유가 있습니다. 다음은 사용자 위치를 사용해 애플리케이션을 강화할 수 있는 여러 시나리오입니다. 각 시나리오에서는 정확한 정보를 가져오고 배터리 수명을 절약하는 데 도움이 되도록 위치 수신을 시작하고 중지해야 하는 시기에 관한 모범 사례도 설명합니다.

위치로 사용자 제작 콘텐츠에 태그 지정

사용자 제작 콘텐츠에 위치로 태그를 지정하는 애플리케이션을 만들 수 있습니다. 사용자가 현지 경험을 공유하고 음식점 리뷰를 게시하며 현재 위치로 증가될 수 있는 일부 콘텐츠를 기록한다고 생각해 보세요. 위치 서비스와 관련하여 이러한 상호작용이 일어날 수 있는 방식에 관한 모델이 그림 2에 표시되어 있습니다.

그림 2. 사용자가 현재 위치를 사용할 때 사용자 위치를 가져오고 수신을 중지하는 기간을 나타내는 타임라인

이 타임라인은 코드에서 사용자 위치를 가져오는 방법에 관한 이전 모델(그림 1)과 일치합니다. 최상의 위치 정확성을 위해 사용자가 콘텐츠 작성을 시작할 때 또는 애플리케이션이 시작될 때도 위치 업데이트 수신을 시작한 다음 콘텐츠가 게시되거나 기록될 준비가 되었을 때 업데이트 수신을 중지하도록 선택할 수 있습니다. 콘텐츠를 만드는 일반적인 작업이 얼마나 오래 걸리는지 고려하고 이 기간 동안 위치 예상치를 효율적으로 수집할 수 있는지 판단해야 할 수 있습니다.

사용자가 어디로 갈지 결정할 수 있도록 지원

사용자에게 이동할 위치에 대한 옵션 집합을 제공하는 애플리케이션을 만들 수 있습니다. 예를 들어 주변 음식점, 상점 및 엔터테인먼트 목록을 제공할 수 있으며, 추천 순서는 사용자 위치에 따라 변경됩니다.

이 경우 다음과 같이 하여 이러한 흐름에 맞출 수 있습니다.

  • 새로운 최상의 예상치가 확보되면 추천 재정렬
  • 추천 순서가 안정되면 업데이트 수신 중지

그림 3에서는 이러한 종류의 모델을 표시합니다.

반복적으로 개선되는 위치 데이터를 보여주는 이벤트의 타임라인

그림 3. 사용자 위치가 업데이트될 때마다 동적 데이터 세트가 업데이트되는 기간을 나타내는 타임라인

모의 위치 데이터 제공

애플리케이션을 개발할 때 사용자 위치를 가져오는 모델이 얼마나 잘 작동하는지 테스트해야 합니다. 가장 쉬운 테스트 방법은 실제 Android 지원 기기를 사용하는 것입니다. 하지만 기기가 없어도 Android 에뮬레이터에서 모의 위치 데이터로 위치 기반 기능을 테스트할 수 있습니다. 기기에서 개발자 옵션에 제공된 모의 위치 옵션을 사용하거나 에뮬레이터 콘솔에서 geo 명령어를 사용해 모의 위치 데이터를 애플리케이션에 보낼 수 있습니다.

참고: 모의 위치 데이터를 제공하면 GPS 위치 데이터로 삽입되므로, 모의 위치 데이터가 작동하려면 GPS_PROVIDER에서 위치 업데이트를 요청해야 합니다.

개발자 옵션 사용

기기에서 개발자 옵션과 USB 디버깅을 사용 설정한 다음 모의 위치 앱 선택 옵션을 사용하기 위한 안내를 따르세요.

에뮬레이터 콘솔에서 geo 명령어 사용

명령줄에서 모의 위치 데이터를 보내려면 다음 작업을 하세요.

  1. Android 에뮬레이터에서 애플리케이션을 실행하고 SDK의 /tools 디렉터리에서 터미널/콘솔을 엽니다.
  2. 에뮬레이터 콘솔을 연결합니다.
    telnet localhost <console-port>
  3. 위치 데이터를 보냅니다.
    • geo fix: 수정된 위치 정보를 보냅니다.

      이 명령어는 경도와 위도를 십진수로, 고도(선택사항)를 미터로 받습니다. 예:

      geo fix -121.45356 46.51119 4392
    • geo nmea: NMEA 0183 문장을 보냅니다.

      이 명령어는 '$GPGGA'(수정 데이터) 또는 '$GPRMC'(전송 데이터) 유형의 단일 NMEA 문장을 받습니다. 예:

      geo nmea $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62

에뮬레이터 콘솔에 연결하는 방법을 자세히 알아보려면 에뮬레이터 콘솔 사용을 참조하세요.