차근차근/Android

안드로이드 어플리케이션 기초4 - 프로세스와 쓰레드

예쁜꽃이피었으면 2014. 7. 29. 00:40

http://wikiware-textcube.blogspot.kr/2009/12/4-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B8%B0%EC%B4%88.html

 

 

 

4.4 프로세스와 쓰레드

 

(1) 프로세스(process)

 

어떤 어플리케이션의 컴포넌트들 중 하나가 최초로 실행될 때, 안드로이드는 실행 쓰레드(thread) 하나를 가진 리눅스 프로세스(process)를 하나를 생성합니다. 기본적으로 이 어플리케이션의 모든 다른 컴포넌트들은 새로 생성된 이 프로세스와 쓰레드(메인 쓰레드라고 불림)에서 동작을 합니다. 컴포넌트가 다른 프로세스에서 실행되게 하려면 그 프로세스에 추가적인 쓰레드를 만들어서 실행해야 합니다.

 

어떤 컴포넌트가 어떤 프로세스에서 동작할 지는 매니패스트 파일에서 제어가 됩니다.<activity>, <service>, <receiver>, <provider> 요소의 process 속성으로 제어를 할 수 있습니다. process 속성으로 컴포넌트가 자신의 프로세스에서 동작할 지, 다른 프로세스에서 동작할 지 결정할 수 있습니다. 또한 리눅스 사용자  ID와 권한을 자신 것으로 할 지 다른 프로세스 것으로 할 지 결정할 수 있습니다. 각 컴포넌트에 개별적으로 속성을 지정하지 않고 <application> 요소의 process 속성으로 모든 컴포넌트에 적용되는 디폴트 값을 설정할 수도 있습니다. 

모든 컴포넌트는 해당 프로세스의 메인 쓰레드에서 인스턴스화됩니다. 시스템 호출도 메인 쓰레드에서 동작합니다. 따라서 각 컴포넌트는 시스템에 의해서 불렸을 때는 시간이 오래 걸리거나 블럭킹(blocking)이 되는 연산(예, 네트웍 연산 또는 계산 루프)은 메인 쓰레드에서 수행하지 않도록 주의해야 합니다. 시간 오래 걸리는 것은 별도 쓰레드를 생성하는 것이 좋습니다.

 

안드로이드가 프로세스를 죽이는 경우는 메모리가 부족한 경우이거나 사용자에게 더 즉각적으로 서비스해야 하는 다른 프로세스의 요구가 있을 경우에 한합니다. 이런 상황이 닥쳤을 때 어떤 프로세스를 죽일지는 중요도에 따라 결정이 됩니다. 예를 들면 사용자에게 보이는 것과 보이지 않는 것이 있으면 보이지 않는 것이 중요도가 더 낮습니다. 안드로이드는 중요도가 낮은 보이지 않는 프로세스를 먼저 죽이게 됩니다. 중요도는 프로세스 상에서 동작하고 있는 컴포넌트의 상태에 따라서 달라집니다. 이에 대해서는 추후 다시 설명 드리겠습니다.

 

(2) 쓰레드(thread)

 

어플리케이션이 하나의 프로세스 상에서 동작하더라도 백그라운드 작업의 경우는 별도의 쓰레드를 만들어서 수행할 필요가 있다고 앞에서 설명하였습니다. 쓰레드를 만드는 방법은 표준 자바 Thread 객체를 사용하면 됩니다. 안드로이드는 쓰레드 관리를 쉽게 하도록 여러 클래스를 제공하고 있습니다. 예를 들면, 쓰레드 내에서 메시지 루프를 실행하기 위한Looper, 메시지 처리를 위한 Handler, 메시지 루프를 가진 쓰레드를 만들기 위한HandlerThread 등이 있습니다.

 

여러 쓰레드에서 동시에 실행되어도 문제가 없이 동작해야 하는 것을 쓰레드에 안전(thread-safe)하다고 합니다. 하나 이상의 쓰레드에서 호출될 수 있는 메소드는 쓰레드에 안전하게 작성되어야 합니다. 특히 원격에서 호출될 수 있는 메소드는 쓰레드에 안전하게 작성될 필요가 있습니다. 

 

(3) RPC(Remote Procedure Call)

 

