먼저 권한 설정을 위해서 Manifest에 설정을 해줍니다.
ACCESS_BACKGROUND_LOCATION
권한은 안드로이드 10(Q) 버전부터 도입되었습니다.
이 권한은 앱이 백그라운드에서 사용자의 위치 정보에 접근할 수 있도록 합니다.
<service>
는 앱이 foreground에서 동작할 수 있도록 하는 설정입니다.
본인이 만들어둔 Service.kt 파일 class를 넣어주는 것입니다.
저의 경우에는 Service()
를 받은 class가 RecordingService
이므로 해당 이름으로 설정해두었습니다.
// ex .service.RecordingService
android:foregroundServiceType="location"
은 위치 정보를 foreground에서 받아오기 위해 넣어주었습니다.
Manifest.XML
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application>
<activity>
...
</activity>
<service
android:name="{본인의 Service class 파일 경로}"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="location" />
</application>
다음은 Service 구현체입니다.
Service에서 Foreground를 실행시키는 구현체가 만들어지고, 앱이 백그라운드 상태에서도 지속적인 위치 정보를 가져오도록 합니다.
정확히 위치 정보가 나오게 되는 건 onCreate()
에서 구현한 LocationCallback
의 onLocationResult()
에서 정보를 수집할 수 있습니다.
위치 데이터를 Screen에 전달할 때는 Broadcast Receiver, ViewModel, EventBus 등 다양한 방법으로 각자 용도에 맞도록 전송하시면 될 것 같습니다.
val locationInterval = 5000L
이 시간을 설정하여 얼마의 시간마다 새로운 위치 정보를 가져올지 설정할 수 있습니다.
1000L
이 1초이니 여기서는 5초가 됩니다.
또한 Priority.PRIORITY_HIGH_ACCURACY
옵션은 위치의 정확도와 배터리 효율성을 기준으로 하는 옵션이니,
아래 공식 문서를 읽어보시고 본인의 용도에 맞도록 잘 설정해서 사용하는 것을 추천드립니다.
Service.kt
class RecordingService : Service() { // End of RunningService class
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
// Context
private lateinit var context: Context
override fun onCreate() {
super.onCreate()
context = applicationContext
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
} // End of onCreate()
override fun onBind(p0: Intent?): IBinder? {
return null
} // End of onBind()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
Actions.START.toString() -> start()
Actions.STOP.toString() -> stopSelf()
}
return super.onStartCommand(intent, flags, startId)
} // End of onStartCommand()
private fun start() {
val intent = Intent(this, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
val pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val locationInterval = 5000L
val locationRequest =
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, locationInterval)
.setWaitForAccurateLocation(false).build()
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
val location = locationResult.locations[0]
Log.d(TAG, "onLocationResult: $location")
}
}
fusedLocationProviderClient.requestLocationUpdates(
locationRequest, locationCallback, Looper.getMainLooper()
)
val notification = NotificationCompat.Builder(this, "foreground_channel")
.setContentTitle("Title")
.setContentIntent(pendingIntent).setContentText("Elapsed time").build()
startForeground(1, notification)
} // End of start()
override fun onDestroy() {
stopForeground(true)
stopSelf()
super.onDestroy()
}
enum class Actions {
START, STOP
} // End of Actions class
} // End of RunningService class
다음 Application에서 NotificationManager를 설정합니다.
Application.kt
...
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(
"walking_channel", "walking_channel",
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(channel)
}
} // End of onCreate()
...
이제 Foreground Service를 실행할 수 있습니다!
저 같은 경우는 특정 화면에 들어서거나, 버튼으로 동작하도록 했는데, 여기서는 가장 쉬운 예시를 들기 위해 생명주기에 따라 실행되도록 했습니다.
특정 Screen이 실행되었을 때 자동으로 Foregroud Service
가 START
되고, Screen이 종료되었을 때 Foregroud Service
가 STOP
되도록 구현하였습니다.
이것은 예시이니 본인이 설정하고 싶은 타이밍에 맞도록 START
를 넣어주시면 될 것 같습니다.
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
// onStart 에 자동으로 Service를 시작하고 싶다면 여기 넣기
Intent(
context.applicationContext,
RecordingService::class.java,
).also {
it.action = RecordingService.Actions.START.toString()
context.applicationContext.startService(it)
}
} else if (event == Lifecycle.Event.ON_STOP) {
// ON_STOP 이 되면 service 종료
Intent(
context.applicationContext,
RecordingService::class.java,
).also {
it.action = RecordingService.Actions.STOP.toString()
context.applicationContext.startService(it)
}
}
}
lifeCycleOwner.lifecycle.addObserver(observer)
return@DisposableEffect onDispose {
lifeCycleOwner.lifecycle.removeObserver(observer)
}
}
궁금하신 부분은 댓글로 남겨주시면 언제든지 답변해드립니다!