Developing Myself Everyday
article thumbnail
Published 2023. 8. 21. 15:11
Android 4대 컴포넌트 - Service Android

이 글에서는 Android의 4대 컴포넌트인 Service에 대해 더 자세하게 알아보고자 합니다.


 

 

 

Service란?


Service의 주된 목적은 오래 걸리는 작업을 백그라운드에서 처리하는 것입니다. 주로 큰 파일을 다운로드하는 것과 같은 작업을 서비스로 처리합니다. 사용자에게 다운로드 진행률을 지속적으로 표시해 사용자를 귀찮게 하고 싶지는 않기 때문에 서비스는 UI가 없이 실행됩니다.

 

 

Service의 종류

서비스의 종류는 크게 3가지로 나눌 수 있습니다.

 

1. Foreground

2. Background

3. Bound

 

ForegroundBackgroundStarted Service라 불리고 Bound는BoundService라 불립니다.

 

포그라운드(Foreground) 서비스: 포그라운드 서비스는 사용자에게 눈에 띄게 작업을 수행하는 서비스입니다. 이 서비스는 사용자에게 알림을 보여주어 앱이 현재 작업을 수행하고 있는 것을 시각적으로 알리며, 시스템 리소스를 더 우선적으로 사용합니다. 주로 사용자에게 중요한 정보를 제공하거나 앱이 백그라운드에서도 활동적으로 작업해야 하는 경우에 사용됩니다. 예를 들어, 음악 재생 앱이나 위치 추적 앱에서 주로 사용됩니다.

 

백그라운드(Background) 서비스: 백그라운드 서비스는 용자에게 눈에 띄지 않는 작업을 수행하는 서비스입니다. 이 서비스는 보통 사용자가 직접 인지하지 않아도 되는 작업에 사용되며, 시스템 리소스 사용을 덜 중요시합니다. 백그라운드에서 지속적인 데이터 처리, 네트워크 통신, 데이터베이스 작업 등이 해당됩니다. 백그라운드 서비스는 백그라운드 작업을 수행하는 데 사용됩니다.

 

바운드(Bound) 서비스: 바운드 서비스는 앱의 컴포넌트가 다른 컴포넌트와 통신하기 위해 사용하는 서비스입니다. 이 서비스는 클라이언트-서버 모델을 따르며, 클라이언트 컴포넌트는 서비스에 연결(bind)하고 데이터를 요청하거나 전달할 수 있습니다. 바운드 서비스는 일반적으로 앱 내부의 컴포넌트 간 통신에 사용되며, 다른 컴포넌트와 강하게 결합된 작업을 수행하는 데 유용합니다.

 

 

 

Foreground Service

Foreground Service는 사용자에게 Notification(상태 표시줄 알림)을 사용하여 시스템 리소스를 사용하고 있음을 알리는 역할을 합니다.

 

Foreground Service를 사용하는 가장 흔한 예시는 바로 음악 재생 앱입니다.

 

 

유튜브 뮤직 앱을 실행하면, 앱은 Foreground Service를 사용해 음악 재생 작업을 처리하고 상태 표시줄에 Notification을 표시합니다. 이 Notification을 통해 사용자는 유튜브 뮤직이 실행되고 있다는 것을 알 수 있으며, 재생 중인 곡의 정보를 확인할 수 있습니다.

 

만약, 우리의 작업이 즉각적으로 앱을 사용하는 유저에게 응답을 주어야 하지만, 앱이 백그라운드 상태에 있거나 기기가 재시작되어도 지속적으로 진행이 되어야하는 작업이라면 WorkManager를 사용할 수 있습니다. 그렇기에 우리가 보통 Service를 사용하는 경우는 Foreground Service를 사용하는 경우입니다.

 

 

 

Foreground Service 구현해보기


Service를 직접 구현해보기 전에 Service의 생명주기 메서드를 알고 가는것이 좋을 것 같습니다.

 

  • onCreate(): 서비스는 `onCreate()` 메서드에서 생성됩니다. 이 단계에서는 서비스가 초기화되고 필요한 리소스를 설정할 수 있습니다. 이 메서드는 서비스가 처음 생성될 때 한 번만 호출됩니다.
  • onStartCommand(): 시스템이 `startService()` 함수를 이용해 서비스를 시작할 때 호출됩니다. 서비스를 시작하면 서비스를 중단하는 것은 우리의 몫입니다. 서비스 내에서 직접 stopSelf를 호출하거나 밖에서 stopService를 호출해주면 됩니다. 이는 새롭게 `startCommand` 호출이 일어날 때 여러번 호출될 수 있습니다.
  • onBind(): 바운드 서비스를 사용하려면 클라이언트가 bindService() 메서드를 호출하여 서비스에 바인딩해야 합니다. 이후 서비스와 클라이언트 간 통신이 가능해집니다. 바운드 서비스의 생명주기는 클라이언트에 의해 관리되며, 서비스가 모든 클라이언트와의 연결을 끊으면 onDestroy() 메서드가 호출되어 서비스가 종료됩니다.
  • onDestroy(): 서비스가 더 이상 필요하지 않을 때 또는 시스템이 자원 부족 등의 이유로 서비스를 종료해야 할 때 onDestroy() 메서드가 호출됩니다. 이 메서드에서는 서비스가 사용한 리소스를 정리하고 종료 작업을 수행할 수 있습니다.

 

