목차.
1. Android Service란?
2. Service 생명주기
3. Service를 활용해 Timer 개발하기
4. Service 개발 후기
Android Service란?
안드로이드 4대 컴포넌트(Activity, Service, BroadCast Receiver, Content Provider) 중 하나로 백그라운드 작업을 위한 구성 요소입니다.
서비스의 종류에는 크게 세 가지가 있습니다.
1. Foreground
사용자에게 잘 보이는 몇몇 작업을 수행하며, 진행 상황을 지속적으로 보여줘야 하는 경우에 사용합니다. 예를 들어 음악 앱에서 음악을 재생할 때 또는 러닝 앱에서 달리기를 할 때 사용됩니다. 포그라운드 서비스는 Notification을 사용하여 실행 중인 작업을 사용자에게 반드시 표시해야 합니다.
2. Background
사용자에게 직접 보이지 않는 작업을 수행하며, 진행 상황이나 결과에 대한 메시지를 전달할 필요가 없는 경우에 쓰입니다. 예를 들어 SNS에서 게시물을 업로드할 때 사용됩니다.
3. Bound
앱 구성요소에 서비스를 바인딩 하여 상호작용할 수 있으며 bindService()를 호출하여 사용합니다. 바인딩 된 서비스는 클라이언트-서버 인터페이스를 제공해 컴포넌트가 서비스와 상호작용하게 하며, 결과를 받을 수도 있고 이런 작업을 여러 프로세스에 걸쳐 프로세스 간 통신으로 수행할 수도 있습니다.
Foreground Service와 Background Service를 합쳐 startService라고 불리고 Bound는 bindService라고 불립니다. 세 가지 종류의 서비스가 있지만 각각 독립된 것은 아닙니다. 서비스 하나가 구현이 어떻게 되냐에 따라 binding(Bound) 되는 용도로도 쓰이며 동시에 Foreground, Background(Started)로도 쓰일 수 있습니다.
서비스는 안드로이드의 컴포넌트이기 때문에 Manifest에 선언해 주어야 사용이 가능합니다.
<manifest>
...
<application>
<service android:name=".ExampleService" />
...
</application>
</manifest>
Service에 아래와 같이 다양한 속성을 설정할 수 있습니다.
<service android:description="string resource"
android:directBootAware=["true" | "false"]
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:foregroundServiceType=["connectedDevice" | "dataSync" |
"location" | "mediaPlayback" | "mediaProjection" |
"phoneCall"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</service>
자주 쓰는 두 가지 속성에 대해 알아보겠습니다.
- enabled : 시스템에 의해 서비스가 인스턴스와 될 수 있을지를 의미합니다. 기본값은 "true"입니다.
- exported : 다른 앱에서 접근을 가능하게 할 것인지를 결정합니다. 값이 false 이면 앱 내에서만 서비스를 이용할 수 있습니다. 기본값은 intent filter를 포함하고 있는지의 여부에 따라 달라집니다. intent filter가 없다는 것은 앱 내에서만 사용하도록 만들어졌다는 의미이므로 기본값은 "false"입니다. 반대로 intent filter가 있을 경우 기본값은 "true"입니다.
Service 생명주기
startService() 호출 시
최초 서비스 객체가 새롭게 생성될 때 onCreate()가 호출되고, 이후 onStartCommand()가 호출되며 서비스가 시작됩니다. 이미 생성된 서비스일 경우 onCreate() 없이 onStartCommand()가 실행됩니다. 서비스를 종료하기 위해서는 stopService()를 실행하거나 서비스 내부에서 stopSelf()를 실행해야 합니다. 서비스가 제거되기 직전 onDestroy()가 호출되며 이후 서비스가 소멸됩니다.
* onStartCommand()의 return 값 flag
메모리 부족 등으로 인해 서비스가 죽었을 때 어떻게 처리할 것인지 설정할 수 있습니다.
- START_STICKY - 서비스가 죽으면 인텐트를 null로 초기화해 재시작 합니다.
- START_REDELIVER_INTENT - 서비스가 죽으면 인텐트 값을 그대로 유지해서 재시작 합니다.
- START_NOT_STICKY - 서비스가 죽어도 재시작 하지 않습니다. 시스템에 의해 강제 종료되어도 괜찮은 작업을 할 때 사용합니다.
bindService() 호출 시
최초 서비스 객체가 새롭게 생성될 때 onCreate()가 호출되고, 이후 onBind()가 호출되며 서비스가 시작됩니다. 이미 생성된 서비스일 경우 onCreate() 없이 onBind()가 실행됩니다. 바인드를 종료하기 위해서는 unbindService()를 실행합니다. 그러면 onUnbind()가 호출되며 해당 클라이언트는 서비스와 언바운드됩니다. 서비스가 제거되기 직전 onDestroy()가 호출되며 서비스가 소멸됩니다.
Service를 활용해 Timer 개발하기
진행했던 프로젝트에 타이머&알림 기능을 개발하였습니다.
타이머 동작 개발하기
startService()로 서비스를 실행시키고 서비스 내에 Android Timer를 구현하였습니다.
val timerService = Intent(this, TimerService::class.java)
timerService.putExtra(TimerService.TIMER_ACTION, TimerService.START)
startService(timerService)
서비스 클래스에서 타이머가 실행되면 시간 값을 intent에 담아 SendBroadcast()로 보냈습니다.
timerIntent.putExtra(TIME_ELAPSED, timeElapsed)
sendBroadcast(timerIntent)
액티비티의 onResume()에서 registerReceiver()에 리시버와 인텐트필터를 담아 등록하고, BroadcastReceiver의 onReceive()로 타이머의 시간 값을 받아 UI를 업데이트해주었습니다.
* 앱이 강제 종료될 때 서비스 처리
Task가 종료되었을 때 콜백을 받는 onTaskRemoved()에서 stopSelf()로 서비스 종료를 구현하였습니다. onTaskRemoved()를 호출하기 위해 manifest service에 stopWithTask를 false로 설정한 후 servce에서 onTaskRemoved()를 오버라이딩해서 사용했습니다.
<service
android:name=".TimerService"
...
android:stopWithTask ="false"/>
-
override fun onTaskRemoved(rootIntent: Intent?) {
timer.cancel()
stopForeground(true)
stopSelf()
super.onTaskRemoved(rootIntent)
}
포그라운드 알림 구현하기
서비스가 시작될 때 onStartCommand()에서 미리 NotificationChannel과 NotificationManager를 생성합니다. 이후 activity의 onPause()가 호출되면 포그라운드 서비스를 시작했습니다. startForeground()에 id 값과 notification 값을 넣어 시작합니다. activity의 onStart()가 호출되면 stopForeground()로 포그라운드 서비스를 종료시켰습니다.
아래는 프로젝트와 별개로 Android Service를 사용해 개발한 스톱워치 소스코드입니다.
https://github.com/shruddms/Stopwatch
Service 개발 후기
개발 테스트 과정에서 오류가 났었는데 Android Oreo 버전에 대한 예외 처리가 제대로 안 되어 있었던 것이 원인이었습니다. Oreo 버전부터는 notification에 채널을 지정해 줘야 합니다. 또한 아이콘, 제목, 내용은 필수입니다. 서비스를 호출할 때도 기존에는 startService 대신 startForegroundServce() 메소드를 사용해야 합니다.
백그라운드에서의 동작은 예외 처리해야 할 부분도 많고 고려해야 하는 부분이 많아 테스트하는 과정이 여간 번거로웠습니다. 당시 일정이 빠듯해 바로 개발을 진행했었는데 다음에 다시 Service 관련 개발을 하게 된다면 테스트 코드를 먼저 꼼꼼히 작성하고 개발해야겠다는 생각이 들었습니다.
'Main' 카테고리의 다른 글
[Android] Github Actions으로 CI/CD 구축 (8) | 2022.12.21 |
---|---|
[Android] Room을 활용해 Android 오프라인 캐시 구현 (0) | 2022.12.06 |
MVP 앱 개발 (4) | 2022.11.06 |
[Android] 앱 아키텍처 설계 (0) | 2022.11.03 |
[Android] Sentry를 활용한 앱 에러 트래킹 및 모니터링 (0) | 2022.10.29 |
댓글