안드로이드/안드로이드

[안드로이드] Service에서 위치 데이터 지속적으로 얻기

snaildeveloper 2025. 5. 8. 11:12

Service를 이용하여 백그라운드에서 실시간 위치 데이터를 지속적으로 얻어오는 방법입니다.

 

1. 실시간 위치 데이터

실시간 위치 데이터는 FusedLocationProviderClient를 이용하여 쉽게 얻을 수 있다.

val mFusedLocationClient: FusedLocationProviderClient =
        LocationServices.getFusedLocationProviderClient(context)

 

다음과 같이 FusedLocationProviderClient를 초기화하고 이를 통해 위치 데이터를 얻을 수 있다.

 

마지막 위치

val lastLocation: Task<Location> = mFusedLocationClient.lastLocation

 

지속적인 위치

mFusedLocationClient.requestLocationUpdates(
    LocationRequest,
    LocationCallback,
    Looper
)

지속적인 위치의 경우 requestLocationUpdates()를 통해 얻을 수 있으며, 해당 메서드에는 3가지 인자를 필요로 한다.

 

LocationRequest

val locationRequestBuilder = LocationRequest.Builder(
    PRIORITY_BALANCED_POWER_ACCURACY, // priority, 생략 가능
    5_000L // intervalMillis, 필수
)
val locationRequest = locationRequestBuilder.build()

Builder에서 다양한 설정을 할 수 있다.

  • setIntervalMillis: Builder를 생성할 때 초기화하는 속성으로, 위치 업데이트 간격을 지정한다.
  • setPriority: Builder를 생성할 때 초기화 할 수 있으며, 아래와 같은 속성을 설정한다.
    • PRIORITY_BALANCED_POWER_ACCURACY: 기본 값으로, 위치의 정확도와 전력 사용의 균형을 이룬다.
    • PRIORITY_HIGH_ACCURACY: 전력을 추가로 사용하더라도 높은 정확도를 선호한다.
    • PRIORITY_LOW_POWER: 낮은 전력을 선호하며, 이로 인해 정확도가 다소 떨어질 수 있다.
    • PRIORITY_PASSIVE: 위치를 가져오는데 추가 전력을 소모하지 않는다.
  • 이외의 다양한 속성은 여기!

 

LocationCallback

val locationCallback =
        object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                val lastLocation = locationResult.lastLocation!!
                val latitude = lastLocation.latitude
                val longitude = lastLocation.longitude
                
                // TODO TASK
            }
        }

위치가 업데이트 될 때마다 실행되는 인터페이스

 

Looper

현재 스레드(메인 스레드) 의 루퍼 전달

  • Looper.myLooper()
  • Looper.getMainLooper()

따로 스레드 관리를 하지 않은 이상 두 메서드 모두 동일한 Looper를 반환한다.

 

전체 코드

fun initLocationUpdate(){
    val mFusedLocationClient: FusedLocationProviderClient =
            LocationServices.getFusedLocationProviderClient(context)

    val mLocationRequestHighAccuracy = LocationRequest.Builder(PRIORITY_HIGH_ACCURACY, 5_000L)

    val locationCallback =
            object : LocationCallback() {
                override fun onLocationResult(locationResult: LocationResult) {
                    val lastLocation = locationResult.lastLocation!!
                    val latitude = lastLocation.latitude
                    val longitude = lastLocation.longitude

                    // TODO TASK
                }
            }

    mFusedLocationClient.requestLocationUpdates(
            mLocationRequestHighAccuracy,
            locationCallback,
            Looper.myLooper()
        )
}

 

2. Service에 적용

Service에 필요한 foreground와 notification에 대한 설명은 생략하겠습니다.

class LocationService: Service() {
    private lateinit var notificationManager: NotificationManager

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        createNotificationChannel() // 채널 생성
        startForeground(101, getNotification())

        initLocationUpdate()
        
        return START_STICKY
    }
    
    private fun createNotificationChannel() {
        notificationManager =
            getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        val channel = NotificationChannel(
            CHANNEL_ID,
            CHANNEL_NAME,
            NotificationManager.IMPORTANCE_LOW // 알림 시 진동 없음
        )

        notificationManager.createNotificationChannel(channel)
    }
    
    private fun getNotification(): Notification {
        val builder: NotificationCompat.Builder =
            NotificationCompat.Builder(applicationContext, CHANNEL_ID)

        builder
            .setContentTitle("Title")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setSmallIcon(R.drawable.ic_launcher_background)

        return builder.build()
    }
    
    private fun initLocationUpdate(){
        val mFusedLocationClient: FusedLocationProviderClient =
                LocationServices.getFusedLocationProviderClient(context)

        val mLocationRequestHighAccuracy = LocationRequest.Builder(PRIORITY_HIGH_ACCURACY, 5_000L)

        val locationCallback =
                object : LocationCallback() {
                    override fun onLocationResult(locationResult: LocationResult) {
                        val lastLocation = locationResult.lastLocation!!
                        val latitude = lastLocation.latitude
                        val longitude = lastLocation.longitude

                        // TODO TASK
                    }
                }

        mFusedLocationClient.requestLocationUpdates(
                mLocationRequestHighAccuracy,
                locationCallback,
                Looper.myLooper()
            )
    }
    
    companion object {
        private const val CHANNEL_ID = "BackgroundLocationUpdateService"
        private const val CHANNEL_NAME = "BackgroundLocationUpdateService"
    }
}

 

Manifest 등록

<application
	...>
    <service
        android:name=".LocationService"
        android:enabled="true"
        android:exported="false"
        android:foregroundServiceType="location"/>

</application>

Manifest에서 foregroundServiceType을 lcoation으로 지정해주지 않으면 앱을 닫았을 때 백그라운드에서 Service가 동작하고 있지만 실시간 위치를 받지 못하게 된다.

따라서 앱을 닫았을 때에도 실시간 위치를 얻고 싶은 경우에는 반드시 foregroundServiceType을 location으로 지정해주어야 한다.

 

Service 시작

val intent = Intent(context, LocationService::class.java)
startService(intent)