본문 바로가기

개발 코딩 정보 공유/개발후기

GPS 관련 기능 사용정리 (location manager, fused location provider)

안녕하세요. 블루스웨터 소프트 입니다.

사용자의 위치정보를 활용하여 기능 구현을 하는것은 이제 흔한 일이 되었습니다. 

진행한 프로젝트를 기반으로 내용을 정리해보겠습니다. 

최근에 작업에 참여하게된 프로젝트는 골프 관련 앱으로써 사용자의 위치 정보가 굉장히 중요한 요소 입니다. 

위치 정보를 활용하여 라운딩 정보를 기록하고 모니터링, 분석 하는 앱 입니다.

위치 정보를 사용하지 못한다면 앱 기능의 절반은 사용 불가라고 봐도 무방할 정도 입니다. 

때문에 다시한번 위치정보에 대한 내용을 정리하는것이 필요했습니다.

 

GPS의 이해

GPS(Global Positioning System) 는 미국 국방부에서 개발하여 현재는 군사, 공공, 민간 기업, 일반 등 다양하게 활용되어지고 있습니다. GPS의 연간 유지보수 비용은 약 7억5천만달러 입니다만 현재는 무료로 사용되어지고 있습니다. 😱 

 

우리의 일상속에서 GPS 의 기능을 찾아보면 이미 많은곳에서 쓰이고 있습니다. 

현재 위치를 기준으로 배달을 시켜 먹거나, 택시를 부르거나, 중고거래를 하거나 굉장히 많은 일들이 이미 GPS 의 혜택을 받고 있습니다. 

GPS 사용 구현에 있어서 단연 중요한것은 위치의 정확도 입니다. 위치가 정확하지 않다면 해당 개발 자체가 무의미 합니다. 만약 이런경우 기획적으로 방향을 틀어서 개발할수 밖에 없겠습니다. 다행히도 안드로이드에서는 정확한 GPS 활용을 위해 몇가지 방법을 제시합니다.

 

Location Manager 사용

Location Manager는 정말 간단하게 현재 위치 정보를 수신받고, 리스너를 통해 이벤트를 핸들링 할수 있습니다.

우선 권한을 추가해주겠습니다. Manifest에 아래의 위치 정보 관련 권한을 추가 합니다.

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

 

system service 로 location manager를 지정하고 필요한 조건을 구성 합니다.

val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
var location: Location? = null

//조건을 지정한다
val criteria = Criteria().apply {
    accuracy = Criteria.ACCURACY_COARSE
    isCostAllowed = false
}

val provider = locationManager.getBestProvider(criteria, false) //조건을 사용하여 위치정보를 획득

try {
    location = locationManager.getLastKnownLocation(provider) //최근 위치 정보를 획득함
    
    //위치정보 이벤트 리스너
    val locListener = object : LocationListener {
        override fun onLocationChanged(location: Location) {
        }

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

        override fun onProviderEnabled(provider: String) {
        }

        override fun onProviderDisabled(provider: String) {
        }
    }
    
    location?.let {
        Log.d("LocationManager", "=== ${it} ===")
    } 

	//위치 정보를 주기적으로 업데이트 받는다
    locationManager.requestLocationUpdates(provider, 500L, 1f, locListener)
} catch (e: Exception) {
    Log.e("locationManager", e.message ?: "=== Unknown Exception ===")
}

 

이렇게해서 간단하게 위치 정보를 가져올수 있습니다만... 더이상 Location Manager는 사용할 필요가 없게되었습니다. 구글에서 Fused Provider 를 사용하라고 권장하고 있기 때문입니다. (Location Manager 의 내부메서드 들도 deplicated 되었습니다.) 

 

Fused Provider 를 이용하면 GPS, NETWORK 등의 복합적인 방법을 통해 좀더 정확한 위치 정보를 획득하고, 낮은 배터리 사용률을 가지며, 최신의 업데이트를 지원하고, geo fencing, mapapi 등의 관련 서비스로의 연계가 쉽고 편리합니다. 이모든 장점을 두고 굳이 Location Manager 를 사용할 이유가 없겠지요 😓

 

여기서 잠깐 Android 에서 위치정보를 획득하는 방법에 대해서 정리해보겠습니다. 

크게 GPS를 활용하는 방법과 Network (통신사 cell point , wifi ap 등) 를 활용하는 방법이 있습니다.

 

위치정보 제공자의 이해

GPS Provider

GNSS(Global Navigation Satellite System) 위성 항법 기술을 활용한 위치 정보를 수신받습니다. 당연히 실내에 있거나 혹은 지하, 산속 등에서는 굉장히 제한적입니다.

 

Network Provider

통신망 제공자나 WIFI AP 등을 통해 위치정보를 계산하는 방법 입니다. 실내에서도 위치정보를 어느정도 파악할수 있습니다. 다만 Cell 타워 간격이나, WIFI ap 의 유무 등으로 인해 변수가 존재합니다. 때문에 안드로이드는 내부에서 지속적으로 사용자의 네트워크 정보를 수시로 가져갑니다.😨

 

이 두가지를 섞어주면 참 좋을텐데... 그래서 나온게 바로 Fused Location Provider 입니다. 

