Sunday, August 26, 2012

Android Location Startegies



Location Strategies

이 문서는 안드로이드 개발자 노트의 일부를 내맘대로 번역한 것임을 밝힙니다.
출처:http://developer.android.com/guide/topics/location/strategies.html


휴대 단말기 사용자가 자신이 어디에 있는지를 알게하면 너님이 만든 애플리케이션이 더 똑똑해 보이기도 하고 사용자에게 더 나은 정보를 전달할 수도 있지. 위치 정보를 다루는 애플리케이션을 안드로이드에서개발할 때 GPS나 네트워크 위치 제공자(Android's Network Location Provider)를 이용할 수가 있는데, GPS가 더 정확하지만 GPS는 밖에 나가야만 사용할 수가 있어.  게다가 GPS는 배터리를 무쟈게 소모시킬 뿐더러 사용자가 원하는 만큼 재빠르게 위치 정보를 넘겨주지도 않아.

그에 반해, Android's Network Location Provider는 무선 통신 타워나 Wi-Fi 신호를 이용해서 위치를 잡기 때문에 건물 내부에서나 외부에서나 모두 사용할 수가 있고, 반응 속도도 빠르고, 배터리 소모도 적어.

아무튼 사용자의 위치정보를 얻기위해서 위에 언급한 두 가지를 모두 사용할 수도 있고, 그냥 하나만 선택해서 사용할 수도 있다는 건 기억해두자.


Challenges in Determining User Location


휴대 단말기로부터 사용자의 위치정보를 얻어오는건 어찌보면 복잡한 고민이 따르는 일이라고 봐. 왜냐면, 개발자인 너님을 고민에 빠지게 하는 원인들이 많거든.  정보를 받지 못할 수도 있고 기껏 받아온 게 정확하지 않은 정보라면 난감하지 않겠어?

그럼 그런 상황을 초래 하는 이유가 뭐냐. 아래와 같이 정리해 볼 수 있지.

위치 정보를 제공하는 소스가 많다 : GPS, Cell-ID, Wi-Fi가 각각 사용자 위치를 가늠할 수 있는 정보를 주는데 어떤 정보를 사용할지 또 어떤 것이 믿을 만한 정보인지를 결정하는 건  정확도, 속도 배터리에는 어떤 영향을 주는지 사이에서 고민한 후에 결정할 것이라서 그래.

사용자는 계속해서 움직인다 :  사용자는 계속 해서 움직이기 때문에, 개발자인 너님은 이런 상황을 가정하고, 계속해서 반복적으로 자주 다시 계산해야하거든.

정확함이라는 게 뭔지 : 각각의 위치정보 제공자에게 받아온 위치 정보들이라는 게 말이야. 그 제공자들 각자가 원래 가지고 있는 정확도라는게 모두 달라. 일 분 전에 A라는 소스에서 받아온 정보가 방금 B라는 곳에서 받아온 정보보다 더 정확할 수 있다는 거야.

이러한 이유로 사용자에게 신뢰를 줄만한 위치 정보를 얻는다는게 아주 힘들지. 이 문서에서는 그러한 고민을 해결하기 위한 것이야. '어떻게 해야 그나마 신뢰할만한 위치정보를 얻을 수 있을까'에 대한 답이랄까? 너님이 개발하는 앱의 사용자에게 좀 더 정확한 위치정보를 재빠르고 정확하게 제공하려고 하는 너님의 고민에 좋은 아이디어가 되길 바라.


Requesting Location Updates


위에 언급한 문제들을 해결하기 전에 위치정보를 획득하는 방법에 대해서 먼저 알아야겠지? 안다면 다음 소제목으로 패스.

안드로이드에서 사용자 위치정보를 얻는 건 콜백(callback)에 의해 이루어져. LocationManager 클래스의 requestLocationUpdates() 메소드를 호출하면서 '나 위치정보를 업데이트 하고 싶어요'라고 알릴 수 있고, 그럼 업데이트된 위치정보를 LocationListener 에게 전달하지. 이 LocationListener는 너님이 구현해줘야하는 부분인데, 이 인터페이스에는 미리 정의된 몇 개의 콜백 메소드가 있어서 사용자 위치가 변화했을 때나 서비스 상태가 변화할 때 마다 Location Manager가 그 메소드들을 호출하거든.

아래 예제 코드를 봐.
   // 시스템의 위치관리자(Location Manager)에 대한 레퍼런스를 획득한다
   LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

   // 위치정보 업데이트시 반응할 리스너를 정의한다
   LocationListener locationListener = new LocationListener() 
   {
        public void onLocationChanged(Location location) {
             // 네트워크 위치정보 제공자가 새로운 위치정보를 찾게되면 호출되는 메소드임
              makeUseOfNewLocation(location);
        }
        public void onStatusChanged(String provider, int status, Bundle extras) {}
        public void onProviderEnabled(String provider) {}
        public void onProviderDisabled(String provider) {}
   };

   // Location Manager에게 리스너를 등록해준다
   locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
requestLocationUpdate() 메소드의 첫 번째 파라메터는 위치 제공자의 타입이야. 여기서는 무선 통신 타워나 Wi-Fi 기반의 위치 정보를 위한 Network Location Provider로 제공자를 설정한 거지. 두 번째와 세 번째 파라메터는 위치 정보 전달을 위한 최소시간과 최소 거리를 의미해. 여기서는 모두 0으로 설정했으니 가능한 자주 알려달라는 거지. 마지막으로 네 번째 파라메터는 너님이 구현한 Listener 객체를 알려달라는 거고.

GPS 위치 정보 제공자로부터 받고 싶다면 NETWORK_PROVIDER를 GPS_PROVIDER로 바꾸면 되고, 양쪽 모두에서 받고 싶다면 requestLocationUpdates()를 각각에 대해 총 두 번 호출해주면 되지. (물론 LoationManager의 getAllProviders()메소드를 이용해서 모든 provider 정보를 탐색한 후에 몇 몇 조건을 따져서 그 중 가장 나은 하나를 선택할 수도 있어. 이 부분은 이 블로그의 다른 포스팅을 참고해바바바)

Requesting User Permissions


NETWORK_PROVIDER든 GPS_PROVIDER든 위치 정보를 업데이트 받고 싶다면, ACCESS_COARSE_LOCATION이나 ACCESS_FINE_LOCATION 권한을 안드로이드 메니페스트 파일에 추가해 줘야해. 권한이 없으면 위치 정보 업데이트를 요청할 때 멈추게 될끄야.

<manifest ... >
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
</manifest>
그리고, NETWORK_PROVIDER와  GPS_PROVIDER 모두를 사용하려 한다면 ACCESS_FINE_LOCATION 권한 하나만 추가해줘도 돼. 왜냐면, 요게 두 종류 PROVIDER에 대한 권한 모두를 포함하고 있거든, ACCESS_COARSE_LOCATION은 NETWORK_PROVIDER에 대한 권한만을 가지고 있고.


Defining a Model for the Best Performance


위치 기반 애플리케이션이라는 게 이제 뭐 흔하디 흔하지만, " 떨어지는 정확도, 사용자의 잦은 이동, 너무도 다양한 위치 정보 소스, 그 놈의 배터리"  이런 문제거리들을 모두 해결하면서 애플리케이션을 작성하는 게 그리 만만치는 않지. 배터리 소모를 최소화 하면서 사용자가 만족할만한 위치정보를 얻어오려면 먼저 너님은 '위치 정보를 얻는 방법에 대한 효과적인 모델'을 먼저 정의해야 해.  너가 작성한 LocationListener가 허구헌 날 계속 대기모드로 업데이트를 기다린다고 생각해봐. 소개팅한 여자를 미리 수소문해서 예약해 놓은 맛집에 데려가기도 전에 너님의 스마트폰은 방전되어 버릴테고 결국 근처 김밥헤븐에서 다 말라비틀어진 김밥을 먹은 후 그 소개팅녀의 스맛폰에서는 너님의 전화번호가 지워져 버릴거거든. 무섭지?

그래서, 그 모델에는 언제 업데이트에 대한 리스닝을 시작할지, 언제 끝낼지, 언제 수집된 위치정보를 사용할지에 대한 정의도 포함되어야 하지.



Flow for obtaining user location

자 이제 사용자 위치 정보를 획득하는 가장 일반적인 흐름을 볼까?

  1. 애플리케이션을 시작한다
  2. 잠시 뒤, 위치 정보 제공자들로 부터 업데이트 정보를 수신하도록 리스닝을 시작한다
  3. 새로운 위치 정보가 잡히더라도 덜 정확한 정보를 걸러내면서 최상의 위치 장보를 유지한다
  4. 위치 정보 업데이트에 대한 리스닝을 멈춘다
  5. 최상의 위치 정보로 추정되는 정보를 이용한다


아래 그림은 시간대별로, 애플리케이션이 업데이트에 귀기울이는  과정에서 일어나는 일들을 표현해 본 것이야. 자세히 보면 너님이 너님의 앱에서 위치 정보를 사용함에 있어  너님 스스로 결정해야할 시점이 다양하게 존재한다는 걸 볼 수가 있지.



















Deciding when to start listening for updates

애플리케이션이 시작하자마자 리스닝을 시작하게 하거나 아니면 사용자 선택한 특정 시점에만 시작하게 구현하고 싶을텐데, 하나 기억할 것은 위치 정보를 결정하기 꽤 긴 과정을 거치게 되면 그만큼 배터리 수명이 떨어져 버린다는 거다. 그렇다고 짧게 잡으면 좋으냐 그것도 아니지.. 정확한 위치 정보를 잡기가 힘들어 지거든.(도대체 어쩌라구)


Getting a fast fix with the last known location

그래서!! 맨 처음 위치 정보를 잡을 때까지의 시간이 꽤 길어질 수가 있으니까,  더 정확한 위치 정보가 리스너에 전달되기 전까지는 최근에 저장되어 있는 쓰면 어떠냐는 거지. 그런 정보도 getLastKnownLocation(String) 메소드로 알아낼 수가 있거든. 인자로 location provider를 알려주면 해당 제공자의 최근 위치를 가져오는 거지 ( API를 보면 알겠지만, 이 메소드는 단말기기 꺼진 상태에서 전혀 다른 장소로 이동한 상태라도, 꺼지기 이전에 저장된 정보를 알려주게 되므로 받은 정보가 전혀 쓸모 없는 정보일 수도 있다는 거야)

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

Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);


