Android 위치 정보 얻어오기

김성환·2024년 3월 31일

안드로이드에서 지도나 날씨같은 정보를 얻기위해서는 현재 위치에대한 정보가 필요로 합니다.
위치같은경우 사용자의 개인정보이고 그만큼 안드로이드 자체적으로 보안을 유지하고 있습니다.
앱에서 해당정보를 사용하려면 권한을 요청해야합니다.

권한은 크게 2가지로 나눌수있습니다.
ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION으로 나눌수있습니다.

ACCESS_COARSE_LOCATION

대략적인 위치 정보에 액세스할 수 있도록 허용합니다.
LocationManagerService 또는 FusedLocationProvider에서 가져온 경우 이 위치 추정치의 오차 범위는 약 3제곱킬로미터 이내입니다.

ACCESS_FINE_LOCATION

최대한 정확한 기기 위치 추정치를 제공합니다.
LocationManagerService 또는 FusedLocationProvider에서 가져온 경우 이 위치 추정치의 오차 범위는 일반적으로 약 50미터 이내이며 몇 미터 이내 또는 그 이상으로 정확할 때도 있습니다.
단일 런타임 요청에서 ACCESS_FINE_LOCATION 권한과 ACCESS_COARSE_LOCATION 권한을 모두 요청해야 합니다.
Android 12 버전이상에서는 ACCESS_FINE_LOCATION만 요청할경우 해당요청을 무시합니다.

<manifest ... >
  <!-- Always include this permission -->
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

  <!-- Include only if your app benefits from precise location access. -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
val locationPermissionRequest = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        when {
            permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
                // Precise location access granted.
            }
            permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> {
                // Only approximate location access granted.
            } else -> {
                // No location access granted.
            }
        }
    }

// ...