이렇게 최신의 기술이 응집된? Fused Location Provider 를 구현해보겠습니다. 굉장히 간단합니다.

 

Fused Location Provider를 사용한 사용자의 마지막 위치 가져오기

google-play service 에서 제공하는 play-services-location 를 추가합니다.

apply plugin: 'com.android.application'

...

dependencies {
    implementation 'com.google.android.gms:play-services-location:21.1.0'
}

 

FusedLocation을 선언하고 onCreate에서 초기화 합니다.

private var fusedLocation: FusedLocationProviderClient? = null

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    fusedLocation = LocationServices.getFusedLocationProviderClient(this)
}
fusedLocationClient.lastLocation
	.addOnSuccessListener { location : Location? ->
    location?.let{
    	Log.d("", "=== ${location} ===")
    }
    
}

lastLocation에 리스너를 등록하고 위도 경도 등의 정보를 가지고 있는 Location 정보를 전달받게 됩니다. 그런데 왜 Location? 일까요? 특정 아래의 이유에 의해서 null 이 들어올수 있기 때문입니다. 

  • 기기 설정에서 위치가 사용 중지되어 있습니다. 위치를 사용 중지하면 캐시도 지워지므로 이전에 마지막 위치를 가져온 경우에도 결과는 null일 수 있습니다.
  • 기기에서 위치를 기록한 적이 없습니다. 예를 들면 새 기기이거나 기본 설정으로 복원된 기기일 수 있습니다.
  • 기기의 Google Play 서비스가 다시 시작되었으며 서비스가 다시 시작된 후 위치를 요청한 활성 통합 위치 정보 제공자 클라이언트가 없습니다. 이러한 상황이 발생하지 않도록 새 클라이언트를 만들고 직접 위치 업데이트를 요청할 수 있습니다. 자세한 내용은 위치 업데이트 받기를 참고하세요.

주의할점은 가져온 위치정보값이 반드시 최신의 값은 아닐수 있다는데 있습니다.  마지막으로 기록된 위치가 현재 나의 위치라고 볼수는 없기 때문입니다. (현재 이동한 위치가 기록이 안됐거나) 그래서 정확한 현재위치를 표기하기 위해서는 다른 방법으로 구현해야 합니다.

 

Fused Location Provider를 사용한 현재 위치 가져오기

getCurrentLocation() 메서드를 활용하는 것입니다. 최신의 위치를 가져오는 가장 좋은 방법입니다. requestLocationUpdates 를 활용하여 위치를 업데이트 하고 가져오는것보다 안정적 입니다. requestLocationUpdates를 사용하여 중단시키지 않으면 배터리 등의 소모가 많을수 있습니다.

 

Fused Location Provider를 사용한  위치 업데이트 등록

private var locationCallback: LocationCallback? = null

override fun onCreate(savedInstanceState: Bundle?) {
	//...
    
    locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult?) {
            locationResult ?: return
            for (location in locationResult.locations){
            	Log.d("locationCallBack", "=== location: $location ===")
            
            }
        }
    }
    
    //...
}

override fun onResume() {
    super.onResume()
    startLocationUpdates()
}

private fun startLocationUpdates() {
    fusedLocation.requestLocationUpdates(locationRequest,
            locationCallback,
            Looper.getMainLooper())
}

 

업데이트 요청을 시작했다면 중지하는 로직도 필요합니다. 화면이 전환되거나, 앱이 백그라운드로 들어갔다면 위치 정보 획득 중단을 요청할수 있습니다.

override fun onPause() {
    super.onPause()
    stopLocationUpdates()
}

private fun stopLocationUpdates() {
    fusedLocationClient.removeLocationUpdates(locationCallback)
}
override fun onResume() {
    super.onResume()
    if (requestingLocationUpdates) startLocationUpdates()
}

 

화면 전환시 위치정보를 업데이트 하려면 requestingLocationUpdates 를 활용해서 onResume 에서 startLocationUpdates 합니다.

이러한 복잡성 때문에 간단한 현재위치 조회의 경우 getCurrentLocation을 통해 가져오라고 권장하고 있습니다. 

 

마무리글

사실 위치 정보를 활용한 앱들은 이전부터 꽤 많이 작업했습니다만, 정리를 안해둔 탓에 매번 찾아보고 다시 공부해야 했습니다. Location Manager 와 Fused Location Provider 에 대해서 이번 글을 작성 하며 지식을 정리하는 계기가 되어 기쁜 마음입니다. 이제는 거의 사용되지 않는 Location Manager의 구현을 작성한 이유는 사용되지 않는다면 기존의 히스토리를 알아둘 필요가 있기 때문입니다. 왜 더 좋은지 왜 사용해야 되는지에 대한 이해가 쉽게 되기 때문입니다. 누군가 참고를 위해 이글을 읽는다면 앞 내용만 보고 Location Manager 를 사용하지 않기를 바랍니다😓

 

 

 

문서참고

https://stackoverflow.com/questions/6775257/android-location-providers-gps-or-network-provider

https://manorgass.tistory.com/82

https://developer.android.com/reference/android/location/LocationManager

https://developers.google.com/location-context/fused-location-provider?hl=ko

https://developer.android.com/training/location/retrieve-current?hl=ko

https://developer.android.com/training/location/request-updates?hl=ko