Service in Android

오승준·2024년 6월 18일
post-thumbnail

Service란?


💡 Android에서 '서비스'는 사용자 인터페이스 없이 장기간 실행되는 작업을 수행하기 위해 백그라운드에서 실행되는 구성 요소입니다. 서비스는 네트워크 트랜잭션, 파일 I/O 또는 콘텐츠 공급자와의 상호 작용과 같은 작업을 처리할 수 있습니다. 서비스에는 세 가지 주요 유형이 있습니다.

1. Android 서비스 유형


  1. 포그라운드 서비스 : 포그라운드 서비스는 사용자가 인지할 수 있는 작업을 실행합니다.
    ****예를 들어 오디오 앱은 포그라운드 서비스를 사용하여 오디오 트랙을 재생합니다. 포그라운드 서비스는 Notification 을 표시해야 합니다. 포그라운드 서비스는 사용자가 앱과 상호작용하지 않을 때도 계속 실행됩니다.

포그라운드 서비스를 사용하는 앱의 예는 다음과 같습니다.

  • 포그라운드 서비스에서 음악을 재생하는 음악 플레이어 앱 알림에 현재 재생 중인 노래가 표시될 수 있습니다.
  • 사용자로부터 권한을 받은 후 포그라운드 서비스에서 사용자의 달리기를 기록하는 피트니스 앱 알림에는 사용자가 현재 피트니스 세션 중에 이동한 거리가 표시될 수 있습니다.
  1. 백그라운드 서비스 : 백그라운드 서비스는 사용자가 직접 인식하지 못하는 작업을 실행합니다. 예를 들어 앱이 저장소를 압축하는 데 서비스를 사용했다면 일반적으로 백그라운드 서비스입니다.

💡 참고: 앱이 API 수준 26 이상을 타겟팅하는 경우 앱 자체가 포그라운드에 있지 않으면 시스템에서백그라운드 서비스 실행에 제한
을 적용합니다. 예를 들어 대부분의 상황에서는 백그라운드에서 위치 정보에 액세스하면 안 됩니다. 대신 WorkManager를 사용하여 작업을 예약하세요.
overlayview를 사용하면 android system 은 foreground로 인식을해서 background service를 사용이 가능합니다. 이부분은 다음글에서 작성해보도록 해보겠습니다.

  1. 바운드 서비스 : 바인드된 서비스란 클라이언트-서버 인터페이스 안의 서버를 말합니다. 이를 통해 Activity과 같은 구성요소가 서비스에 바인딩되고, 요청을 전송하고, 응답을 수신하고, 프로세스 간 통신 (IPC)을 실행할 수 있습니다. 일반적으로 바인드된 서비스는 다른 애플리케이션 구성요소를 도울 때까지만 유지되고 백그라운드에서 무한히 실행되지 않습니다.

2. 서비스의 생명주기


서비스 생명주기. 왼쪽 다이어그램은 startService()로 생성된 경우이고, 오른쪽 다이어그램은 서비스가 bindService()로 생성된 경우의 수명주기를 나타낸다.


3. Service 콜백 메서드


서비스를 만들려면 Service의 서브클래스를 만들거나 기존 서브클래스 중 하나를 사용해야 합니다. 구현 시 서비스 수명 주기의 주요 측면을 처리하는 일부 콜백 메서드를 재정의하고, 해당하는 경우 구성요소를 서비스에 바인딩할 수 있는 메커니즘을 제공해야 합니다. 재정의해야 하는 가장 중요한 콜백 메서드는 다음과 같습니다.

onStartCommand()

다른 구성요소 (예: Activity)가 서비스 시작을 요청할 때 시스템은 startService()를 호출하여 이 메서드를 호출합니다. 이 메서드가 실행되면 서비스가 시작되고 백그라운드에서 무기한으로 실행될 수 있습니다. 이를 구현하는 경우 작업이 완료될 때 stopSelf() 또는 stopService()를 호출하여 서비스를 중지하는 것은 개발자의 책임입니다. 결합만 제공하려면 이 메서드를 구현하지 않아도 됩니다.

onBind()

다른 구성요소가 서비스와 결합을 원하는 경우 (예: RPC 실행) 시스템은 bindService()를 호출하여 이 메서드를 호출합니다. 이 메서드를 구현할 때 클라이언트가 IBinder를 반환하여 서비스와 통신하는 데 사용하는 인터페이스를 제공해야 합니다. 이 메서드는 항상 구현해야 합니다. 그러나 결합을 허용하지 않으려면 null을 반환해야 합니다.

onCreate()

시스템은 서비스가 처음 생성될 때 onStartCommand() 또는 onBind()를 호출하기 전에 이 메서드를 호출하여 일회성 설정 절차를 실행합니다. 서비스가 이미 실행 중이면 이 메서드가 호출되지 않습니다.

onDestroy()