// Before you perform the actual permission request, check whether your app
// already has the permissions, and whether your app needs to show a permission
// rationale dialog. For more details, see Request permissions.
locationPermissionRequest.launch(arrayOf(
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.ACCESS_COARSE_LOCATION)

이밖에도 사용자가 액티비티가 아닌 서비스에서 위치 정보 얻어올때가 있습니다. 이경우에는 해당서비스가 forground 냐 background냐에 따라 달라집니다.

Forground 서비스 같은경우

<!-- Recommended for Android 9 (API level 28) and lower. -->
<!-- Required for Android 10 (API level 29) and higher. -->
<service
    android:name="MyNavigationService"
    android:foregroundServiceType="location" ... >
    <!-- Any inner elements would go here. -->
</service

이런식으로 메니페스트 파일속 해당서비스속성에 android:foregroundServiceType="location"을 추가해주면 됩니다.

Background 서비스 같은경우

<manifest ... >
  <!-- Required only when requesting background location access on
       Android 10 (API level 29) and higher. -->
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>

이런식으로 추가적인 권한요청이 필요합니다.
권한에 대하여 알아보았으니 이제 위치정보를 얻고 사용해봅시다.
위에서 말했듯이 위치정보를 가져오는 클래스는 LocationManager또는 FusedLocationProvider라는 것이 있습니다.

LocationManager

LocationManager는 안드로이드의 시스템 서비스 중 하나로, 위치 관련 정보를 관리하고 앱에 제공합니다.
이 클래스는 GPS, 네트워크(셀룰러 또는 Wi-Fi 기반), 기기의 센서 등 다양한 위치 제공자를 사용하여 위치 정보를 제공합니다.
LocationManager를 사용하여 위치 업데이트를 요청하고, 위치 제공자의 변경 사항을 감지할 수 있습니다.
안드로이드 API 레벨 1부터 제공되어 왔으며, 특히 안드로이드 6.0(Marshmallow) 이전에는 주요한 위치 관련 서비스를 제공하는 주요 클래스였습니

FusedLocationProvider

FusedLocationProvider는 Google Play 서비스의 일부로 제공되는 위치 관련 서비스입니다.
이 클래스는 여러 위치 제공자(GPS, 네트워크, Wi-Fi, 기타 센서 등)의 데이터를 결합하여 최상의 정확도와 효율성을 제공합니다.
FusedLocationProvider는 Google의 위치 추정 알고리즘과 데이터를 기반으로 동작하여, 보다 정확한 위치 정보를 제공합니다.
안드로이드 2.3(Gingerbread) 이후부터 지원되었으며, 특히 안드로이드 4.3(Jelly Bean) 이후부터 FusedLocationProviderClient를 사용하여 개발자에게 제공됩니다.
FusedLocationProvider는 안드로이드 시스템에서 자동으로 최적의 위치 제공자를 선택하고, 배터리 수명을 고려하여 위치 업데이트 간격을 조절합니다.

LocationManager 안드로이드 시스템의 위치 관련 서비스이며, FusedLocationProvider는 Google Play 서비스를 기반으로 한 향상된 위치 관련 서비스입니다. 대부분의 경우에는 FusedLocationProvider를 사용하는 것이 더 효율적이고 정확합니다.

Location

LocationManager와 FusedLocationProvider에서 위치정보를 얻어올 해당정보는 Location의 형태로 받게 됩니다.
Location은 여러 속성을 가지고 있습니다.

latitude
위치의 위도를 나타냅니다. 부동 소수점 형식으로 표현되며, 범위는 -90에서 90 사이의 값입니다. 북쪽은 양수, 남쪽은 음수로 표현됩니다.

longitude
위치의 경도를 나타냅니다. 부동 소수점 형식으로 표현되며, 범위는 -180에서 180 사이의 값입니다. 동쪽은 양수, 서쪽은 음수로 표현됩니다.

altitude
위치의 고도를 나타냅니다. 부동 소수점 형식으로 표현되며, 일반적으로 미터 단위로 표시됩니다. 고도 정보가 없는 경우 값이 0입니다.

bearing
위치의 방위각을 나타냅니다. 부동 소수점 형식으로 표현되며, 도 단위로 표시됩니다. 북쪽을 기준으로 시계 방향으로 0에서 359까지의 값을 가집니다.

speed
위치의 속도를 나타냅니다. 부동 소수점 형식으로 표현되며, 일반적으로 미터/초 단위로 표시됩니다.

provider
위치 정보를 제공한 위치 제공자의 이름을 나타냅니다. 예를 들어, GPS_PROVIDER, NETWORK_PROVIDER 등이 있습니다.

time
위치 정보를 제공한 시간을 나타냅니다. 에포크 시간(1970년 1월 1일 자정 UTC부터 경과한 밀리초)으로 표시됩니다.

accuracy
GPS를 사용하여 측정된 위치의 오차 반경을 나타내며, 값이 클수록 실제 위치와의 오차 범위가 커집니다. 따라서 앱에서는 accuracy 값이 작을수록 정확한 위치를 나타내는 것으로 간주하고, 이를 기준으로 사용자에게 정확한 위치 정보를 제공할 수 있습니다.


구현

해당 위치의 정확도는 LocationManager와 FusedLocationProvider의 제공자에 따라 달라집니다. 예를 들어 LocationManager를 사용하여 위치 업데이트를 요청할 때 requestLocationUpdates() 메서드에 전달하는 위치 제공자가 LocationManager.GPS_PROVIDER인 경우에는 FINE 위치 권한을 요청한 것이고, LocationManager.NETWORK_PROVIDER인 경우에는 COARSE 위치 권한을 요청한 것입니다.

LocationManager

class MyLocationManager(private val context: Context) {

    private val locationManager: LocationManager by lazy {
        context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
    }

    private val locationListener = object : LocationListener {
        override fun onLocationChanged(location: Location) {
            // 위치 정보가 변경될 때 호출되는 콜백
            val latitude = location.latitude
            val longitude = location.longitude
            // 위치 정보를 사용하여 원하는 작업 수행
            Log.e("*****", "onLocationChanged")
            Log.e("*****", "${location.latitude} ${location.latitude}")
        }

        override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
            // 위치 제공자 상태 변경 시 호출되는 콜백
            Log.e("*****", "onStatusChanged")
        }

        override fun onProviderEnabled(provider: String) {
            // 위치 제공자가 사용 가능할 때 호출되는 콜백
            Log.e("*****", "onProviderEnabled")
        }

        override fun onProviderDisabled(provider: String) {
            // 위치 제공자가 사용 불가능할 때 호출되는 콜백
            Log.e("*****", "onProviderDisabled")
        }
    }

    fun requestLocationUpdates() {//등록 꾸준히 업데이트
        try {
            // GPS_PROVIDER를 사용하여 위치 업데이트 요청
            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                1000, // 위치 업데이트 간격(ms)
                0f,   // 최소 거리(m)
                locationListener
            )
        } catch (e: SecurityException) {
            e.printStackTrace()
            // 위치 권한이 없는 경우 처리
        }
    }

    fun removeLocationUpdates() {//위치 업데이트 요청을 중지
        if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)){
        	locationManager.removeUpdates(locationListener)
        }
    }

    fun getCurrentLocationOnce(onLocationReceived: (Location?) -> Unit) {//일회용 request
        try {
            val lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
            onLocationReceived(lastKnownLocation)
        } catch (e: SecurityException) {
            e.printStackTrace()
            // 위치 권한이 없는 경우 처리
            onLocationReceived(null)
        }
    }
}