안드로이드는 경량의 RPC 메커니즘을 제공하고 있습니다. RPC는 지역 메소드 호출로 원격 프로세스의 메소드를 실행하여 그 결과를 반환받는 것입니다. 좀 더 자세히 설명하면, RPC는 운영체제가 이해할 수 있는 수준으로 메소드 호출과 관련 데이터를 쪼개서 다른 원격 프로세스에게 전송한 후에 다시 조립하여 원격에 있는 메소드를 호출하게 됩니다. 반환 값도 이런 식으로 쪼갠 후 조립하여 지역 메소드에 원격 메소드 호출 결과로 전달됩니다. 안드로이드가 이런 동작 메커니즘을 다 준비해 놓았으므로 개발자는 RPC 인터페이스 구현만 신경 쓰면 됩니다.

 

RPC 인터페이스는 메소드들로만 구성되어 있습니다. 여기에 정의된 모든 메소드는 동기화되어 실행이 됩니다. 메소드가 동기화되어 실행된다는 의미는 지역 메소드 호출은 원격 메소드의 실행이 종료될 때까지 블럭된다는 뜻입니다. 반환 값이 없더라도 동기화가 이루어집니다. RPC 인터페이스는 IDL(Interface Definition Language)로 선언됩니다. 안드로이드 SDK에서 제공되는 aidl 도구를 이용하여 IDL을 생성할 수 있습니다. RPC 인터페이스는 아래 그림과 같이 2 개의 내부 클래스로 정의됩니다.

 

 

 

 

 

위 그림에서 Stub 내부 클래스는 원격에서 제공해야 하는 서비스 기능입니다. 개발자가 구현해 주어야 하는 클래스이죠. 점선 박스로 표시된 내부 클래스는 IBinder를 구현한 것으로 안드로이드에 의해 사용되는 클래스입니다.

 

서비스와 클라이언트 간의 연결을 설정하는 방법에 대해 알아 보겠습니다. 우선 클라이언트에서는 원격에 있는 서비스와 연결이 성공하거나 실패하면 알림을 받을 수 있도록onServiceConnected()와 onServiceDisconnected()를 구현해야 합니다. 서비스 연결에 성공했을 때는 onServiceConnected() 메소드가 호출되고, 서비스 연결이 실패했을 때는 onServiceDisconnected() 메소드가 호출됩니다. 서비스 연결에 성공하여onServiceConnected() 메소드가 호출되면 이 메소드에서 bindService() 메소드를 호출하여 연결을 설정하면 됩니다. 반면에 서비스에서도 해 줘야 할 일이 있습니다. 서비스에서는 onBind() 메소드를 구현해 줘야 합니다. 이 메소드애서는 클라이언트에서 연결을 시도해 왔을 때 이를 수용할 지 거부할 지 결정할 수 있습니다. 이런 결정을 내릴 때onBind() 메소드의 인자로 전달되는 인턴트를 보고 결정할 수 있습니다. 만약 클라이언트의 연결 시도가 받아들여지면 Stub 하위 클래스의 인스턴스가 반환됩니다. 서비스가 연결을 수용하면 안드로이드는 onServiceConnected() 메소드를 호출하여 IBinder 객체를 클라이언트에 전달합니다.  IBinder 객체는 서비스에 의해 관리되는 Stub 하위 클래스에 대한 프락시(proxy) 역할을 합니다. 이 프락시를 통해서 클라이언트는 원격 서비스를 호출할 수 있습니다. 앞에서도 설명했지만  IBinder 메소드들은 쓰레드에 안전하게 구현이 되어야 합니다.

 

4.5 컴포넌트의 수명

 

어플리케이션의 컴포넌트는 수명이 있습니다. 다시 말해서 컴포넌트는 태어나서 죽게 됩니다. 안드로이드가 인스턴스를 만들 때 생명이 시작됩니다. 그리고 안드로이드가 인스턴스를 삭제할 때 생명이 끝나게 됩니다. 컴포넌트는 태어나서 죽기까지 활성화되기도 하고 비활성화도기도 합니다. 다시 말해, 활성화될 때는 사용자에게 보이고 비활성화될 때는 사용자에게 안 보이게 됩니다.

 

(1) 액티비티의 수명

 

