
Android 어플리케이션의 컴포넌트가 시작될 때 해당 어플리케이션에 실행 중인 다른 컴포넌트가 없다면, Android 시스템은 해당 어플리케이션을 위해 단일 스레드가 포함된 새로운 리눅스 프로세스를 실행시킨다.
기본값으로, 동일 어플리케이션 내부의 모든 컴포넌트는 동일한 프로세스와 동일한 스레드에서 실행되며, 해당 스레드를 main thread라고 한다.
ㅤ
만약 특정 어플 컴포넌트가 시작될 때 이미 해당 어플의 프로세스가 존재한다면, 이는 동일한 어플 내의 다른 컴포넌트가 이미 시작되었다는 뜻이며, 이로 인해 후속으로 시작된 컴포넌트는 이전에 이미 실행된 프로세스와 스레드에서 작업을 실행한다.
하지만 개발자는 동일 어플 내의 다른 컴포넌트를 별개의 프로세스에서 실행시킬 수 있으며, 어떠한 프로세스에서도 추가적인 스레드를 생성할 수 있다.
ㅤ

기본값으로, 어플리케이션의 모든 컴포넌트는 동일한 프로세스에서 실행된다. 대부분의 어플은 이를 유지한다.
그러나 개발자가 특정 컴포넌트가 어떠한 프로세스에 속하도록 설정해야 한다면, Android Manifest 파일에서 설정할 수 있다.
ㅤ
컴포넌트 요소의 각 타입인 Manifest 항목으로는 <activity>, <service>, <receiver>, <provider>가 있다.
(receiver = broadcast receiver, provider = content provider)
이 항목들에서 android:process 속성을 지원한다.
이 속성은 해당 컴포넌트가 실행될 프로세스를 명시한다. 크게 세 가지 방식으로 사용 가능하다.
ㅤ
Manifest 파일의 <application> 요소에서도 android:process 속성을 지원한다. 기본값을 모든 컴포넌트에 적용할 수 있다.
Android 시스템은 우선순위가 더 높은 프로세스의 실행을 위한 자원이 필요할 때 기존 프로세스를 종료시킬 수 있다. 종료된 프로세스에서 실행 중인 어플 컴포넌트들은 결과적으로 파괴된다. 해당 컴포넌트들이 실행되어야 할 일이 생기면 종료된 프로세스는 다시 시작된다.
Android 시스템은 각 프로세스의 상대적 중요도에 따라 어떤 프로세스를 종료할지 결정한다.
프로세스의 중요도는 컴포넌트의 상태에 의존한다. 컴포넌트의 상태가 디바이스 화면에서 보이는(visible) 상태라면 해당 컴포넌트의 프로세스는 높은 중요도를 갖고, 더 이상 보이지 않는 상태의 컴포넌트가 속한 프로세스는 좀 더 쉽게 종료될 수 있다.
ㅤ


어플이 실행되면, Android 시스템은 해당 어플 실행을 위한 쓰레드를 생성하며, 이를 main 쓰레드라고 한다. main 쓰레드는 이벤트를 적절한 유저 인터페이스 위젯에 전달 (dispatch) 하고, 이벤트를 반영 (drawing) 하는 역할을 한다.
거의 대부분의 경우 main 쓰레드에서 Android UI 툴킷의 컴포넌트와 상호작용을 한다. 이러한 이유 때문에 main 쓰레드는 UI 쓰레드 라고도 불린다.
(특수한 경우에 main 쓰레드가 UI 쓰레드가 아닌 경우가 있다.)
ㅤ
Android 시스템은 각 컴포넌트의 객체마다 분리된 쓰레드를 생성하지 않는다. 동일한 프로세스에서 실행되는 모든 컴포넌트는 UI 쓰레드에서 초기화되며, 각각의 컴포넌트의 시스템 호출 (system call) 은 UI 쓰레드로부터 전달된다. 결과적으로, 시스템 콜백 (system callbacks) 에 반응하는 메서드는 항상 동일한 프로세스의 UI 쓰레드에서 실행된다.
(시스템 콜백에 반응하는 메서드 = 시스템의 특정 시점 실행에 상위 레벨의 메서드 구현체를 실행)
ㅤ
예시
ㅤ
앱을 적절하게 구현할 지라도, 이러한 단일 스레드 모델은 유저 상호작용에 따른 무거운 작업을 수행할 때 성능 저하를 일으킬 수 있다. 네트워크 접근 또는 데이터베이스 쿼리 (요청) 과 같은 긴 작업을 UI 쓰레드에서 실행하면 전체적인 UI 가 차단 (block) 된다. UI 스레드가 차단되면 그리는 이벤트를 포함한 어떠한 이벤트도 전달되지 못한다.
유저 관점에서 UI 쓰레드가 차단되면 어플이 멈춘 것처럼 보인다. 만약 UI 쓰레드가 몇 초 이상 차단되면, ANR (Application Not Responding) 에러 문구가 나타난다. 이는 유저가 해당 어플을 종료하거나 삭제하게 만드는 원인이 될 수 있다.
ㅤ
Android UI 툴킷은 thread-safe 하지 않음을 숙지해야 한다. 그러므로 worker 쓰레드로 부터 UI 를 조작해서는 안된다. UI 쓰레드에서 유저 인터페이스를 조작해야 한다.
(thread-safe = 여러[멀티] thread 로부터 접근해도 안전)
ㅤ
Android 단일 스레드 모델의 두 가지 규칙은 다음과 같다.