시스템은 서비스가 더 이상 사용되지 않고 소멸될 때 이 메서드를 호출합니다. 서비스에서 이를 구현하여 스레드, 등록된 리스너 또는 수신기와 같은 리소스를 정리해야 합니다. 이는 서비스가 수신하는 마지막 호출입니다.


4. Practice


이번 연습에는 background service로 연습을 할 것입니다.

연습을 통해서 달성할 목표는 아래와 같습니다

  • Background Service는 위에 언급한 것과 같이 앱이Android 8.0(API 수준 26) 이상에서는 제한이 되는 데 이것을 테스트할 것입니다.
  • service의 콜백 메서드의 특성을 확인할 것 입니다.
  • background service의 제한사항을 확인할 것입니다.

- example


class MyService : Service() {
    private var count = 0

    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the service.")
    }


    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.e("Service", "Service is started")
        Thread {
            while (true) {
                Log.d("Service", "Service is running")
                Log.d("Service", "${count++}")
                try {
                    Thread.sleep(1000)
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }.start()

        return START_STICKY
    }

    override fun onDestroy() {
        Log.e("Service", "Service is destroyed")
        count = 0
        super.onDestroy()
    }
}

먼저 위처럼 Service 클래스를 만들어줍니다.

그리고 앱이 background 상태를 들어간 것을 확인하기위해서Application.ActivityLifecycleCallbacks를 상속받아서

ApplicationLifecycleStatus 클래스를 만들어줍니다.

enum class ApplicationState {
    FOREGROUND,
    BACKGROUND
}

class ApplicationLifecycleStatus : Application.ActivityLifecycleCallbacks {

    companion object {
        var state: ApplicationState = ApplicationState.FOREGROUND
    }

    override fun onActivityStarted(activity: Activity) {
        state = ApplicationState.FOREGROUND
    }

    override fun onActivityStopped(activity: Activity) {
        state = ApplicationState.BACKGROUND
    }

    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}

    override fun onActivityResumed(activity: Activity) {}

    override fun onActivityPaused(activity: Activity) {}

    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}

    override fun onActivityDestroyed(activity: Activity) {
        Log.d("Test", "State_Destroyed")
    }
}

여기서 onActivityStarte가 호출이되면 foreground, onActivityStopped가 호출이되면 backgroun입니다.

그리고 ApplicationLifecycleStatus를 호출하는 부분은 Applicataion에서 호출하도록하겠습니다.

class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()
        Log.d("TEST","current state: ${ApplicationLifecycleStatus.state}")
        registerActivityLifecycleCallbacks(ApplicationLifecycleStatus())
    }
}

자 이제 마지막으로 Activity에서 startService를 호출해주고 onResume, onStop, onDestroy 콜백 함수를 이용하여 앱이 background인지 foreground인지 확인해보도록 하겠습니다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val serviceIntent = Intent(this, MyService::class.java)
        startService(serviceIntent)
    }

    override fun onResume() {
        super.onResume()
        Log.d("Test","State_Resume: ${ApplicationLifecycleStatus.state}")
    }

    override fun onStop() {
        super.onStop()
        Log.d("Test","State_Stop: ${ApplicationLifecycleStatus.state}")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("Test","State_Destroy: ${ApplicationLifecycleStatus.state}")
    }
}

앱을 실행시키고 홈 화면을 누릅니다 그러면 아래의 로그처럼 onStopActiviry의 콜백이 호출이 되고 앱은 backgound의 상태로 들어갑니다.

그후에 onDetroy()service의 콜백함수가 호출이되고 Service가 중지됩니다.

여기서까지는 공식문서의 제한사항처럼 service가 중지됩니다.

하지만 onStartCommand 에 while문은 여전히 돌아가고 있습니다. 문제가 무엇일까요?

service는 기본적으로 mainThread에서 실행이 됩니다. 그래서 serivce가 중지된다고 해서 onStartCommand에서 새로 추가한 Thread것이 같이 사라지지는 않습니다.

그래서 공식문서에 나온 것처럼 onDestroy 함수에서 등록된 Thread나 listener등을 정리해줘야합니다. 만약 onDestroy에서 정리하지 않는다면 App Process 종료시키지 않는 한 계속 실행될 것 입니다.


5. Conclusion

Service에 대해서 많이 헤맸습니다. 처음에는 onDestroy 함수를 오버라이드 하지 않고 그냥 onStartCommand에 while문만 적어두고 App이 백그라운드 상태에 들어갔는데도 계속 log에 count가 찍히길래 당황했습니다. 그래서 service 생명주기 함수들을 다시 읽어보고 다 찍어보고나니 이해 할 수가 있었습니다. 저와 같은 의문을 가진 분들께 많은 도움이 되는 글이 됐으면 좋겠습니다.

Reference


백그라운드 실행 제한  |  Android Developers

Service  |  Android Developers

서비스 개요  |  Background work  |  Android Developers

Services in Android with Example - GeeksforGeeks

0개의 댓글