반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
Archives
Today
Total
관리 메뉴

코딩하기 좋은날

Android Service ( Start / Intent / bound) 란? - 1 본문

Android

Android Service ( Start / Intent / bound) 란? - 1

huiung 2020. 6. 15. 02:32
반응형

 

안드로이드 4대 컴포넌트 중 하나인 Service에 대해서 알아 보겠습니다.

 

Service는 background에서 실행되는 작업을 수행 할 수 있도록 해줍니다.

 

예를 들면 우리가 앱을 이용해서 어떤 파일을 다운 받는 다거나 다른 작업을 하면서 음악을 계속 듣는 다거나 하는 동작이 필요 할 때 사용 할 수 있습니다.

 


Thread

서비스를 알기 전에 먼저 thread의 개념에 대해 알고 있어야 합니다. 운영체제를 배우셨다면 많이 들어 보셨을텐데 간단하게 독립된 실행 단위 라고 생각하시면 될 것 같습니다. 프로세스 내에서 동시에 여러가지 작업을 해야 할 때가 있는데 이때 각각의 thread를 생성하여 작업들을 병렬적으로 처리 해 주면 됩니다.

 

안드로이드 애플리케이션은 처음 시작되면 런타임 시스템에서 하나의 스레드를 생성하게 되는데

모든 애플리케이션 컴포넌트는 기본적으로 그 스레드 내에서 실행됩니다. 그리고 이러한 스레드를 main thread라고 합니다.

 

main thread는 기본적으로 사용자 인터페이스 즉 UI에 대한 작업을 처리 합니다. 그런데 만약 이 메인 스레드를 사용하여 시간이 오래 걸리는 작업을 수행한다면 이 작업이 끝날 때까지 앱 전체가 멈춘 것처럼 보이게 됩니다. 그리고 이러한 경우 '애플리케이션이 응답하지 않음' 이라는 경고메시지를 보게 됩니다. 종종 보신적이 있을 겁니다. 따라서 이러한 일이 발생하지 않기위해 저러한 작업을 별도의 스레드에서 수행하여야 합니다.

 

따라서 안드로이드 개발의 중요한 규칙 중 하나는 main thread에서 시간이 오래 걸리는 작업을 절대로 수행하지 않는 것입니다. 그리고 두번째로는 main thread 외의 다른 thread 코드에서는 UI를 절대로 변경해서는 안 된다는 규칙입니다.

기본적으로 안드로이드 UI 툴킷은 thread safe 하지 않기 때문입니다.

 

따라서 다른 스레드에서 실행되는 코드가 UI와 상호작용할 필요가 있는 경우는 main thread와 synchronizing을 해서 사용 하여야 하고 이를 가능하게 하기 위하여 thread handler 가 필요합니다. 이 handler는 다른 스레드로부터 메시지를 받아 UI를 변경합니다.

 

handler 는 Handler 클래스의 서브 클래스로 구현 되어야 하며 handleMessage() 콜백 메서드를 오버라이딩 하여 사용합니다. handleMessage 메서드는 스레드에서 핸들러에게 메시지를 보낼 때 호출 됩니다.

 

예를 들어 mainThread가 아닌 어떤 스레드에서 textview를 바꾸고 싶다면 변경하고 싶은 string 을 담은 bundle을 handler에게 sendMessage를 통해서 전달 해주고 handleMessage에서 이를 처리해 주면 됩니다.

 

 

Thread -> handler.sendMessage -> handler의 handleMessage -> UI 처리

 

아래는 그러한 역할을 하는 간단한 예제 프로그램입니다. 버튼 클릭시 새로운 쓰레드를 생성하여 string 값을 handler에게 전달하면 handler에서 이를 받아 textview에 표시하게 됩니다.

class MainActivity : AppCompatActivity() {

    val handler = object: Handler() {
        override fun handleMessage(msg: Message) { //스레드가 핸들러에게 메시지를 보낼 때 호출됨
            val bundle = msg.data
            val string = bundle.getString("str")
            textview.text = string
            super.handleMessage(msg)
        }


    }


