티스토리 뷰

이번 포스팅에서는 Android의 위치 산출방법에 대해 살펴보고 매우 정확한 위치정보를 제공해주는 FusedLocationProvider의 개념과 사용법에 대해 이야기합니다.

Android 기기의 위치정보를 획득하기 위해서는 위험 수준(dangerous level)의 권한인 ACCESS_COARSE_LOCATION 혹은 ACCESS_FINE_LOCATION 권한을 획득해야 합니다. 권한 관련 내용은 본 포스팅에서는 다루지 않으니, 관련 정보가 필요하신 분은 아래 포스팅을 참고해주세요. 

Android 권한 파헤치기 글 보기

 

 

#1> 개요


잠시 제 이번 주 일상에 대해 이야기를 해볼까 합니다.

  1. 커피머신이 필요해 당근 마켓을 통해 같은 동네에 살고 있다고 인증된 어떤분과 중고 거래를 했습니다.
  2. 택시를 탈일이 생겨 카카오 택시를 앱을 통해  통해 택시를 불러 목적지까지 편안하게 이동했습니다. 제 위치를 입력하지 않았는데 기사분이 정확히 찾아오셨습니다.
  3. 지인들과 맛집으로 알려진 식당에서 맛있는 저녁식사를 했습니다. 골목에 있는 가게인데 네이버 지도 길안내 서비스를 통해 정확히 찾아갔어요.

위 사례들은 우리 삶 속에서 자주 겪게 되는 상황들입니다. 우리는 당근마켓 앱을 통해 내 동네를 인증하거나, 카카오택시가 내 위치까지 정확히 찾아오거나, 네이버지도에 내 위치가 정확히 표시되는 것들에 전혀 놀라워하지 않습니다. 내 손가락이 내 생각대로 움직이듯이, 정확한 위치정보를 제공받는 것이 아주 당연한 일로 여겨지는 세상에 살고 있기 때문입니다.

 

하지만 개발자 입장에서 이런 위치기반 서비스를 구현하려고 한다면, 정확한 위치를 제공하는 것이 복잡하고 어렵게 느껴지는데, 높은 정확도의 위치정보를 제공하기 위해서는 고려해야 할 것들이 많기 때문입니다. 기본적으로 Android OS에서 제공하는 위치정보 서비스를 사용하는 경우, 위치가 튀는 경우도 발생하고 건물 내부에서의 움직임을 정확히 파악할 수 없습니다. 또한 통신사 기지국 Cell의 가장자리 부분에서 위치가 정확하지 않은 경우도 발생합니다.

통신사 Cell 이미지. 출처: 위키피디아

배터리 효율도 고민의 대상입니다. 가장 높은 정확도의 위치를 계속해서 요청하면 배터리 사용량이 증가하기 때문입니다.

 

이런 개발자들의 고민들을 해결해주려는 걸까요? 구글은 2019 Google I/O 행사에서 FusedLocationProvider에 대한 스피치를 진행하였습니다. 발표 내용을 들어보면 알 수 있듯이 이 통합 위치 제공자를 통해 위에서 언급한 위치기반 서비스 구현 시 고민해야 했던 대부분의 문제들을 해결할 수 있습니다. 뿐만 아니라 이 녀석, 건물 내부에서의 움직임까지 정확히 파악합니다. 그래서 최근 Android 공식 사이트에서는 위치정보 획득 시 FusedLocationProvider를 사용할 것을 권장하고 있습니다.

 

새로운 위치 제공자에 대한 설명에 앞서 Android OS에서 제공하는 위치정보 서비스(Location Service)가 위치정보를 제공하는 원리와 한계에 대해 살펴보도록 하겠습니다.

 

 

#2> 안드로이드의 위치 서비스 (Android Location Service)


Android OS 에서 제공하는 Location Service는 2가지의 위치 제공자(Location Provider)에 위치정보를 요청할 수 있습니다.

 

1. GPS_PROVIDER

GNSS(Global Navigation Satellite System) 위성을 통해 위치정보를 파악합니다. 때문에 위성신호가 잡히지 않는 건물 내부에서는 위치정보 업데이트가 되지 않습니다.

 

2. NETWORK_PROVIDER

통신사 기지국의 Cell 타워와 근처 WiFi Access Point 정보를 통해 위치를 파악합니다. 참고로, 셀타워나 WiFi AP의 위치정보는 Google에서 지속적으로 수집 및 업데이트를 하고 있습니다.

 

GPS Provider는 GPS 위성 신호를 통해서 위치정보를 파악합니다. 이런 위성을 통한 위치 파악 기술은 잘 알려져 있지만 Network Provider는 어떻게 기지국 Cell이나 와이파이와 같은 네트워크 정보만으로 위치를 알 수 있는 걸까요?

 

