백그라운드의 문제?
Background Service는 사용자가 인지할 필요가 없는 작업을 수행함으로 상호작용 하지 않는다.
이는 장점 같지만, 앱 입장에서는 성능 저하를 일으킬 만한 특성이다.
무분별하게 Background Service가 사용된다면 사용자는 이를 인지하지도 못할 것이고, 이로 인해 디바이스가 과부화 되어 메모리 부족을 겪을 수 있고 심하면 앱이 갑자기 죽는 일들이 일어날 수 있기 때문이다.
Background 제한
Google은 이 점을 인지하고 Oreo 버전부터 Background Service를 제한시켜버린다. 정확히는 앱이 Closed 상태일 때의 Background Service를 제한한 것이다. 앞으로는 앱이 Background 상태일 때도 Service를 유지 시키려면 Foreground Service만 사용 가능해진 것이다. 즉, 사용자가 Service를 계속 돌고 있음을 인지하고 직접 관리할 수 있도록 한 것이다.
더 나아가 Google은 startForegroundService()라는 메서드를 만들었다. 이 메서드는 Service 시작 후 Service 내에서 5초 안에 startForeground()를 호출하지 않으면 ANR을 띄우는 메서드다. Closed상태의 앱에서 서비스는 startForegroundService() 메서드를 사용하게 됐다.
그러면 다 Foreground Service로 바꿔서 관리해야하나? 그럼에도 똑같이 과부하가 올 것인데? 이를 대응하기 위해 JobScheduler, WorkManager가 등장하였고, 예약 작업을 통해 ForgroundService의 단점을 극복할 수 있게 되었다.
아래에서 순차적으로 백그라운드 Task 처리방법에 대해 알아보자.
1 : AlarmManager와 BroadCastReceiver 사용
지정한 타이밍에 시스템에서 알람이 오고 여기에 맞춰 백그라운드 작업을 수행할 수 있었지만 킷켓 버전에서는 알람이 미뤄지거나 한번에 몰아서 오는 등 정확한 실행을 보장하지 않게 된다.
BroadCastReciever를 통해서 기기의 부팅, 네트워크 연결 등의 디바이스 이벤트를 시스템으로부터 전파받아서 특정 작업을 수행해왔는데, OS 업데이트가 점점 진행되면서 제약이 생긴다.
누가 버전에서 특정 인텐트에 대해 동작이 제한되고, 오레오 버전에서 암시적 브로드캐스트리시버 등록을 차단하는 등 제한이 추가되고 있다.
그래서 대안책이 Job을 사용하는 것 이다.
2 : Forground Service
음악을 재생하는 앱을 만들거나 달리기 앱을 만들어 현재 상황을 유저에게 지속적으로 보여줘야 하는 경우엔 Foreground Service를 사용할 수 있다.
Foreground Service는 사용자가 직접 봐야 할 중요한 작업을 수행하는 서비스로, 백그라운드에서 실행되면서도 알림(Notification)을 통해 사용자에게 작업 진행 상황을 알리는 기능을 제공한다.
Foreground Service는 Service를 상속받아 구현할 수 있다. Service에서 startForeground() 메서드를 호출하여 Foreground Service로 등록하고, Notification을 설정하여 사용자에게 알림을 보여줄 수 있다.
3 : JobScheduler
그래서 Google은 예약된 작업을 해결법으로 제시했고 이를 위해 JobScheduler를 추천했다.
JobScheduler란? 개발자가 Background task를 정의하고 언제 이 작업이 실행될지 타이밍을 정할 수 있게 도와준다.
하지만 JobScheduler에서도 문제가 있었다. 원인은 버전 때문이었는데, JobScheduler는 롤리팝(버전 21)부터 지원하지만 롤리팝 버전에서 JobScheduler는 불안정하다는 이슈가 있었고 제대로 사용하려면 마쉬멜로우(버전 23)부터 사용해야 했습니다. 그 이전의 버전에서는 AlarmManager와 Broadcast Receiver를 사용해야 했다.
Q) 그럼 그냥 AlarmManager랑 Broadcast Receiver 사용하면 되잖아?
하지만 AlarmManager도 마쉬멜로우부터 문제가 생겼다. Doze 모드가 생기면서 알림이 울리지 않는 경우가 생겼고 Google은 setAndAllowWhileIdle(), setExactAndAllowWhileIdle() 같은 메서드를 지원하여 해결할 수 있다고 했지만 이외에도 AlarmManager를 잘못 설계하면 배터리 소모가 심해질 수 있다는 문제점이 발견됐다. 또한, 오레오(버전 26)부터 Background Service와 함께 암시적 Broadcast가 제약을 먹으면서 사용하기 더 어려워졌다.
3-1 : JobDispatcher
이런 상황을 인지한 Firebase가 JobDispatcher를 제공한다. JobDispatcher는 버전이 마쉬멜로우 버전 이상이라면 JobScheduler를, 미만이라면 AlarmManager를 사용하도록 해줌으로서 버전을 나누어 코드를 짜는 개발자들은 조금 편해지는 듯 했으나..
if (Build.VERSION.SDK_INT >= 23) {
// use JobScheduler
} else {
// use AlarmManager
}
Firebase와 관련된 것을 사용하려면 Google Play Service에 의존성을 가지게 되고 이로 인해 글로벌 앱들은 문제가 생긴다.
해서.. 서드 파티 라이브러리를 이용하기도 했다는데..
(중국이 바로 이 서비스를 사용하지 못하게 막혀있다고 하네요)
4 : WorkManager
2018 구글 I/O에서 WorkManager를 발표하고 만다. 완전히 새로운 방법으로 처리하는 것이 아니라 이전의 JobDispatcher 처럼 OS 버전별로 필요한 처리를 핸들링할 수 있게 도와준다.
즉, 내부적으로 API 버전에 맞게 AlarmManager와 JobScheduler를 사용하고 구글플레이서비스 사용이 가능하다면 JobDispatcher를 적극 사용한다.
위의 디스패처처럼 Google Play Service에 대한 의존성도 없고 확장된 여러 기능을 제공하기도 한다.
WorkManager 장점
- Android가 제시하는 best practice에 가까워 전력소비를 줄일 수 있다.
- Doze모드와 같은 절전 기능을 지원합니다.
- 일회성이나 장기적인 작업을 지원한다.
- 작업 체이닝이 가능하여 작업의 순차적 처리가 가능하다.
- 작업의 모니터링 관리가 가능하다. 작업의 현재 상태 조회가 가능하다.
- 예약된 작업은 내부적으로 관리되는 SQLite 데이터베이스에 저장되어서 기기를 재부팅해도 작업이 유지되고 다시 예약되도록 보장한다.
- RxJava와 Coroutine을 지원한다.
WorkManager 사용
- 워커를 만들어주고 (위에서 숱하게 말이 나왔던 Worker)
class LogWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
(0..100).forEach {
logging(it)
}
return Result.success()
}
private fun logging(count: Int) {
Log.e("log", count.toString())
Thread.sleep(1000)
}
}
- Worker를 WorkRequestBuilder로 WorkRequest 형태로 빌두 후 WorkManager에 enqueue()
class LogActivity : AppCompatActivity() {
private lateinit var binding: ActivityLogBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLogBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener {
OneTimeWorkRequestBuilder<LogWorker>()
.build().also {
WorkManager.getInstance(this)
.beginWith(it)
.enqueue()
}
}
}
}
- 그 외 기능들
// Constraints + PeriodicWorkRequest + InputData
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build()
val myWorkRequest: WorkRequest =
PeriodicWorkRequestBuilder<MyWork>(
1, TimeUnit.HOURS, // repeatInterval (the period cycle)
15, TimeUnit.MINUTES // flexInterval
)
.setInputData(
workDataOf(
"IMAGE_URI" to "http://..."
)
)
.setConstraints(constraints)
.build()
// Observe Work Progress
WorkManager.getInstance().getWorkInfoByIdLiveData(mathWork.id)
.observe(this, Observer { info ->
if (info != null && info.state.isFinished) {
val myData: Data = info.outputData
val myResult = myData.getInt(KEY_RESULT, myDefaultValue)
// ... do something with the result ...
}
})
// Work Chaining
WorkManager.getInstance(myContext)
// Candidates to run in parallel
.beginWith(listOf(plantName1, plantName2, plantName3))
// Dependent work (only runs after all previous work in chain)
.then(cache)
.then(upload)
// Call enqueue to kick things off
.enqueue()
마치며
그냥 예제 보면서 구현했던걸 요즘은 WorkManager로 많이들 구현해서 그렇구나 했던 걸 다시 면밀하게 살펴볼 수 있었다. WorkManager가 정말 다양한 작업들을 지원하는 것도 알 수 있었다.
android sdk 버전이 (api 레벨이) 올라가면서 서비스에 대한 제약도 강력하게 건다고 느꼈는데 이유가 있음을 알게 됐다. 기존부터 엄청 신경쓰던 문제 중 하나라서 그럴 수 밖에 없었던 과거 행적들로 인해 이해도 하게 됐다!
참고자료
새로운 안드로이드 백그라운드 작업 처리법 : WorkManager
안드로이드에는 백그라운드 작업을 처리하는 많은 방법들이 존재 합니다.
medium.com
https://medium.com/@ramkid91/android-background-task-a31e74bfd471
Android- Background task
백그라운드 태스크
medium.com
https://duzi077.tistory.com/222
WorkManager로 안드로이드 하위 버전부터 오레오 버전까지 백그라운드 작업 통합
안드로이드 디바이스에서 백그라운드로 진입했을 때 작업을 수행하려면 서비스를 사용해야합니다.하지만 서비스를 사용할때 고려해야될 사항들이 있습니다. 1. 서비스는 프로세스가 계속 실행
duzi077.tistory.com
'Android > Background Task' 카테고리의 다른 글
[Android] Doze모드와 대응 방법 (0) | 2024.11.16 |
---|---|
[Android] WorkManager 사용법 (2) - 심화 (1) | 2024.11.16 |
[Android] WorkManager 사용법 (1) - 기본 (0) | 2024.11.16 |
[Android] 안드로이드 백그라운드 작업 (1) - 개요 (1) | 2024.11.16 |