액티비티도 컴포넌트 중의 하나이므로 생명을 가지고 있고 수명도 있습니다. 액티비티가 살아 있는 동안 다음 3 가지 상태에 있을 수 있습니다.

 

  • 활성(active) 또는 실행 중(running) 상태
    액티비티가 포그라운드 상태에 있을 때입니다. 다시 말해서 현재 태스크 스택의 꼭대기에 위치해 있을 때입니다. 이 상태에서는 사용자의 동작에 대해서 포커스를 가지게 됩니다.
  • 일시 정지(paused) 상태
    포커스를 잃지만 여전히 보이는 상태에 있을 때입니다. 다른 액티비티가 이 액티비티 위에 일부 또는 투명하게 덮은 경우에 해당합니다. 일시 정지 상태에서는 안드로이드가 메모리가 부족하면 컴포넌트를 죽일 수 있습니다.
  • 멈춤(stopped) 상태
    다른 액티비티에 의해 완전히 가려진 때입니다. 이럴 때는 사용자에게 더 이상 보이지 않는 경우입니다. 멈춤 상태에서도 안드로이드가 메모리가 부족하면 컴포넌트를 죽일 수 있습니다.

 

액티비티 수명 관련 메소드

 

액티비티의 위 3 가지 상태 간의 전이가 일어나면 안드로이드는 다음과 같은 액티비티의 보호된(protected) 메소드를 불러서 상태 변경을 알려 줍니다.

 

  • void onCreate(Bundle savedInstanceState)
  • void onStart()
  • void onRestart()
  • void onResume()
  • void onPause()
  • void onStop()
  • void onDestroy()

 

위 메소드들은 재정의가 가능한 메소드들입니다. 따라서 상태 전이가 있을 때 무언가 일을 하려면 위 메소드들을 재정의하면 됩니다. 예를 들어서 액티비티가 인스턴스화 된 후에 초기화 코드를 구현해 주고 싶다면 onCrate() 메소드를 재정의하면 됩니다. 일시 정지 상태에서 시스템에 의해 죽는 경우에 복구를 할 수 있도록 영구적인 데이터를 스토리지에 저장할 필요가 있을 때는 onPause() 메소드를 재정의하면 됩니다.

 

액티비티 상태 전이 다이어그램

 

액티비티의 전체 수명은 onCreate()와 onDestroy() 사이의 시간입니다. 눈에 보이는 상태에 있는 경우는 onStart()와 onStop() 사이의 시간입니다. 포그라운드(foreground) 상태에 있는 경우는 onResume()과 onPause() 사이의 시간입니다. 다음 그림은 액티비티의 탄생에서부터 죽음에 이르는 과정과 이 사이의 여러 상태 전이 과정을 보이는 다이어그램입니다.

 

 

 

 

위 다이어그램에서 보면 액티비티가 탄생하여 인스턴스화가 되면 onCreate()가 불리고, 눈에 보이는 상태가 되면 onStart()가 불리고, 포그라운드로 되면서 onResume()이 불립니다. 이 상태가 활성화되어 실행 중인 상태입니다. 그리고 잠들어서 백그라운드가 되면서 일시 정지되어 onPause()가 불리고, 눈에 보이지 않게 되면서 멈추게 되어 onStop()이 불리고, 죽으면서 onDestroy()가 불립니다. onPause()나 onStop() 상태에서는 시스템에 의해서 죽음을 당할 수 있고 사용자가 이 액티비티로 되돌아 오면 부활을 하여onCreate()가 다시 불립니다. 백그라운드에서 잠들어 있는 상태에서 포그라운드로 깨어난 상태가 되면서, 다시 말해 멈춤 상태에서 실행 상태로 바뀌면서 onRestart()onStart()가 연속으로 불립니다.

 

액티비티의 상태 저장


시스템이 액티비티를 죽을 수 있으므로, 사용자가 죽었던 액티비티로 되돌아 왔을 때 이전 상태를 복원할 수 있어야 합니다. 이전 상태를 복원하려면 onSaveInstance()라는 메소드를 구현하면 됩니다. 안드로이드는 액티비티를 죽이기 전에 이 메소드를 호출하여 나중에 부활 후 복원에 필요한 정보를 저장할 기회를 줍니다. onSaveInstance()에 인자로Bundle 객체를 전달해 줍니다. 여기에 이름-값 쌍을 저장하면 됩니다. 액티비티가 다시 부활하여 다시 시작할 때 Bundle 객체가 onCreate() 메소드에 다시 전달됩니다. 또한onStart()가 불린 후에 불리는 OnRestoreInstanceState() 함수에도 전달됩니다.onSaveInstance()라는 메소드는 액티비티가 죽을 때 항상 불리는 것이 아니고, 시스템에 의해서 죽을 때만 불리므로, 데이터를 영구적으로 저장할 목적으로 이 함수 이용하지는 말아야 합니다. 이런 목적을 위해서는 앞에서 설명했듯이 onPause() 함수에서 구현해야 합니다.