Deciding when to stop listening for updates

획득한 위치 정보가 더 이상 필요없어 질 시점을 결정하는 로직도 역시 애플리케이션에 따라 아주 간단하기도 하고, 아주 복잡해지기도 하지. 중요한 건 위치 정보를 획득하는 시점과 그것이 사용되는 시점 간의 갭이 아주 작아야 추정한 위치 정보의 정확도도 올라간다는 거야.  오랫동안 리스닝을 하게 되면 배터리 소모가 심하다는 건 항상 기억하고 있지? 그래서 너님이 필요하다 생각했던 위치 정보를 얻었다면 리스닝하는 작업을 바로 끝내줘야 해. removeUpdates(PendingIntent) 메소드를 호출해서 말이야.

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


Maintaining a current best estimate

가장 최근에 얻어진 위치 정보가 가장 정확한 값이라고 판단하기가 쉽지만, 사실은 그렇지 않아. 수집된 위치 정보마다 그 정확도가 아주 다양하거든. 그렇기 때문에 가장 정확한 위치 정보를 선별하기 위한 로직을 가져야만 하지. 그런 선별에도 일반화된 기준이 있어야 하겠지? 물론, 애플리케이션에 따라 다르고 실제 필드에서 어떻게 테스트하느냐에 따라 달라지겠지만 말이야.