Service 생성

Service를 상속하는 클래스를 생성합니다. 서비스의 이름은 'RunningService`로 하겠습니다.

class RunningService : Service() {

    override fun onBind(intent: Intent): IBinder? {
        return null
    }
}

 

onBind()는 Bound Service에서 사용되는 메서드입니다. 그렇기에 Foreground Service에서는 null을 반환해주면 됩니다.

 

 

그 다음으로는 서비스의 작업을 시작, 종료하고 서비스가 수행해야 할 실제 작업을 구현해야 합니다. 이런 작업은 `onStartCommand()` 메서드에서 구현합니다.

class RunningService : Service() {

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    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)
    }
    
    private fun start() {
        
    }
    
    enum class Actions {
        START, STOP
    }
}

 

위의 클래스에서는 enum class로 Actions을 정의하고 Action에 따라 start() 메서드를 실행하거나 stopSelf()를 수행하고 있습니다. stopSelf()는 서비스를 중지하는 메서드입니다.

 

 

start() 메서드에서는 알림과 관련된 작업을 수행합니다. 아래는 알림을 구현한 예시입니다.

private fun start() {
    val notification = NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.ic_launcher_foreground)
        .setContentTitle("Run Title")
        .setContentText("Run Text")
        .build()

    startForeground(RUN_ID, notification)
}

companion object {
    const val RUN_ID = 1
    const val CHANNEL_ID = "running_id"
    const val CHANNEL_NAME = "running_notifications"
}

 

알림은 아래와 같이 Service의 Context와 channel ID를 설정해 구현합니다.

NotificationCompat.Builder(this, CHANNEL_ID)

ID같은 여러곳에서 사용되는 값은 companion object로 관리하는 것이 좋습니다.

 

 

 

NotificationChannel 생성

알림 채널은 알림을 관리하고 사용자에게 표시할 때 미리 정의된 설정을 제공하는 방법입니다. 애플리케이션 클래스에서 onCreate() 메서드에서 정의하면 됩니다.

class RunningApp: Application() {

    override fun onCreate() {
        super.onCreate()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                RunningService.CHANNEL_ID,
                RunningService.CHANNEL_NAME,
                NotificationManager.IMPORTANCE_HIGH
            )
            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
}

 

NotificationChannel은 Oreo 이후에 나온 개념이기 때문에 해당하는 버전 이상인 경우에만 실행되도록 합니다.

 

NotificationChannel을 생성하고 이 때 채널의 ID, 이름, 중요도 등을 설정합니다.

 

getSystemService() 메서드를 사용하여 시스템의 NotificationManager 객체를 가져옵니다. 이 객체를 사용하여 NotificationChannel을 생성한 후 등록합니다.

 

마지막으로 NotificationChannelNotificationManager 에 등록하면 시스템은 해당 NotificationChannel의설정을 인식하고 알림을 표시할 때 이 설정을 적용합니다.

 

 

 

Manifest 설정

Manifest에서 관련 설정을 해야 합니다. 퍼미션을 추가하고, 애플리케이션에 Service과 애플리케이션 클래스를 등록해야 합니다.

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    
    <application
        android:name=".RunningApp"
        ~~~~
        >

        <service android:name=".RunningService" />

 

 

 

Service 시작

액티비티에서 Service를 실행시키기 위해서는 `Intent`를 사용해야 합니다. 아래의 예시 코드는 Intent에서 Service를 찾은 다음 action을 정의하고 `startService()` 메서드를 통해 서비스를 시작하고 있습니다.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposePractTheme {
                Column(
                    modifier = Modifier.fillMaxSize(),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Center
                ) {
                   Button(onClick = {
                       Intent(applicationContext, RunningService::class.java).also { intent ->
                           intent.action = RunningService.Actions.START.toString()
                           startService(intent)
                       }
                   }) {
                       Text(text = "시작")
                   }
                    Button(onClick = {
                        Intent(applicationContext, RunningService::class.java).also { intent ->
                            intent.action = RunningService.Actions.STOP.toString()
                            startService(intent)
                        }
                    }) {
                        Text(text = "종료")
                    }
                }
            }
        }
    }
}

 

 

 

실행 결과

시작을 누르면 백그라운드에서 Service가 실행되면서 알림을 보내고 있습니다.

 

앱을 종료해도 알림은 여전히 남아있으며, 다시 앱을 실행해서 종료를 누르면 이전에 띄웠던 알림이 제거되는 것으로 백그라운드에서 잘 작동하고 있다는 것을 알 수 있습니다.

 

profile

Developing Myself Everyday

@배준형

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!