Android 기기는 인터넷에 연결된 경우 Google 서버에 주기적으로 위치정보 및 인터넷 연결 정보를 업로드합니다. 그리고 이렇게 수집된 정보들을 기반으로 구글 서버에서는 나름의 네트워크 별 위치정보 테이블을 만들고 위치정보를 알려줍니다. 그리고 위치정보 수집에 대해서는 우리가 알게 모르게 이미 동의를 한 상태입니다. 세상에 공짜는 없는 법이죠.

 

위치정보 수집에 대한 안내문구

 

예를 들어 Cell A 구역의 WiFi B에 연결한 기기들의 마지막 위치정보가 L1인 경우가 3건, L2인 경우가 2건인 경우, Network Provider는 "이 와이파이에 연결했던 기기의 마지막 위치 정보가 L1인 경우가 제일 많았으니까 L1일 거야!" 라며 위치정보를 L1으로 내려주는 식입니다.

따라서 Network Provider의 경우 건물 내부에 있는 경우에도 위치정보를 제공하지만, GPS Provider의 경우 위성신호가 잡히지 않기 때문에 정보를 제공하지 못합니다.

 

건물 내부에서 위치정보 요청 시 GPS Provider에서는 위치정보를 획득하지 못한다.

하지만 밖으로 나가면 얘기가 달라집니다. 외부에서 GPS_PROVIDER는 높은 정확도로 움직임을 업데이트하지만 NETWORK_PROVIDER는 AP에서 AP로 순간이동(?)을 합니다.

 

 

#3> FusedLocationProvider(통합 위치 제공자) 란?


FusedLocationProvider는 LOCATION_SERVICE에서 해결하지 못한 GPS와 NETWORK 위치 제공자의 간극을 매워주기 위해 출시되었습니다. 뿐만 아니라 빌딩 섬에서의 GPS 외곡과 같은 현상도 보정해줍니다.

 

건물 내/외부에서의 각 위치제공자 별 정확도 그래프
건물 내/외부에서 모두 좋은 정확도를 보여주는 FusedLocationProvider의 그래프

그리고 단순히 레거시 위치 제공자에만 의존하지 않고 가속도 센서, 자이로스코프, 자기장 센서 등의 메타정보를 활용해 사용자가 위치할 것이라고 예측되는 위치를 계산합니다.

 

건물 내부에서 위치정보를 파악하는 FusedLocation의 알고리즘을 가시화한 모습

위 사진에서 큰 회색원은 마지막으로 수집된 GPS 정보이고, 파란색 줄은 사용자의 움직임을 추적한 선입니다. 건물 내부에 들어갔다고 판단되면 똑똑한 FusedLocationProvider는 활용할 수 있는 모든 정보(WiFi AP, 기타 센서 등)를 종합해 사용자가 있을 것으로 예상되는 위치를 계산합니다.

 

이렇게 계산된 위치들이 위 사진에서 작은 점들로 표시되어 있습니다. 점에 꼬리처럼 붙어있는 것은 예측되는 사용자의 방향입니다. 그리고 예측된 위치의 밀도가 가장 높은 좌표를 최종 사용자 위치로 판단합니다.

 

어떤가요? 정말 똑똑하죠? FusedLocationProvider에 대한 더 자세한 내용이 궁금하시면 아래 영상을 참고하세요.

 

Google I/O 19 에서 발표한 FusedLocationProvider에 대한 스피치 영상

 

 

#4> FusedLocationProvider 를 사용한 위치정보 획득 방법


위에서 살펴본 것처럼 FusedLocationProvider가 위치를 계산하는 알고리즘이 매우 정밀한데 반해, 개발자가 위치를 획득하는 방법은 아주 간단합니다.

그저.. 빛...

이번 챕터에서는 FusedLocationProvider를 통해 마지막으로 알려진 위치를 얻는 방법과, 원하는 interval 주기로 위치 업데이트 정보를 요청하는 방법을 살펴보도록 하겠습니다. 코드만 봐도 이해가 될 것 같은 부분에는 딱히 설명을 하지 않았습니다.

 

프로젝트 종속성 설정

통합 위치 제공자(Fused Location Provider)는 Google Play Service의 Location API에서 제공합니다. 따라서 아래와 같이 프로젝트에 Google Play Service 종속성을 추가해야 합니다.

 

  • 프로젝트 수준의 Gradle 파일의 repositories 속성에 아래 저장소 추가
    maven { url "[<https://maven.google.com>](<https://maven.google.com/>)"}

  • 모듈 (Application) 수준의 Gradle 파일에 아래 종속성 추가
    implementation 'com.google.android.gms:play-services-location:17.1.0'

통합 위치 제공자 초기화

