Service란?
안드로이드 개발자 사이트에서 Service는 다음과 같이 설명하고 있다.
A service is a general-purpose entry point for keeping an app running in the background for all kinds of reasons. It is a component that runs in the background to perform long-running operations or to perform work for remote processes.
Link: Google Developer
즉, Service는 백그라운드에서 앱을 계속 실행하기 위한 진입점으로 오랫동안 실행되는 작업이나 원격 프로세스를 위한 작업을 수행한다.
서비스의 세 가지 유형
포그라운드(Forground): 사용자에게 잘 보이는 몇몇 작업을 수행한다. 포그라운드 서비스는 알림을 표시하며 사용자가 앱과 상호작용하지 않을 때도 계속 실행된다. (e.g. 오디오 앱의 트랙 재생)
백그라운드(Background): 사용자에게 직접 보이지 않는 작업을 수핸한다. (e.g. 저장소 압축)
바인드(Bound): 클라이언트-서버 인터페이스를 제공하여 구성 요소가 서비스와 상호작용하며 결과를 받을 수 있다.
Service Lifecycle
onCreate(): 서비스가 처음 생성되었을 때 호출하는 콜백 메서드이다. 일회성 설정 절차를 수행되며 만약 서비스가 이미 실행 중인 경우 이 메서드는 호출되지 않는다.
onStartCommand(): 다른 컴포넌트가 서비스를 시작하도록 요청하는 경우 호출하는 콜백 메서드이다. 서비스가 시작되면 백그라운드에서 무한히 실행될 수 있기 때문에 중단을 위해 stopSelf() 또는 stopService()를 호출하여 중단할 수 있다.
onBind(): 다른 컴포넌트가 서비스에 바인딩되고자 하는경우 호출하는 콜백 메서드이다. 메서드를 구현할 때 클라이언트가 서비스와 통신을 주고받기 위한 IBinder 인터페이스를 제공해야 한다.
onDestroy(): 더 이상 서비스를 사용하지 않고 소멸시킬 때 호출하는 콜백 메서드이다. 서비스가 수신하는 마지막 호출로 스레드와 리스너 등과 같은 리소스를 정리하기 위한 코드를 구현해야한다.
Manifest 환경 설정
Service도 다른 컴포넌트와 마찬가지로 애플리케이션의 매니페스트 파일에서 선언해야 한다.
Service 선언
<service>의 유일한 필수 요소는 android:name이다. 그 외의 속성에 관한 자세한 내용은 개발자 가이드에서 확인 할 수 있다.
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
...
</manifest>
Service 생성
service는 다른 컴포넌트가 startService()를 호출하여 생성되고, 그 결과로 서비스의 onStartCommand() 메서드가 호출된다.
서비스가 시작되면 이를 생성한 컴포넌트와 독립적인 수명 주기를 가지게 된다. 서비스가 백그라운드에서 무한히 실행될 수 있으며, 서비스를 생성한 컴포넌트가 소멸되었더라도 무관하기 때문에 작업이 완료되면 stopSelf()나 stopService() 메서드를 호출하여 중단시킬 수 있다.
다음은 service를 생성하기 위한 몇 가지 방법에 대해 알아본다.
IntentService 클래스 확장
IntentService는 백그라운드 스레드로 동작하기 때문에 별도의 스레드 처리가 필요 없다. 작업은 순차적으로 처리되며 한 번에 하나의 작업만 수행된다. API 30에서 deprecated가 되어 JobIntentService로 대체되었다.
class HelloIntentService : IntentService("HelloIntentService") {
override fun onHandleIntent(intent: Intent?) {
// 단순히 5초간 sleep
try {
Thread.sleep(5000)
} catch (e: InterruptedException) {
// 인터럽트 상태를 복원함
Thread.currentThread().interrupt()
}
}
}
Service 클래스 확장
서비스가 멀티스레딩을 수행해야 하는 경우 Service 클래스를 확장하여 각 인텐트를 처리할 수 있다. 기본적으로 UI 스레드이며, 별도의 스레드 처리가 필요하다.
다음은 IntentService 클래스의 샘플과 같은 작업을 수행하는 Service 클래스의 구현이다.
class HelloService : Service() {
private var serviceLooper: Looper? = null
private var serviceHandler: ServiceHandler? = null
// 스레드에서 메시지를 수신하는 핸들러
private inner class ServiceHandler(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
// 단순히 5초간 sleep
try {
Thread.sleep(5000)
} catch (e: InterruptedException) {
// 인터럽트 상태를 복원함
Thread.currentThread().interrupt()
}
// Start ID를 이용하여 서비스 중지
stopSelf(msg.arg1)
}
}
override fun onCreate() {
// 서비스를 실행하는 스레드를 시작한다.
HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
serviceLooper = looper
serviceHandler = ServiceHandler(looper)
}
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
// 각 시작 요청에 대해 메시지를 보내 작업을 시작하고
// Start ID를 전달하여 작업을 마치면 중지되는 요청을 알 수 있다.
serviceHandler?.obtainMessage()?.also { msg ->
msg.arg1 = startId
serviceHandler?.sendMessage(msg)
}
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? {
// 바인딩을 제공하지 않으므로 null을 반환한다.
return null
}
override fun onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
}
}
보시다시피 IntentService를 사용할 때보다 훨씬 손이 많이 간다.
Bound Service 생성
바인딩을 제공하는 서비스를 생성할 때는 클라이언트가 서비스와 상호작용하기 위한 IBinder 인터페이스를 제공해야 한다. 인터페이스를 정의하는 방법은 세 가지가 있다.
1. 바인더 클래스 확장
서비스가 로컬 애플리케이션에서만 사용되고 여러 프로세스에서 작동할 필요가 없는 경우, 자체적인 Binder 클래스를 구현하여 클라이언트가 서비스 내의 공개 메서드에 직접 액세스하도록 할 수 있다.
class LocalService : Service() {
private val binder = LocalBinder()
private val mGenerator = Random()
val randomNumber: Int
get() = mGenerator.nextInt(100)
// 클라이언트 바인더에 사용되는 클래스
inner class LocalBinder : Binder() {
fun getService(): LocalService = this@LocalService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
}
LocalBinder는 LocalService의 현재 인스턴스를 검색하기 위한 getService() 메서드를 클라이언트에 제공한다. 이를 통해 클라이언트가 서비스 내의 public 메서드를 호출할 수 있다.
다음은 버튼을 클릭했을 때 LocalService에 바인딩되어 getRandomNumber()를 호출하는 Acvivity이다.
class BindingActivity : Activity() {
private lateinit var mService: LocalService
private var mBound: Boolean = false
// bindService()에 전달될 서비스 바인딩에 대한 콜백을 정의
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as LocalService.LocalBinder
mService = binder.getService()
mBound = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
mBound = false
}
}
override fun onStart() {
super.onStart()
// 로컬 서비스에 바인딩
Intent(this, LocalService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
unbindService(connection)
mBound = false
}
// 버튼 클릭 이벤트(레이아웃 파일의 android:onClick 속성에 적용)
fun onButtonClick(v: View) {
if (mBound) {
// 로컬 서비스에서 메서드를 호출
// 만약 호출이 중단될 수 있는 경우, 별도의 스레드에서 수행되어야 한다.
val num: Int = mService.randomNumber
Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show()
}
}
}
2. 메신저 사용
서비스가 원격 프로세스와 통신해야 한다면 Messenger를 사용하여 서비스에 인터페이스를 제공할 수 있다. 이 기법을 사용하여 AIDL을 쓰지 않고도 프로세스 간 통신(IPC)을 실행할 수 있다.
/** 서비스에 대한 명령 */
private const val MSG_SAY_HELLO = 1
class MessengerService : Service() {
// 대상 클라이언트가 IncomingHandler로 메시지를 보낼 수 있도록 게시
private lateinit var mMessenger: Messenger
// 클라이언트에서 수신하는 메시지 핸들러
internal class IncomingHandler(
context: Context,
private val applicationContext: Context = context.applicationContext
) : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO ->
Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show()
else -> super.handleMessage(msg)
}
}
}
// 서비스에 바인딩할 때, 서비스에 메시지를 보내기 위한 인터페이스를 반환
override fun onBind(intent: Intent): IBinder? {
Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
mMessenger = Messenger(IncomingHandler(this))
return mMessenger.binder
}
}
Handler의 handleMessage() 메서드에서 서비스가 수신되는 Message를 받고 what 멤버에 기초하여 무엇을 할지 결정한다.
다음은 서비스에 바인딩되어 MSG_SAY_HELLO 메시지를 서비스에 전달하는 Activity이다.
class ActivityMessenger : Activity() {
// 서비스와 통신하기 위한 메신저
private var mService: Messenger? = null
// 서비스에서 바인드를 호출했는지 여부를 나타내는 플래그
private var bound: Boolean = false
// 서비스의 기본 인터페이스와 상호 작용하기 위한 클래스
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// 서비스와 연결되었을 때 호출되어 상호 작용하기 위한 개체를 제공한다.
// Messenger로 서비스와 소통하며, IBinder는 클라이언트로 부터 representation을 얻는다.
mService = Messenger(service)
bound = true
}
override fun onServiceDisconnected(className: ComponentName) {
// 서비스 프로세스가 중단되었을 때 호출된다.
mService = null
bound = false
}
}
fun sayHello(v: View) {
if (!bound) return
// 'what' 값을 사용하여 서비스 메시지 생성 및 전송
val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
try {
mService?.send(msg)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
}
override fun onStart() {
super.onStart()
// 서비스에 바인딩
Intent(this, MessengerService::class.java).also { intent ->
bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
// 서비스에 바인딩 해제
if (bound) {
unbindService(mConnection)
bound = false
}
}
}
3. AIDL(Android Interface Definition Language) 사용
객체를 운영체제가 이해할 수 있는 원시 유형으로 해체한 다음, 여러 프로세스를 집결시켜 프로레스 간 통신(IPC)을 실행한다.
Note: 대부분의 애플리케이션은 바인드 서비스를 생성할 때 AIDL을 사용하면 안된다. 이 방법은 다중 스레딩 기능이 필요할 수 있고 구현이 더욱 복잡해 지기 때문에 대부분의 애플리케이션에 적합하지 않다. AIDL을 반드시 직접 사용해야 할 경우에는 AIDL 문서를 참고한다.
서비스 바인딩
bindService()를 호출하면 애플리케이션 컴포넌트(클라이언트)를 서비스에 바인딩할 수 있다. Android 시스템이 onBind() 메서드를 호출하고, 이 메서드가 서비스와 상호작용을 위한 IBinder를 반환한다.
Note: Activitys, Services, Content Providers은 서비스 바인딩할 수 있으나 Broadcast Receiver에는 서비스 바인딩할 수 없다.
클라이언트에서 서비스 바인딩하려면 다음 단계를 따른다.
1. ServiceConnection 구현
두 가지 콜백 메서드를 재정의해야 한다.
- onServiceConnected(): 시스템이 이를 호출하여 서비스의 onBind() 메서드가 반환한 IBinder를 전달한다.
- onServiceDisconnected(): 서비스가 비정상 종료나 중단되었을 때 이를 호출한다. 클라이언트에 의해 바인딩을 해제할 때는 호출되지 않는다.
2. bindService() 를 호출하여 ServiceConnection 구현을 전달
서비스 바인딩 예시
var mService: LocalService
// ServiceConnection 구현
val mConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as LocalService.LocalBinder
mService = binder.getService()
mBound = true
}
override fun onServiceDisconnected(className: ComponentName) {
Log.e(TAG, "onServiceDisconnected")
mBound = false
}
}
// bindService() 메서드 호출
Intent(this, LocalService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
참고 사이트
'Android > Components' 카테고리의 다른 글
[안드로이드] App Components - Content Provider (0) | 2021.06.08 |
---|---|
[안드로이드] App Components - Broadcast Receiver (0) | 2021.06.04 |
[안드로이드] App Components - Activity (0) | 2021.01.17 |