🚨 Trouble Shooting
프로젝트에서 Kakao Map Api를 사용하기 위해 현재 사용자의 위치를 가져오기 위해서 안드로이드 OS에서 제공하는 LocationManager 시스템 서비스를 이용해 현재 사용자의 위치를 요청하였지만 계속해서 위치를 받아오지 못하는 문제가 발생했습니다.
원인을 분석하는 과정에서 이 API가 다음과 같은 문제점이 있다는 것을 알게 되었습니다.
- 정확한 위치를 불러오는 기능이 생각보다 배터리를 많이 소모합니다. -> 전력 효율 저하
- 건물 내부에서 정확한 위치를 파악하기 힘들거나 정확한 위치를 파악하기 힘들 때가 있습니다. -> 정확도 저하
- API 자체가 복잡합니다. -> 개발 과정의 복잡성 증가
이에 대한 대응 방안으로 구글에서 서비스 하는 최적의 알고리즘으로 위치 제공자를 제공 하는 Fused Location Provider 라이브러리를 사용해 보겠습니다.
📕 안드로이드의 위치 서비스 (Android Location Service)
Android Location Service는 두 가지 위치 제공자 (Location Provider) 에게 위치 정보를 요청할 수 있습니다.
- GPS_PROVIDER
- GPS(Global Positioning System)를 통해 정확한 위치 정보에 접근할 수 있도록 합니다.
- 이 권한은 앱이 상대적으로 정확한 위치 정보가 필요한 경우에 사용됩니다. 예를 들어, 네비게이션 앱이나 위치 기반 서비스에서 주로 사용됩니다.
- ACCESS_FINE_LOCATION을 사용해 권한을 요청합니다.
- NETWORK_PROVIDER
- Wi-Fi 및 휴대폰 기지국과 같은 네트워크 신호를 사용하여 위치를 확인할 수 있습니다.
- 이 권한은 정확한 위치가 필요하지 않은 경우에 사용됩니다. 예를 들어, 날씨 앱이나 근처 매장을 찾는 앱에서 주로 사용됩니다.
- ACCESS_COARSE_LOCATION를 사용해 권한을 요청합니다.
LocationManager의 getLastKnownLocation( ) 메소드는 다음 데이터를 구할 수 있습니다.
- getAccuraycy( ) : 정확도
- getLatitude( ) : 위도
- getLongitude( ) : 경도
- getTime( ) : 획득 시간
또한, 두 가지 Location Provider 에게 반드시 ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION 권한에 대한 검사를 해줘야 합니다.
val locationManager = requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
when(checkPermission()){
true -> {
Log.d("locationManagerProcess", "권환 허가 완료 위치 찾기")
val location: Location? = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
Log.d("locationManagerProcess", "현재 사용자 위치 : $location")
location?.let {
latLng = LatLng.from(location.latitude, location.longitude)
}
}
false -> {
latLng = LatLng.from(37.394660,127.111182)
//showPermissionSnackBar()
}
}
private fun checkPermission() = ContextCompat.checkSelfPermission(requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
📕 통합 위치 제공자(Fused Location Provider)
- 안드로이드 공식 문서에서는 위치정보를 획득하기 위해서 FusedLocationProvider를 사용할 것을 권장하고 있습니다.
- GPS를 계산은 배터리 소모가 큰 작업이지만 이 API를 사용하면 위치정보 사용목적에 따라서 BatterEfficientMode, HighAccuracyMode 등 다양한 옵션을 사용할 수 있습니다.
- GPS, Wi-Fi 등 여러가지 위치정보를 계산하는데 필요한 소스를 자세하게 설정할 수도 있다.
또한 API 안내 사이트를 확인해보면 간편성과 배터리의 효율을 강조하고 있습니다. 이는 위에서 언급한 Android Location service의 단점을 보완할 수 있습니다.
🔨 FusedLocationProvider 사용하기
1️⃣ 다른 글들을 보면 모듈 (Application) 수준의 Gradle 파일에 아래에 다음 종속성을 추가 추가하라고 합니다.
implementation ("com.google.android.gms:play-services:12.0.1")
🚨 Error
- Duplicate class android.support.v4.app.INotificationSideChannel found in modules core-1.10.1-runtime (androidx.core:core:1.10.1) and support-compat-26.1.0-runtime (com.android.support:support-compat:26.1.0)
위의 종속성을 추가하는 코드는 Google Play services의 구성 요소를 모두 가져옵니다. 이는 다른라이브러리 간의 충돌을 일으킬 수 있습니다. 그래서 필요한 구성 요소만 가져와 불필요한 리소스 및 충돌을 피할 수 있습니다.
implementation 'com.google.android.gms:play-services-location:18.0.0'
2️⃣ Fused Location Provider를 사용 하기 위한 클래스를 생성합니다.
🌰 fun initLocationClient( )
- 통합 위치 제공자를 사용하기 위한 클라이언트 객체를 생성하고 초기화 합니다.
- 원하는 Location Request를 생성합니다. priority는 현재 위치를 얼마나 정확하게 가져오는지 설정합니다. 해당 옵션의 값으로 올 수 있는 것은 다음과 같습니다.
- BALANCED_POWER_ACCURACY : 약 100m 정도의 대략적인 도시 블록 내의 위치 정밀도, 전력을 비교적 적게 사용
- HIGH_ACCURACY : 가장 적확한 위치 요청, GPS를 사용해 위치를 확인할 가능성이 높음
- LOW_POWER : 약 10km 정도의 도시 수준의 정밀도, 아주 대략적인 수준으로 전력을 더 적게 소비
- NO_POWER : 전력 소비에 영향을 거의 미치지 않으며 사용, 앱에서 위치를 트리거 하지 않고 다른 앱에서 트리거 한 정보 사용
- 위치 제공자의 checkLocationSettings( ) 을 사용해 위에서 생성한 Location Request를 실제로 사용할 수 있는지 확인해야 합니다.
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
// 1. 위치 서비스를 관리하는 클라이언트를 초기화
private fun initLocationClient() {
Log.d("FusedLocationManager", "initLocationClient() start ")
// 위치 서비스를 관리하고 제공하는 클라이언트
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
// priority : 위치 정확도
// BALANCED_POWER_ACCURACY : 약 100m 정도의 대략적인 도시 블록 내의 위치 정밀도, 전력을 비교적 적게 사용
// HIGH_ACCURACY : 가장 적확한 위치 요청, GPS를 사용해 위치를 확인할 가능성이 높음
// LOW_POWER : 약 10km 정도의 도시 수준의 정밀도, 아주 대략적인 수준으로 전력을 더 적게 소비
// NO_POWER : 전력 소비에 영향을 거의 미치지 않으며 사용, 앱에서 위치를 트리거 하지 않고 다른 앱에서 트리거 한 정보 사용
val locationRequest = LocationRequest.create().apply {
interval = 1000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
// 위치 업데이트 요청을 위한 요청
val builder = LocationSettingsRequest.Builder()
.addLocationRequest(locationRequest)
// 위치 서비스를 사용하기 전에 사용자의 위치 설정을 확인하거나 변경하기 위해 사용
// 위치 서비스를 사용할 때 GPS나 네트워크 기반의 위치 정보를 활성화 하도록 사용자에게 요청
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")
}
}
🌰 fun initLocationCallback( )
- LocationCallback은 위치 서비스로부터 새로운 위치 업데이트가 있을 때마다 호출되는 추상 클래스 입니다.
public abstract class LocationCallback {
public LocationCallback() {
}
public void onLocationResult(@RecentlyNonNull LocationResult var1) {
}
public void onLocationAvailability(@RecentlyNonNull LocationAvailability var1) {
}
}
- 위치 업데이트를 처리하는 콜백 함수를 초기화 합니다.
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
// 2. 위치 업데이트를 처리하는 콜백 초기화
private fun initLocationCallback() {
Log.d(TAG, "initLocationCallback() start")
// Fused Location Provider에서 위치 업데이트 이벤트를 수신하는 콜백 클래스
// 위치 서비스로부터 새로운 위치 업데이트가 있을 때마다 호출
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
locationResult ?: return
for (location in locationResult.locations) {
listener.onLocationUpdated(location)
break
}
}
}
}
🌰 fun requestLastLocation
- 마지막으로 알려진 위치 정보 즉, 현재 사용자의 위치를 요청하는 메소드 입니다.
- 현재 사용자의 위치를 알기 위해선 반드시 위에서 언급한 권한을 허용해줘야 합니다.
// 4. 마지막으로 알려진 위치 정보를 요청
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
) {
Log.d(TAG, "requestLastLocation() : PERMISSION NOT GRANTED")
return
}
fusedLocationProviderClient.lastLocation.addOnSuccessListener { location ->
listener.onLocationUpdated(location)
}
}
🌰 interface OnLocationUpdateListener
// 위치 업데이트가 발생할 때 리스너를 통해 위치 정보를 전달하는 인터페이스
interface OnLocationUpdateListener {
fun onLocationUpdated(location: Location)
}
🌰 전체 코드
class FusedLocationProvider (
private val context: Context,
private val listener: OnLocationUpdateListener
) {
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
init {
initLocationClient()
initLocationCallback()
}
// 1. 위치 서비스를 관리하는 클라이언트를 초기화
private fun initLocationClient() {
Log.d("FusedLocationManager", "initLocationClient() start ")
// 위치 서비스를 관리하고 제공하는 클라이언트
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
// priority : 위치 정확도
// BALANCED_POWER_ACCURACY : 약 100m 정도의 대략적인 도시 블록 내의 위치 정밀도, 전력을 비교적 적게 사용
// HIGH_ACCURACY : 가장 적확한 위치 요청, GPS를 사용해 위치를 확인할 가능성이 높음
// LOW_POWER : 약 10km 정도의 도시 수준의 정밀도, 아주 대략적인 수준으로 전력을 더 적게 소비
// NO_POWER : 전력 소비에 영향을 거의 미치지 않으며 사용, 앱에서 위치를 트리거 하지 않고 다른 앱에서 트리거 한 정보 사용
val locationRequest = LocationRequest.create().apply {
interval = 1000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
// 위치 업데이트 요청을 위한 요청
val builder = LocationSettingsRequest.Builder()
.addLocationRequest(locationRequest)
// 위치 서비스를 사용하기 전에 사용자의 위치 설정을 확인하거나 변경하기 위해 사용
// 위치 서비스를 사용할 때 GPS나 네트워크 기반의 위치 정보를 활성화 하도록 사용자에게 요청
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")
}
}
// 2. 위치 업데이트를 처리하는 콜백 초기화
private fun initLocationCallback() {
Log.d(TAG, "initLocationCallback() start")
// Fused Location Provider에서 위치 업데이트 이벤트를 수신하는 콜백 클래스
// 위치 서비스로부터 새로운 위치 업데이트가 있을 때마다 호출
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
locationResult ?: return
for (location in locationResult.locations) {
listener.onLocationUpdated(location)
break
}
}
}
}
// 3. 위치 업데이트를 요청
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()
)
}
// 4. 마지막으로 알려진 위치 정보를 요청
fun requestLastLocation() {
Log.d(TAG, "requestLastLocation() start")
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
Log.d(TAG, "requestLastLocation() : PERMISSION NOT GRANTED")
return
}
Log.d(TAG, "requestLastLocation() 권한 허용")
fusedLocationProviderClient.lastLocation.addOnSuccessListener { location ->
Log.d(TAG, "requestLastLocation addOnSuccessListener start")
Log.d(TAG, "requestLastLocation() : ${location.latitude} / ${location.longitude}")
listener.onLocationUpdated(location)
}
}
fun stopLocationUpdates() {
fusedLocationProviderClient.removeLocationUpdates(locationCallback)
}
companion object {
private const val TAG = "FusedLocationManager"
}
}
'PROJECT' 카테고리의 다른 글
안드로이드 클린 아키텍처 도메인 레이어 설계 (0) | 2024.07.13 |
---|---|
[PROJECT] MulterError: Unexpected field (1) | 2024.02.06 |
[PROJECT] 프로젝트에 DataBinding & @BindingAdapter 사용해보기 (2) | 2024.01.24 |
[PROJECT]프로젝트 리팩토링 (0) | 2024.01.16 |