FusedLocationProvider

class MyFusedLocationProvider(private val context: Context) {

    private var isLocationUpdateRequested: Boolean = false
    private val fusedLocationProviderClient: FusedLocationProviderClient by lazy {
        LocationServices.getFusedLocationProviderClient(context)
    }

    private val locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            locationResult?.let { result ->
                for (location in result.locations) {
                    // 위치 정보를 사용할 수 있음
                    val latitude = location.latitude
                    val longitude = location.longitude
                }
            }
        }
    }

    fun requestLocationUpdates() {//등록 꾸준히 업데이트
        val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000L)
            .apply {
                setMinUpdateDistanceMeters(0f)//위치 업데이트 간의 최소 업데이트 거리
                setGranularity(Granularity.GRANULARITY_PERMISSION_LEVEL)
                setWaitForAccurateLocation(true)
            }.build()

        try {
            // 위치 업데이트 요청
            fusedLocationProviderClient.requestLocationUpdates(
                locationRequest,
                locationCallback,
                null
            )
            isLocationUpdateRequested = true
        } catch (e: SecurityException) {
            e.printStackTrace()
            // 위치 권한이 없는 경우 처리
        }
    }

    fun removeLocationUpdates() {//위치 업데이트 요청을 중지
        if (isLocationUpdateRequested) {
            fusedLocationProviderClient.removeLocationUpdates(locationCallback)
            isLocationUpdateRequested = false
        }
    }

    @SuppressLint("MissingPermission")
    fun getCurrentLocationOnce(
        onSuccessListener: OnSuccessListener<Location>,
        onFailureListener: OnFailureListener,
    ) { //일회용 request
        fusedLocationProviderClient.lastLocation
            .addOnSuccessListener(onSuccessListener)
            .addOnFailureListener(onFailureListener)
    }
}

reference
https://developer.android.com/develop/sensors-and-location/location/permissions?hl=ko
https://developers.google.com/codelabs/while-in-use-location?hl=ko&continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Flocation-based-features-in-android%3Fhl%3Dko%23codelab-https%3A%2F%2Fdevelopers.google.com%2Fcodelabs%2Fwhile-in-use-location#0
https://tekken5953.tistory.com/17
https://developer.android.com/reference/android/location/LocationManager
https://developers.google.com/location-context/fused-location-provider?hl=ko
https://velog.io/@changhee09/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%9C%84%EC%B9%98-%EC%A0%95%EB%B3%B4-LocationManager

0개의 댓글