상태 전이 시 액티비티들의 호출 순서


어떤 액티비티가 다른 액티비티를 호출 시 두 액티비티는 상태가 변하게 됩니다. 상태가 변하면서 불리는 메서드의 순서를 잘 알고 있어야 합니다. 먼저, 현재 액티비티의 onPause()메소드가 호출됩니다. 그리고 신규 액티비티의 onCreate()onStart()onResume() 메소드가 순서대로 불립니다. 마지막으로 신규 액티비티가 보이지 않게 되면, 이 액티비티의onStop() 메소드가 호출됩니다.

 

(2) 서비스의 수명

 

서비스를 시작하는 2 가지 방법

 

서비스는 2 가지 방법으로 시작하거나 중지될 수 있습니다. 서비스가 시작된 후에 다른 누군가가 중지하거나 스스로 중지할 수 있습니다. 다른 서비스를 시작할 때는 Context.startService() 메소드를 호출합니다. 다른 서비스를 중지시킬 때는Context.stopService() 메소드를 호출합니다.

 

반면에 자기 스스로 서비스를 중지할 때는 Service.stopSelf() 또는Service.stopSelfResult() 메소드를 호출하면 됩니다. startService()는 여러 서비스에서 여러 번 불릴 수 있습니다. stopService()를 불렀다고 해서 서비스가 중지되는 것은 아닙니다. 여러 서비스에서 서비스를 시작한 횟수만큼 stopService()가 불려야 중지가 됩니다. 그렇다고 stopService()를 여러 번 부르면 안 되고 startService()를 불렀다면 한 번만 부르면 됩니다.


서비스를 동작시키는 두 번째 방법은 외부 노출된 인터페이스를 이용하는 것입니다. 클라이언트가 Service 객체에 연결한 후에, 이 연결을 이용하여 서비스를 호출하는 방법이 있습니다. 서비스와 연결 맺기와 연결 끊기를 위해서 호출되는 메소드는 다음과 같습니다.

 

  • 연결 맺기 : Context.bindService() 호출
  • 연결 끊기 : Context.unbindService() 호출

 

이렇게 하여 여러 클라이언트가 하나의 서비스에 바인드(bind)될 수 있습니다. 서비스에 바인드되기 위해서는 다음과 같이 호출을 해야 합니다.

 

  • 서비스 바인드 : bindService() 호출

 

예를 들어, startService()에 인텐트 객체를 넘겨서 음악을 연주한 후에, 나중에 현재 연주 중인 곡에 대한 정보를 알고 싶다면 bindService()를 호출하여 액티비티가 서비스와 연결을 맺을 수 있습니다. 이런 경우는 stopService()를 불러도 실제 서비스가 중지되지 않을 수 있습니다. bindService()가 여러 번 불렸을 수 있기 때문이며 마지막 바인딩이 끊어질 때에야 비로소 서비스가 중지됩니다.

 

서비스의 수명 관련 메소드


서비스의 수명에 관련된 3 가지 메소드는 다음과 같습니다. 서비스는 액티비티와는 달리 아래 3 개 외에는 없습니다. 서비스에는 onStop()과 같은 메소드가 없는 것에 특히 주의바랍니다.

 

  • void onCreate()
  • void onStart(Intent intent)
  • void onDestroy()

 

서비스의 탄생에서 죽음까지의 수명은 onCreate()와 onDestroy() 사이입니다.Context.startService() 또는 Context.bindService()가 불려서 서비스가 시작될 때는 항상 이 2 개 메소드가 호출됩니다. 서비스는 onStart() 메소드가 호출되면 활성 상태가 되었다가 이 메소드가 끝나면 비활성 상태가 됩니다. 클라이언트가startService() 메소드를 불러 서비스를 시작할 때 인자로 전달한 인텐트 객체가 서비스의 onStart() 메소드에 전달됩니다. 이 인자를 보고 어떤 서비스를 할 지 알 수 있습니다. 예를 들면, 인텐트 인자를 보고 연주할 음악이 무엇인지 알 수 있습니다. 이 메소드는startService()로 시작된 서비스에서만 호출됩니다. 

 

바인드된 서비스에서 구현해야 콜백 메소드

 