하지만, 아래와 같은 질문들을 해보며 위치정보의 정확도를 체크해보면 어때?

  • 가져온 위치 정보가 이전에 획득한 추정 위치값들에 비해  뚜렷하게 새로운 정보인가?
  • 위치 정보의 정확도가  이전에  작성된 값들에 비해서는 더 나은가? 아니면 떨어지는가?
  • 새로운 위치 정보를 보내온 제공자는 누구고 이게 이전것들보다 더 믿을만 한가?


위의 체크 사항들을 하나하나 로직으로 작성해 보면 아마도 아래의 소스코드와 같을 거야.

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

/** 어떤 Location 정보가 현재 Location fix보다 더 나은 값인지를 결정하는 메소드
  * @param location  너님이 평가하고 싶어하는 위치 정보
  * @param currentBestLocation  현재의 Location fix, 새로운 값을 이 값과 비교할 것임
  */
protected boolean isBetterLocation(Location location, Location currentBestLocation) {
    if (currentBestLocation == null) {
        // 위치 정보가 없는 것보다는 있는 게 더 나으므로
        return true;
    }

    // 새로 들어온 위치 정보가 현재의 fix값보다 새것인지 오래된 것인지부터 검사
    long timeDelta = location.getTime() - currentBestLocation.getTime();
    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    boolean isNewer = timeDelta > 0;

    // 새로 들어온 위치 정보가 현재 정보보다 2분 이상 더 새로운 것이라면 그냥 그걸 쓰자
    // 2분이면, 우사인 볼트의 경우 달나라까지 갔다올 시간이잖아.
    if (isSignificantlyNewer) {
        return true;
    // 위와 같은 이유로, 현재 정보보다 2분이나 더 오래된 정보라면 무시해도 될거 같아.
    } else if (isSignificantlyOlder) {
        return false;
    }

    // 이제는 새로운 정보가 현재의 정확도보다 더 나은건지 아닌지를 검사해보자
    int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
    boolean isLessAccurate = accuracyDelta > 0;
    boolean isMoreAccurate = accuracyDelta < 0;
    boolean isSignificantlyLessAccurate = accuracyDelta > 200;

    // 혹시, 동일한 Provider에서 제공한 정보인지도 알면 판단하기가 더 쉽겠지?
    boolean isFromSameProvider = isSameProvider(location.getProvider(),
            currentBestLocation.getProvider());

    // 두 정보를 시간과 정확도를 함께 비교하면서 판단해본다
    if (isMoreAccurate) {
        return true;
    } else if (isNewer && !isLessAccurate) {
        return true;
    } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
        return true;
    }
    return false;
}