단일 쓰레드 모델의 이유로 UI 쓰레드를 막지 않는다는 어플 UI 의 대응은 필수적이다. 만약 작업의 수행이 즉각적이지 않다면, 해당 작업을 개별적인 background 또는 worker 쓰레드에서 실행시키도록 한다.
기억해야 할 점은, UI 또는 main 스레드가 아닌 다른 어떠한 스레드에서도 UI를 업데이트할 수 없다는 것이다.
이 규칙을 따르는 것을 돕기 위해, Android 는 다른 쓰레드에서 UI 쓰레드에 접근할 수 있는 몇 가지 방법을 제공한다.
그러나, 작업의 복잡성이 증가함에 따라, 위의 worker thead 사용만으로 유지하는데 복잡함과 여러움이 있다. 더 복잡한 worker 스레드와의 상호작용을 다루기 위해, UI 스레드로부터 전달된 메시지를 처리할 수 있도록 worker 스레드 내에서 Handler를 사용하는 것을 고려할 수 있다.
ㅤ

어떤 상황에서는 개발자가 구현한 메서드가 여러 스레드로부터 호출될 수 있으며, 이 경우 해당 메서드는 반드시 스레드 안전(thread-safe)하게 작성되어야 한다.
이는 주로 바운드 서비스(bound service)의 메서드처럼 원격으로 호출될 수 있는 메서드들에 해당한다.
예를 들어, 서비스의 onBind() 메서드는 해당 서비스 프로세스의 UI 스레드에서 호출되는 반면, onBind()가 반환하는 객체(RPC, 원격 프로시저 호출, 메서드를 구현하는 서브클래스)에 정의된 메서드들은 스레드 풀에서 호출된다. 서비스는 둘 이상의 클라이언트를 가질 수 있으므로, 스레드 풀 내의 여러 스레드가 동일한 IBinder 메서드에 동시에 참여(접근) 가능하다. 따라서 IBinder 메서드는 반드시 스레드 안전(Thread-safe) 하게 구현되어야 한다.
Service 실행 종류
Service 컴포넌트의 사용 방법은 두 가지로 나뉜다.
- 호출하여 실행 (start service)
- 다른 컴포넌트와 결합하여 동시에 실행 (bind service)
Bound Service 의 경우, 결합하는 다른 컴포넌트가 별개의 어플리케이션에 존재하는 경우 RPC 방식 (IPC) 를 사용
유사하게, Content Provider 는 다른 프로세스에서 발생하는 데이터 요청을 받을 수 있다 (호출 프로세스에서 데이터 작업을 위해 인자값을 넘겨주거나 결과를 반환받음). ContentResolver 와 ContentProvider 클래스는 프로세스 간 통신(IPC)이 관리되는 구체적인 방식을 숨겨주지만, 요청에 응답하는 ContentProvider의 메서드들(query(), insert(), delete(), update(), getType())은 프로세스의 UI 스레드가 아닌 스레드 풀(Thread Pool) 내의 스레드에서 호출된다. 이러한 메서드들은 동시에 여러 스레드로부터 호출될 수 있으므로, 반드시 Thread-safe 하게 구현되어야 한다.
상단 예시 정리
특정 기능의 메서드를 호출할 때, 호출하는 측과 호출되는 메서드가 동일한 프로세스 내에 있다면 해당 메서드는 호출한 측의 스레드에서 즉시 실행된다.
하지만 호출하는 측과 호출되는 메서드가 서로 다른 프로세스 사이에서 발생한다면, 호출되는 측의 시스템(해당 메서드를 갖는 별도의 프로세스) 은 자신이 관리하는 별도의 스레드 풀(thread pool)에서 가용 스레드를 하나 선택하여 해당 기능을 실행한다.
ㅤ

Android 는 RPC 를 사용하여 IPC 매커니즘을 제공한다. RPC는 메서드가 Activity 또는 다른 어플 컴포넌트에 의해 호출되는지만 다른 프로세스에서 원격으로 실행되며, 이에 대한 결과를 호출자에게 전달된다.
이는 메서드를 분해하고 OS 레벨에서 이해할 수 있는 데이터를 수반한다. 이는 로컬 프로세스와 해당 주소 공간에서 원격 프로세스와 그 주소 공간에 전송하는 것이며, 호출자 측에서 재결합하고 재구현하는 것이다.
반환되는 값들은 반대 방향으로 전송된다. 안드로이드는 IPC 처리를 수행하도록 하는 모든 코드를 제공하므로, 개발자는 RPC 프로그래밍 인터페이스를 정의하고 구현하는 것에 집중할 수 있다.
IPC 를 실행시키기 위해, 어플리케이션은 bindService() 메서드를 이용하여 service와 결합(bind) 해야 한다.
Android는 RPC를 사용하는 IPC 메커니즘을 제공한다. RPC에서는 메서드가 액티비티나 다른 앱 컴포넌트에 의해 호출되지만 실행은 다른 프로세스에서 원격으로 이루어지며, 그 결과가 호출자에게 반환된다.
안드로이드는 이러한 IPC 트랜잭션을 수행하는 모든 코드를 제공하므로, 개발자는 RPC 프로그래밍 인터페이스를 정의하고 구현하는 것에만 집중할 수 있다. IPC를 수행하려면 애플리케이션은 bindService()를 사용하여 서비스에 결합 (bind) 해야 한다.
Processes and threads overview | App quality | Android Developers
Processes and app lifecycle | App architecture | Android Developers