💡 Intro
현재 진행하고 있는 프로젝트는 위치를 기반으로 하는 프로젝트다. 때문에 필수로 위치 정보를 가져와야 한다. 네이버맵 혹은 카카오맵에서 반환되는 값으로부터 간편하게 좌표를 받아올 수 있다. 네이버맵 혹은 카카오맵을 사용하는 액티비티에서 API를 이용하여 가져오는 방법이다. 하지만 우리는 지도를 사용하지 않는 뷰에서도 위치 좌표가 필요했고, 이 때는 위치 정보를 어떻게 받아올 수 있을까 고민했다.
현재 해결한 방법은 LocationManager를 이용한 방법이며 글을 정리하려고 더 자세하게 찾아본 결과 google-play service를 사용한다면 더 정확하고 효율적으로 위치 정보를 받아올 수 있을 것으로 예상된다. 일단은 사용했던 LocationManager에 대해 기록하고, 4주차 데모데이 혹은 레벨 4에 더욱 고도화 하면서 위치 정보에 대한 내용도 고도화 할 생각이다.
❗️ permission
디바스의 위치 정보를 얻기 위해서는 다음의 두 가지 퍼미션을 추가해야 한다.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
ACCESS_FINE_LOCATION : 정확한 위치에 접근할 수 있도록 허용
ACCESS_COARSE_LOCATION : 대략적인 위치에 접근할 수 있도록 허용
위쪽 사진은 ACCESS_COARSE_LOCATION 만 요청했을 경우, 아래쪽 사진은 ACCESS_COARSE_LOCATION 과 함께 ACCESS_FINE_LOCATION 까지 함께 요청하는 경우다.
참고로 적으면 requestPermission을 통해 요청하는 경우 위처럼 시스템 권한 상자가 뜨는데 사용자가 두 번 거절하면 그 이후로는 뜨지 않는다. 그래서 필히 이 앱이 권한을 가지고 있는지 확인하고 갖고 있지 않는 경우 사용자에게 요청하는 로직이 있어야한다.
(퍼미션을 요청하는 것은 주된 내용이 아니니 생략하도록 하겠다.)
❓ LocationManager
위치 정보를 획득하기 위해 다음과 같은 코드를 통해 LocationManager를 얻어 사용할 수 있다.
val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
이렇게 획득한 LocationManager의 메서드를 통해 위치 정보를 가져올 수 있다. LocationManager에게 위치 정보를 요청하는 함수를 사용할 때 파라미터로 넣어줘야 하는 값이 있는데 이것이 위치 정보 제공자(LocationProvider)임을 기억하자.
기본적으로 제공되는 위치 정보 제공자는 다음의 메서드로 확인할 수 있다.
locationManager.allProviders
현재 내가 구동하고 있는 에뮬레이터는 API Level33이고 확인해본 결과 위처럼 [passive, network, fused, gps] 총 네가지가 나온다. 각 위치 정보 제공자에 대해 알아보면 다음과 같다.
- Passive : 앱이 위치 정보를 직접 요청하지 않은 경우에 사용된다. 다른 앱이나 시스템에서 위치 정보를 업데이트하면, Passive Provider는 해당 정보를 제공한다. 일반적으로 이 Provider를 사용하여 배터리 수명을 아끼고 다른 앱이나 서비스가 제공하는 위치 정보를 활용할 수 있다.
- Network : 기지국 또는 Wi-Fi 네트워크를 활용하여 위치 정보를 제공한다. GPS보다 정확도는 낮지만, 배터리 수명을 덜 소모한다. 도시나 밀집 지역에서 정확한 위치 정보를 제공하며, 실내에서도 상대적으로 좋은 성능을 보인다.
- Fused : Google Play Services를 통해 제공되는 고급 위치 제공자다. 기기의 다양한 센서들 (GPS, Wi-Fi, 기지국 등)을 통합하여 가장 정확한 위치 정보를 제공한다. 기기의 센서 데이터와 위치 데이터를 통합해 정확한 위치를 파악하므로 다른 Provider에 비해 더 정확한 결과를 얻을 수 있다. 이는 Google Play Services가 설치되어 있어야 사용할 수 있다.
- Gps : 가장 정확한 위치 정보를 제공하지만, 실내나 건물 밀집 지역에서는 정확도가 떨어질 수 있다. 주로 야외에서 사용되며 GPS가 잘통하는 지역에서 높은 정확도를 보인다. GPS 사용 시 배터리 소모량이 상대적으로 높을 수 있다.
❓Criteria
위치 정보 제공자를 지정하지 않고, 조건을 명시하여 위치 정보 제공자를 획득하기 위해서는 Criteria 클래스를 사용할 수 있다. 조건을 명시하여 해당 조건에 맞는 최적의 위치 정보 제공자를 이용하기 위해 Criteria의 setter() 메서드를 사용할 수 있다. 각 setter() 메서드에 대해 알아보면 다음과 같다.
- setAccuracy(int accuracy) : 정확도에 대한 조건
- setAltitudeRequired(boolean altitudeRequired) : 고도 제공
- setBearingRequired(boolean bearingRequired) : 방향 제공
- setCostAllowed(boolean costAllowed) : 비용이 드는 것을 허용할 것인지
- setSpeedRequired(boolean speedRequired) : 속도 제공
- setPowerRequirement(int level) : 전원 소모량 조건
setter() 메서드를 사용하여 조건을 명시한 부분은 그대로 사용되지 않고, 각 조건에 부합하는 위치 정보 제공자를 찾아준다. 만약 정확히 일치하는 위치 정보 제공자가 없다면 최적의 위치 정보 제공자를 반환해준다.
val criteria = Criteria().apply {
accuracy = Criteria.ACCURACY_FINE
isAltitudeRequired = false
isBearingRequired = false
isSpeedRequired = false
isCostAllowed = true
powerRequirement = Criteria.POWER_LOW
}
val locationProvider = locationManager.getBestProvider(criteria, true)
이 조건을 통해 가져온 위치 정보 제공자는 fused 다. 이렇게 가져온 위치 정보 제공자를 사용하는 방법은 다음과 같다.
val locationProvider = locationManager.getBestProvider(criteria, true)
val location = locationManager.getLastKnownLocation(locationProvider)
❓ 위치 정보 획득
위치 정보 제공자를 어떤 것으로 사용할지 결정했다면 이를 통해 스마트폰의 위치를 획득할 수 있다.
이 때 사용하는 메서드는 LocationManager의 getLastKnownLocation(String provider)이다. 만약 위치 값을 얻지 못하는 경우 반호나 값은 null이 되고, 위치 값을 얻었다면 획득한 위치와 관련된 정보를 가지고 있는 Location 객체에 담아 반환한다.
이 메서드는 이름에서도 유추할 수 있듯이 마지막으로 알려진 위치를 가져온다. 여기서 중요한 것은 마지막으로 알려진 위치가 없는 경우 null을 반환하고, 반환되는 위치가 경우에따라 오래된 위치일 수도 있다고 한다. 그래서 공식문서에서도 반환된 위치가 오래된 위치인지 확인하라고 한다.
(API level 1부터 지원하던 API를 발견해서 굉장히 신기하다…)
val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
if(location != null) {
val accuracy = location.accuracy
val latitude = location.latitude
val longitude = location.longitude
}
반환받은 Location 객체로부터 얻을 수 있는 정보는 다음과 같다.
- getAccuracy() : 정확도
- getAltitude() : 고도
- getBearing() : 방위
- getLatitude() : 위도
- getLongitude() : 경도
- getProvider() : 위치 정보 제공자
- getSpeed() : 속도
- getTime() : 획득 시간(UTC)
- getElapsedRealtimeNanos() : 해당 위치가 얻어진 시점부터 현재까지 경과한 시간을 나노초 단위로 반환한다.(nanoseconds)
그리고 앞에서 말했던 것처럼 이 위치 정보가 언제 생성된 것인지 확인이 필요할 수 있다. 반환된 Location 객체로부터 얻을 수 있는 값 중 획득 시간이 있기 때문에 이와 현재 시간을 비교해서 받아온 값이 언제 생성된 것인지 알아낼 수 있다.
Location.getElapsedRealtimeNanos()는 해당 위치가 얻어진 시점부터 현재까지 경과한 시간을 나노초 단위로 반환하기 때문에 나노초 단위를 초 단위로 바꿔볼 수 있다.
val elapsedSec = (location.elapsedRealtimeNanos) / 1_000_000_000
글을 작성하다 알게 된 것인데, API 레벨 33부터 getElapsedRealtimeAgeMillis(이 위치를 언제 받아왔는지 실시간으로 반환하는 메서드), getElapsedRealtimeMillis(이 위치의 실시간 경과시간(밀리초)을 반환) 와 같이 편리한 메서드들이 추가되었다. 어떤 것이 추가되었는가를 확인하기 위해서는 공식문서를 확인해보도록 하자.
❓ LocationListener
앞서 알아본 getLastKnownLocation() 메서드는 호출한 순간에만 위치 정보를 받아온다. 만약 주기적으로 위치 정보를 받고 싶다면 LocationListenter를 등록해주면 된다.
(이 친구도 API level 1부터 지원되고 있다…wow)
val locationListener = object : LocationListener {
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
super.onStatusChanged(provider, status, extras)
// provider의 상태가 변경될때마다 호출
// deprecated in API level 29
}
override fun onLocationChanged(location: Location) {
// Called when the location has changed
val longitude = location.longitude
val latitude = location.latitude
Log.d("Location", "Latitude : $latitude, Longitude : $longitude")
}
override fun onProviderEnabled(provider: String) {
super.onProviderEnabled(provider)
// provider가 사용 가능한 생황이 되는 순간 호출
}
override fun onProviderDisabled(provider: String) {
super.onProviderDisabled(provider)
// provider가 사용 불가능 상황이 되는 순간 호출
}
}
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, // 위치 정보 제공자
10000, // LocationListenr 호출 추기
10.0f, // 위치 업데이트 간 최소 거리
locationListener // LocationListener
)
locationManager.requestLocationUpdates(
String provider,
long minTimeMs,
float minDistanceM,
LocationListener listener)
각 파라미터들에 대해 설명하면 다음과 같다.
- provider : 위치 정보를 제공하는 위치 정보 제공자를 지정하는 역할을 한다. 앞에서 언급했던 것처럼 locationManager.allProviders 메서드를 통해 사용할 수 있는 위치 정보 제공자들을 확인할 수 있다. 안드로이드 API level 33기준으로 [passive, network, fused, gps] 이 있다. 대표적으로 많이 사용하는 것은 gps와 network다. 각각에 대한 자세한 설명은 위에 있기 때문에 넘어가도록 하겠다.
- minTimeMs : 밀리초 단위로 시간 간격을 지정한다. 이 매개변수는 위치 정보를 업데이트하는 주기를 제어하는데 사용된다. 즉, minTimeMs 값을 설정하면 해당 시간 간격으로 위치 정보를 업데이트하도록 지정할 수 있다. 따라서 minTimeMs 값을 낮게 설정하면 더 자주 위치 정보를 업데이트하고, 값을 높게 설정하면 주기적으로 위치 정보를 업데이트한다.
- minDistanceM : 위치 정보가 이전에 수신한 위치와의 거리 차이가 minDistanceM 미터보다 큰 경우에만 위치 정보를 업데이트하도록 지정하는 역할을 한다. 따라서 minDistanceM 값을 높게 설정하면 더 멀리 이동할 때만 위치 정보를 업데이트하고, 값을 낮게 설정하면 작은 거리 이동에도 위치 정보를 빈번하게 업데이트 할 수 있다. 위치 정보를 빈번하게 업데이트 하는 것은 배터리에 영향을 줄 수 있다.
- listener : 위치 정보가 업데이트될 때 호출되는 콜백 함수를 지정하는 역할을 한다. LocationListener를 구현하여 원하는 동작을 각 메서드들에 정의해두면, 위치 정보가 업데이트될 때마다 해당 메서드가 호출되어 원하는 작업을 수행할 수 있다. 즉, 위치 정보 업데이트에 대한 이벤트를 처리할 수 있는 콜백을 등록하는 것이다.
추가적인 FusedLocationProviderClient에 대한 내용은 아래 링크를 따라가 확인할 수 있다.
안드로이드 위치정보 가져오기 - FusedLocationProviderClient
❓ FusedLocationProviderClient FusedLocationProviderClient는 LocationManager보다 더 정확하고 효율적으로 위치 정보를 제공한다. FusedLocationProviderClient는 Google Play 서비스의 위치 API중 하나다. 기본 위치 기술을
krrong.tistory.com
참고