    public fun btnClick(view: View) {

        val runnable = Runnable { //run만 override 하므로 lambda로
            val msg = handler.obtainMessage()
            val bundle = Bundle()
            val str = "hi my message"
            bundle.putString("str", str)
            msg.data = bundle
            handler.sendMessage(msg) //UI 변경할 내용을 handelr에게 보낸다.
        }

        val myThread = Thread(runnable)
        myThread.start()
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

 

스레드는 간단하게 여기 까지 알아 보고 서비스로 넘어 가겠습니다.

 


Service

서비스는 스타트 서비스 / 바운드 서비스/ 인텐트 서비스 총 3가지로 나뉘게 됩니다.

 

스타트 서비스

 

스타트 서비스는 기본적으로 다른 컴포넌트(액티비티, 브로드캐스트 리시버) 에 의해 시작됩니다. 그리고 서비스가 중단되거나 시스템에 의해 소멸될 때까지 백그라운드로 무한정 실행됩니다.

 

자신을 시작시킨 앱이 더이상 포그라운드에 있지 않아도 실행되며 소멸된 경우에도 계속 실행이 됩니다. 그리고 기본적으로 자신이 시작된 앱의 프로세스와 동일한 메인 스레드에서 실행 됩니다.

 

따라서 CPU를 많이 사용하는 작업은 새로운 스레드에서 수행되도록 하여야 합니다.

 

스타트 서비스는 startService() 메서드를 호출하여 시작하며 이를 식별하는 인텐트 객체를 인자로 전달 하면 됩니다. 모든 작업이 완료된 후에는 stopSelf() 를 호출하여 자신을 중단 시켜야 합니다. stopService() 메서드를 호출하면 실행 중인 서비스를 다른 컴포넌트에서 중단 시킬 수 있습니다. (해당 서비스와 일치하는 인텐트를 인자로 전달)

 

 

인텐트 서비스

 

인텐트 서비스는 앞에서 처럼 CPU를 많이 사용하는 작업 수행시 새로운 스레드에서 수행을 하여야 하는데 이때 사용 할 수 있습니다. 이 클래스는 비동기 작업들을 처리 할 때 아주 편하게 구현을 할 수 있습니다. 기본적으로 백그라운드 작업을 처리하는 worker스레드를 정하고 비동기 방식으로 하나씩 요청들을 처리합니다. 모든 요청을 처리한 후 자동으로 중단 됩니다. IntentService 클래스는 각 요청을 실행하는

onHandleIntent() 메서드만 구현하면 되므로 다른 서비스에 비해 아주 간단하게 사용 할 수 있습니다.

 

그러나 동시에 처리해야 하는 작업들이 존재한다면 인텐트 서비스의 사용이 불가능 하고 이때는 직접 스레드를 구현하고 관리하여야 합니다.

 

바운드 서비스

 

바운드 서비스는 스타트 서비스와 유사한데 차이 점은 결과를 반환하며 자신을 실행 시킨 컴포넌트와 상호작용이 가능하다는 것입니다. IPC(inter-Process Communication)의 구현을 통해 프로세스간 상호작용도 가능합니다.

 

한 클라이언트가 bindService() 메서드를 호출하면 바운드 서비스에 binding이 됩니다. 이런 식으로 동시에 여러 컴포넌트가 서비스에 바인딩 할 수있습니다. 클라이언트에서 서비스가 더 이상 필요하지 않을 경우 unbindService() 메서드를 호출하여야 합니다. 모든 클라이언트가 서비스에서 바인딩을 해제하면 이 서비스는 종료됩니다.

 

그리고 바운드 서비스는 startService() 의 호출로도 시작 될 수 있습니다. startServicec에 의해 서비스가 시작되고 클라이언트 들이 bindService로 이 서비스에 바인딩 할 수 있습니다. 그러나 위에서 말했듯 스타트 서비스는 stopSelf() / stopService() 를 통해서 직접 중단 시켜줘야 합니다.

 

바운드 서비스는 onBind() 메서드의 구현을 포함해야 합니다. 서비스 최초 생성시 / 클라이언트들이 서비스에 바인딩할 때 onBind() 메서드가 자동 호출 됩니다. 목적은 클라이언트들에게 IBinder 객체를 반환하는 것입니다. 이 객체를 통해 클라이언트는 서비스와 상호작용이 가능합니다.

 

로컬통신( 같은 프로세스 내)의 경우는 Binder 클래스로부터 서브 클래스를 생성하고 IBinder를 반환함으로써 서비스 구현이 가능하고 만약 서로 다른 프로세스간 통신을 해야 한다면 Messenger와 Handler가 필요하다.

 


Service LifeCycle

서비스는 위와 같은 생명주기를 가진다. 스타트 서비스 / 바운드 서비스가 서로 다른 것을 볼 수 있다.

 

- onStartCommand() : startService()  메서드를 호출해서 서비스를 시작 할 때 호출 됨.

 

- onBind() :bindService() 메서드를 호출하여 서비스에 바인딩 할 때 호출 됨. 바운드 서비스의 경우 IBinder 객체를 반환 해야 하며 스타트 서비스의 경우는 null 값을 반환하도록 구현해야 함.

 

- onCreate() : 서비스 생성 시 호출

 

- onDestroy() : 서비스 소멸 시 호출

 

- onHandleIntent() : IntentService 의 경우에만 적용, 각 서비스를 처리함.

 

 

onStartCommand() 메서드에서는 정수값을 기본적으로 반환 하는데, 이는 안드로이드 런타임 시스템에 의해 서비스가 소멸될 경우 서비스를 어떻게 할 것인지 정의 하는 값입니다.

 

START_NOT_STICKY / START_STICKY / START_REDELIVER_INTENT 총 3가지의 값이 존재하며

 

- START_NOT_STICKY: 전송을 기다리는 인텐트가 없다면 서비스가 소멸될 때 다시 시작 하지 않음.

 

- START_STICKY: 서비스가 소멸된 후 가능한 한 빨리 다시 시작되어야 함을 나타냄.

 

- START_REDELIVER_INTENT: 현재의 인텐트를 onStartCommand() 메서드에 재전송하여 그 서비스가 다시 시작되어야 함을 나타냄.

 

서비스가 그리 중요하지 않다면 START_NOT_STICKY를 반환 음악 재생과 같이 오랫동안 실행 되는 서비스의 경우는 START_STICKY를 반환하는 것이 좋습니다. 각 서비스의 실질적 구현은 다음 포스팅에서 이어 가겠습니다

반응형