애플리케이션 구성 요소가 시작되고 애플리케이션에 실행 중인 다른 구성 요소가 없으면 Android 시스템은 하나의 실행 스레드로 애플리케이션의 Linux 프로세스를 시작합니다. 기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행됩니다("기본" 스레드라고 합니다). 애플리케이션 구성 요소가 시작되었는데 (애플리케이션의 다른 구성 요소가 존재하기 때문에) 해당 애플리케이션의 프로세스가 이미 존재할 경우, 해당 구성 요소는 프로세스 내에서 시작되고 같은 실행 스레드를 사용합니다. 하지만 애플리케이션 내의 여러 가지 구성 요소가 각자 별도의 프로세스에서 실행되도록 할 수도 있고, 어느 프로세스에나 추가 스레드를 만들 수 있습니다.
이 문서는 프로세스와 스레드가 Android 애플리케이션에서 작동하는 방식을 설명합니다.
프로세스
기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행되고, 대부분의 애플리케이션은 이를 바꿔서는 안 됩니다. 그러나 어느 프로세스가 특정 구성 요소에 속하는지 확인해야 할 경우 매니페스트 파일에서 확인할 수 있습니다.
각 유형의 구성 요소(<activity>
, <service>
, <receiver>
및 <provider>
)에 대한 매니페스트 항목은 이 구성 요소가 실행되는 프로세스를 지정할 수 있는 android:process
특성을 지원합니다. 이러한 특성을 설정하여 각 구성 요소를 자체 프로세스에서 실행하거나 다른 구성 요소를 제외한 일부 구성 요소만 프로세스를 공유하게 할 수 있습니다. 또한, android:process
를 설정하여 다른 애플리케이션의 구성 요소를 동일한 프로세스에서 실행할 수도 있습니다. 단, 이는 애플리케이션이 동일한 Linux 사용자 ID를 공유하고 동일한 인증서로 서명되었을 경우에 한합니다.
<application>
요소도 android:process
특성을 지원하여, 모든 구성 요소에 적용되는 기본값을 설정합니다.
Android는 어느 시점에서 프로세스를 종료하기로 결정할 수도 있습니다. 즉 메모리가 부족하거나, 사용자에게 더욱 즉각적인 서비스를 제공하는 다른 프로세스가 이 프로세스를 중단해야 하는 경우 등이 있습니다. 그러면 중단된 프로세스에서 실행되고 있던 애플리케이션 구성 요소도 따라서 소멸됩니다. 그와 같은 구성 요소에 수행할 작업이 다시 생기면 그에 대한 프로세스도 다시 시작됩니다.
어느 프로세스를 종료할지 결정할 때, Android 시스템은 사용자에 대한 이들의 상대적 중요성을 가늠합니다. 예를 들어, 눈에 보이는 액티비티를 호스팅하는 프로세스와 비교하여 화면에 보이지 않는 액티비티를 호스팅하는 프로세스를 쉽게 종료할 수 있습니다. 프로세스 종료를 결정하는 것은 해당 프로세스에서 실행되는 구성 요소의 상태에 따라 달라집니다.
프로세스 수명 주기, 그리고 프로세스 수명 주기와 애플리케이션 상태의 관계에 대한 자세한 내용은 프로세스 및 애플리케이션 수명 주기에서 설명합니다.
스레드
애플리케이션이 시작되면 시스템이 애플리케이션에 대한 실행의 스레드를 생성하며, 이를 "기본"이라고 합니다. 이 스레드는 드로어블 이벤트를 포함하여 적절한 사용자 인터페이스 위젯에 이벤트를 발송하는 역할을 맡기 때문에 중요합니다. 대부분의 경우 이것은 Android UI 도구 키트의 구성 요소(android.widget
과 android.view
패키지의 구성 요소)와 개발자의 애플리케이션이 상호작용하는 스레드입니다. 따라서 기본 스레드는 UI 스레드라고 불릴 때도 있습니다. 그러나 특수한 상황에서 앱의 기본 스레드가 UI 스레드가 아닐 수도 있습니다. 자세한 내용은 스레드 주석을 참조하세요.
시스템은 구성 요소의 각 인스턴스에 대해 별도의 스레드를 생성하지 않습니다. 같은 프로세스에서 실행되는 모든 구성 요소는 UI 스레드에서 인스턴스화되고 각 구성 요소에 대한 시스템 호출은 해당 스레드에서 발송됩니다. 따라서 시스템 콜백에 응답하는 메서드(사용자 작업을 보고하는 onKeyDown()
또는 수명 주기 콜백 메서드)는 항상 프로세스의 UI 스레드에서 실행됩니다.
예를 들어 사용자가 화면의 버튼을 터치하면, 앱 UI 스레드가 위젯에 터치 이벤트를 발송하고, 위젯은 눌린 상태를 설정한 뒤 이벤트 큐에 무효화 요청을 게시합니다. UI 스레드가 이 요청을 큐에서 제거하고 위젯에 위젯을 다시 그려야 한다고 알립니다.
앱이 사용자 상호작용에 응답하여 리소스를 많이 소모하는 작업을 수행하는 경우, 이 단일 스레드 모델은 애플리케이션을 제대로 구현하지 않으면 낮은 성능을 보일 수 있습니다. 특히 모든 것이 UI 스레드에서 발생할 경우 네트워크 액세스나 데이터베이스 쿼리 등의 긴 작업을 수행할 때 전체 UI가 차단됩니다. 스레드가 차단되면 드로잉 이벤트를 포함하여 모든 이벤트가 발송되지 않습니다. 사용자에게는 애플리케이션이 중단된 것처럼 보입니다. 더욱 심각한 경우, UI 스레드가 몇 초 이상 차단되면(현재 약 5초) 사용자에게 "애플리케이션이 응답하지 않습니다"(ANR)라는 악명 높은 대화상자가 표시됩니다. 그러면 사용자가 애플리케이션을 종료할 수도 있고, 불만족한 경우 앱을 제거할 수도 있습니다.
또한, Android UI 도구 키트는 스레드로부터 안전하지 않습니다. 따라서 UI를 작업자 스레드에서 조작해서는 안 됩니다. 사용자 인터페이스 조작 작업은 모두 UI 스레드에서 해야만 합니다. 결론적으로 Android의 단일 스레드 모델에는 단순히 두 가지 규칙이 있습니다.
- UI 스레드를 차단하지 마세요.
- UI 스레드 외부에서 Android UI 도구 키트에 액세스하지 마세요.
작업자 스레드
위에 설명한 단일 스레드 모델이 적용되기 때문에 애플리케이션 UI가 반응하기 위해서는 UI 스레드를 차단하지 않는 것이 매우 중요합니다. 수행은 해야 하지만 즉각적인 조치가 필요하지 않은 작업일 경우, 반드시 별도의 스레드에서 수행해야 합니다("백그라운드" 또는 "작업자" 스레드).
그러나 UI 스레드나 "기본" 스레드를 제외한 다른 스레드에서 UI를 업데이트할 수 없습니다.
이 문제를 해결하기 위해 Android는 다른 스레드에서 UI 스레드에 액세스하는 여러 가지 방식을 제공합니다. 다음은 몇 가지 유용한 메서드 목록입니다.
Kotlin
fun onClick(v: View) { Thread(Runnable { // a potentially time consuming task val bitmap = processBitMap("image.png") imageView.post { imageView.setImageBitmap(bitmap) } }).start() }
Java
public void onClick(View v) { new Thread(new Runnable() { public void run() { // a potentially time consuming task final Bitmap bitmap = processBitMap("image.png"); imageView.post(new Runnable() { public void run() { imageView.setImageBitmap(bitmap); } }); } }).start(); }
이 구현은 스레드로부터 안전합니다. 네트워크 작업은 별도의 스레드에서 수행되는 반면 ImageView
는 언제나 UI 스레드에서 조작되기 때문입니다.
그러나 작업이 복잡해질수록 이런 종류의 코드가 더 복잡해질 수 있고 유지관리하기 까다로워질 수 있습니다. 더 복잡한 상호작용을 작업자 스레드로 처리하려면, 작업자 스레드에서 Handler
를 사용하여 UI 스레드에서 전달받은 메시지를 처리하는 방안을 고려해보세요. 최선의 해결책은 AsyncTask
클래스를 확장하는 것일 수 있습니다. 이 방법은 UI와 상호작용해야 하는 작업자 스레드 작업의 실행을 단순화합니다.
AsyncTask 사용
AsyncTask
를 사용하면 사용자 인터페이스에서 비동기식 작업을 수행할 수 있습니다. 이것은 작업자 스레드에서 차단 작업을 수행하고 그런 다음 그 결과를 UI 스레드에 게시하므로 개발자가 직접 스레드 및/또는 핸들러를 처리할 필요가 없습니다.
이를 사용하려면 AsyncTask
를 하위 클래스로 지정한 다음, 백그라운드 스레드의 풀에서 실행되는 doInBackground()
콜백 메서드를 구현해야 합니다. UI를 업데이트하려면 onPostExecute()
를 구현해야 합니다. 이는 doInBackground()
에서 결과를 전달하고 UI 스레드에서 실행되므로, 안전하게 UI를 업데이트할 수 있습니다. 그런 다음 UI 스레드에서 execute()
를 호출하여 작업을 실행할 수 있습니다.
이 클래스를 사용하는 법을 완전히 숙지하려면 AsyncTask
참조 문서를 읽어보는 것이 좋습니다.
스레드로부터 안전한 메서드
구현하는 메서드가 하나 이상의 스레드에서 호출되는 경우도 있습니다. 따라서 이를 스레드로부터 안전하게 작성해야만 합니다.
이는 주로 원격으로 호출할 수 있는 메서드에 해당합니다. 예를 들어 바인딩된 서비스 내의 메서드 등이 있습니다. IBinder
에 구현된 메서드에 대한 호출이 IBinder
가 실행되는 동일한 프로세스에서 발생할 경우, 이 메서드는 호출자의 스레드에서 실행됩니다. 그러나 호출이 다른 프로세스에서 발생하면, 해당 메서드는 IBinder
와 동일한 프로세스에 유지되는 스레드 풀에서 선택된 스레드에서 실행됩니다(프로세스의 UI 스레드에서 실행되지 않습니다). 예를 들어 어떤 서비스의 onBind()
메서드는 해당 서비스 프로세스의 UI 스레드에서 호출되고, onBind()
가 반환하는 객체에서 구현된 메서드(예: RPC 메서드를 구현하는 하위 클래스)는 해당 풀 안의 여러 스레드에서 호출됩니다. 서비스에 클라이언트가 하나 이상 있을 수 있으므로 두 개 이상의 풀이 동시에 같은 IBinder
메서드에 참여할 수 있습니다. 그러므로 IBinder
메서드는 스레드로부터 안전하게 구현되어야 합니다.
마찬가지로 콘텐츠 제공자는 다른 프로세스에서 발생한 데이터 요청을 수신할 수 있습니다. ContentResolver
및 ContentProvider
클래스는 프로세스 간 통신의 세부적인 관리 정보는 숨기지만, 이러한 요청에 응답하는 ContentProvider
메서드(즉, query()
, insert()
, delete()
, update()
및 getType()
메서드)는 프로세스의 UI 스레드에서 호출되지 않고 콘텐츠 제공자 프로세스의 스레드 풀에서 호출됩니다. 이러한 메서드가 동시에 몇 개의 스레드에서 호출될 수 있으므로, 스레드로부터 안전하게 구현되어야 합니다.
프로세스 간 통신
Android는 원격 프로시저 호출(RPC)을 사용한 프로세스 간 통신(IPC) 메커니즘을 제공합니다. 여기서 메서드는 액티비티나 다른 애플리케이션 구성 요소에 호출되지만 원격으로 (또 다른 프로세스에서) 실행되고 결과는 모두 호출자에게 반환됩니다. 메서드 호출과 메서드의 데이터는 운영체제가 이해할 수 있는 수준으로 분해되어서, 로컬 프로세스와 주소 공간에서 원격 프로세스와 주소 공간으로 전송된 다음 다시 결합되어 여기서 호출에 다시 응답합니다. 그런 다음 반환 값이 반대 방향으로 전송됩니다. Android가 이와 같은 IPC 트랜잭션을 수행하는 데 필요한 모든 코드를 제공하므로, 개발자는 RPC 프로그래밍 인터페이스를 정의하고 구현하는 데만 집중하면 됩니다.
IPC를 수행하려면 bindService()
를 사용하여 애플리케이션을 서비스에 바인드해야 합니다. 자세한 내용은 서비스 개발자 가이드를 참조하세요.