private lateinit var fusedLocationProviderClient: FusedLocationProviderClient

private fun initLocationClient() {
    fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)

    val locationRequest = LocationRequest.create()?.apply {
        interval = 1000
        priority = LocationRequest.PRIORITY_HIGH_ACCURACY
    }

    val builder = LocationSettingsRequest.Builder()
        .addLocationRequest(locationRequest!!)
    val client = LocationServices.getSettingsClient(context)
    val task = client.checkLocationSettings(builder.build())
    task.addOnSuccessListener {
        Log.d(TAG, "location client setting success")
    }

    task.addOnFailureListener {
        Log.d(TAG, "location client setting failure")
    }
}

우선 원하는 형태의 Location Request를 생성하고, 해당 설정으로 위치 요청이 가능한지 확인해야 합니다. 위 코드에서 Location Request에 사용된 옵션에 대한 설명은 다음과 같습니다.

 

  • interval: 업데이트 간격을 ms 단위로 설정합니다.
  • priority: 필요한 정확도를 설정하는 값으로 아래 4가지로 분류됩니다.
    1. BALANCED_POWER_ACCURACY
      도시 블록 내의 위치 정밀도 요청. 정확도는 대략 100미터. Wi-Fi 정보와 휴대폰 기지국 위치를 사용할 수 있음. 대략적인 수준의 정확성으로 전력을 비교적 적게 사용함.
    2. HIGH_ACCURACY
      가장 정확한 위치를 요청. 이 설정을 사용하면 위치 서비스가 GPS를 사용하여 위치를 확인할 가능성이 높음.
    3. LOW_POWER
      도시 수준의 정밀도 요청. 대략 10킬로미터의 정확성. 아주 대략적인 수준의 정확성으로 전력을 더 적게 소비함.
    4. NO_POWER
      전력 소비에 별다른 영향을 미치지 않으면서 사용 가능한 경우 위치 업데이트를 수신하려면 이 설정을 사용. 해당 설정을 사용할 경우 앱에서 위치를 트리거하지 않고 다른 앱에서 트리거한 위치 정보를 가져다 씀.

위 코드에서는 priority를 HIGH_ACCURACY로 설정했습니다. 따라서 높은 품질의 위치정보는 기대할 수 있지만, 배터리 효율은 기대하기 어렵습니다.

 

마지막으로 알려진 위치 요청

아래 코드 스니펫은 마지막으로 알려진 위치 정보를 요청하는 코드입니다. 권한체크는 필수입니다.

fun requestLastLocation() {
  if (ActivityCompat.checkSelfPermission(
          context,
          Manifest.permission.ACCESS_FINE_LOCATION
      ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
          context,
          Manifest.permission.ACCESS_COARSE_LOCATION
      ) != PackageManager.PERMISSION_GRANTED
  ) {
      return
  }
  fusedLocationProviderClient.lastLocation
      .addOnSuccessListener { location ->
          listener.onLocationUpdated(location)
      }
}

 

위치 업데이트 요청

아래 코드 스니팻은 interval(ms) 간격으로 주기적인 위치정보 업데이트를 요청하는 코드입니다.

private lateinit var locationCallback: LocationCallback

private fun initLocationCallback() {
    locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult?) {
            locationResult ?: return
            for (location in locationResult.locations) {
                listener.onLocationUpdated(location)
                break
            }
        }
    }
}

fun startLocationUpdates() {
    if (ActivityCompat.checkSelfPermission(
            context,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
            context,
            Manifest.permission.ACCESS_COARSE_LOCATION
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        return
    }
    val locationRequest = LocationRequest.create()?.apply {
        interval = 1000
        priority = LocationRequest.PRIORITY_HIGH_ACCURACY
    }
    fusedLocationProviderClient.requestLocationUpdates(
        locationRequest,
        locationCallback,
        Looper.getMainLooper()
    )
}

 

 

마치며


오늘은 통합 위치 제공자(Fused Location Provider)를 통한 위치정보 획득 방식에 대해 알아보았습니다. 구글이 Android OS의 Location Service를 업데이트하는 방식이 아닌 Google Player Service 중 하나인 FusedLocationProvider 사용을 권고하는 이유는 OS 버전에 종속되지 않고, 지속적으로 업데이트되는 위치 계산 알고리즘이 적용된, 최상의 품질의 위치정보를 제공하겠다는 의지로 보여집니다.

 

끝까지 읽어주셔서 감사합니다. 좋은 하루 보내세요!

 

본 포스팅에 사용된 코드 스니팻의 전체 코드는 아래 스니팻을 통해 확인 가능합니다.

 

 

FusedLocationProvider.kt

GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

 

LegacyLocationProvider.kt

GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

하트와 댓글작성은 필자에게 큰 힘이 됩니다!
댓글