/** 이 메소드는 인자로 넘겨진 두 개의 provider가 동일한 것인지를 판단한다  */
private boolean isSameProvider(String provider1, String provider2) {
    if (provider1 == null) {
      return provider2 == null;
    }
    return provider1.equals(provider2);
}


Adjusting the model to save battery and data exchange

간단한 애플리케이션을 만들어서 테스트 해보면, 위의 코드만으로는 뭔가 좀 부족하다 싶을 거야. 약간의 조정이 필요하지. 아래에 적어놓은 두 가지 상황을 비교해 가면서 두 요건들 사이에서 밸런스를 찾아가야 할거야.

위치 정보를 받아들일 범위를 좁힌다.
신호를 받아들이는 범위를 좁혀버리면 이전보다 더 적은 수의 GPS와 네트워크 위치 서비스 하고만 교신하면 될 뿐더러 배터리 수명도 유지하기가 편할거야. 하지만,  그만큼 좋은 정보를 찾기위한 리소스가 부족해지기도 하지

위치 제공자한테 더 적은 빈도로 업데이트 정보를 보내라 한다.
새로운 업데이트를 보내는 빈도를 줄여버리면 배터리 효율을 개선할 수가  있지만, 정확도 측면에서는 손해를 볼 수 밖에 없지. 배터리 효율이냐 정확도 사이에서의 판단은, 어떤 앱에서 어떻게 사용되느냐에 따라 많이 달라지겠네 그치? 업데이트 빈도를 줄이려면 requestLocationUpdates() 메소드의 interval time, minimun distance 값을 올려주면 돼.

Provider의 종류를 제한해버린다.
앱을 사용하는 환경에 따라 정확도를 요구하는 수준도 달라지잖아. 그런 상황에 맞춰서,  네트워크 위치 제공자나 GPS 둘 중 하나만을 쓸 수가 있을거야. 그런 서비스들 중 하나만 사용함으로써 (정확도 감소라는 비용을 지불해야할 상황이 있을 수도 있지만 - 반드시 수반되는 비용은 아니라는 의미로 이해해) 배터리 사용을 감소시킬 수가 있지.



Common application cases

너님이, 너님의 앱에서 위치 정보가 필요한 이유야  내 월급이 한달에 1억은 되어야하는 이유 만큼이나 많겠지만, 아래의 시나리오를 잘 참고한다면 아마도 왠만큼은 너님의 앱이 좀 더 나은 모습을 변화하도록 위치 정보를 잘 활용할 수 있을 거라 생각해. 각각의 시나리오는 너님이 리스닝을 언제 시작하고 끝내야 할지에 결정하는데 좋은 예가 될테고, 질 좋은 위치 정보를 획득하면서도 배터리 사용은 더 효율적으로 하는 앱을 위한 길라잡이가 될 수도 있겠지.


사용자가 만드는 컨텐츠 내에 위치 정보가 태그로 삽입되는 경우

여행 정보를 만든달지, 음식점에 대한 리뷰를 포스팅한달지 이런 경우의 시나리오야. 이런 경우에는 사용자가 컨텐츠를 작성하는 동안(트위터를 작성하는 등의)에 위치 정보를 결정하면 되겠지? 컨텐츠 작성을 시작할 때나 해당 앱이 시작했을 때 리스닝을 시작하면 될테고, 글을 저장하거나 포스팅하면 리스닝을 끝내야겠지.  물론, 그 작업이 이루어지는 평균적인 시간에 대한 예측, 그리고 그 사이에 리스닝을 지속한다면 초래될 여러 조건들도 너님이 판단해야하는 건 당연한 거고...

주변의 레스토랑이나 쇼핑센터를 찾고자 한다면?

추천 항목을 제공하면서도 사용자가 이동을 하면 추천 항목도 당연히 변화해야 하겠지?
리스닝을 시작한 이후로 조금 더 정확한 위치 정보를 획득할 때마다, 거기에 맞는 추천 항목리스트도 업데이트 해줘야 할거야. 그러다, 추천 항목이 안정화되면 리스닝도 멈춰 버리면 되겠지
그런 앱에서 사용자가 관심 있는건 주변의 추천 항목들이지, 자기 자신의 위치가 아니잖아.




















원문에는 Mock 위치 정보를 사용하는 법이 이후로도 설명되어 있지만, 그 정도는 금방 알 수 있는 부분이라서 여기서 난 끝내고자 한다. 도움이 되었기를.......끝!









No comments:

Post a Comment