서비스에서 구현해야 할 3 개의 콜백(callback) 메소드가 있습니다.
 

  • IBinder onBind(Intent intent)
    이 메소드의 인자는 bindService()에 전달된 인텐트 객체입니다. 반환 값은 클라이언트가 서비스와 인터랙션을 하는데 사용할 통신 채널입니다.
  • boolean onUnbind(Intent intent)
    이 메소드의 인자는 unbindService()에 전달된 인텐트 객체입니다.
  • void onRebind(Intent intent)
    새로운 클라이언트가 서비스에 연결하는 경우 onUnbind()에서 onRebind()를 부릅니다.

 

다음 그림은 콜백 메소드 호출을 설명한 다이어그램입니다. 서비스가 startService()로 시작된 경우와 bindService()로 시작된 경우에 각각 호출되는 메소드나 콜백 함수가 다릅니다. 바인드된 서비스에는 onStart() 메소드가 없는 대신 위 3 개의 콜백 함수가 있습니다.

 

 

 

 

(3) 브로드캐스트 수신자의 수명

 

브로드캐스트 수신자는 다음과 같이 하나의 콜백 메소드만 제공합니다.

 

  • void onReceive(Context curContext, Intent broadcastMsg)

 

브로드캐스트 메시지가 수신자에게 도착하면 안드로이드가 onReceive() 메소드를 호출합니다. 이 때 메시지를 담고 있는 인텐트 객체를 인자로 전달합니다. 브로드캐스트 수신자는 onReceive() 함수가 호출될 때만 활성화되고, 이 함수가 끝나면 다시 비활성화됩니다. 활성화 상태의 브로드캐스트 수신자를 가지고 있는 프로세스는 시스템에 의해서 죽임을 당하지 않습니다.
 

(4) 컴포넌트의 중요도 순위

 

안드로이드는 메모리가 부족할 때 죽일 프로세스를 선택합니다. 어떤 프로세스를 죽일지는 컴포넌트의 “5 단계 중요도 계층 구조”로 결정합니다. 계층 구조에서 중요도 순위가 가장 낮은 것이 먼저 죽임을 당합니다. 1 순위부터 5 순위까지 중요도 계층 구조는 다음과 같습니다.

 

  • 1 순위 : 포그라운드 프로세스
    사용자가 현재 사용 중인 프로세스로 포그라운드 상태에 있는 것들이 중요도 1 순위입니다. 사용자가 상호작용 중인 액티비티를 가진 프로세스가 여기에 속합니다. 사용자가 상호작용 중인 액티비티에 바인드된 서비스를 가진 프로세스도 여기에 속합니다. 수명 관련된 콜백 함수들(onCreate(), onStart(), onDestroy()) 중 하나를 실행 중인 서비스 객체를 가진 프로세스도 여기에 속합니다. 그리고 onReceive() 메소드를 실행 중인 BroadcastReceiver객체를 가진 프로세스도 여기에 속합니다.
  • 2 순위 : 보이는 프로세스
    포그라운드 컴포넌트를 가지고 있지 않으나 화면에 보이는 것들이 중요도 2 순위입니다. 포그라운드 상태는 아니나 사용자에게 여전히 보이는 상태의 액티비티를 가진 프로세스가 여기에 속합니다. onPause()가 불린 경우로, 이전 화면이 보이게 대화상자가 뜬 경우가 이런 상태입니다. 보이는 액티비티에 바인드된 서비스를 가진 프로세스도 여기에 속합니다.
  • 3 순위 : 서비스 프로세스
    startService() 
    메소드로 시작된 서비스 중 실행 중인 프로세스들이 중요도 3 순위입니다. 단, 위 1~2 순위에 속하지 않는 것들만 해당됩니다. 예를 들면, 음악을 연주 중인 서비스, 네트웍에서 다운로들 하고 있는 서비스가 여기에 속합니다.
  • 4 순위 : 백그라운드 프로세스
    보이지 않는 액티비티를 가진 프로세스들이 중요도 4 순위입니다. onStop()으로 중지된 것들이 여기에 속합니다. 이런 액티비티는 특히 많기 때문에 가장 오래 전에 사용된 것들부터 즉, LRU(Least Recently Used)로 죽임을 당합니다.
  • 5 순위 : 텅빈 프로세스
    활성화된 어플케이션 컴포넌트가 없는 프로세스들이 중요도 5 순위입니다. 이런 프로세스들은 대부분 프로세스 시작 시간을 줄이기 위해 캐쉬된 프로세스들이 여기에 속합니다.

 

끝.


반응형