본문 바로가기
Mobile App

[안드로이드] - Android Process & Thread

by Jman 2022. 7. 13.
애플리케이션 컴포넌트가 시작되고, 애플리케이션에 실행 중인 다른 컴포넌트가 없다면 안드로이드 시스템은 하나의 실행 스레드로 애플리케이션의 Linux 프로세스를 시작한다.

기본적으로 같은 애플리케이션의 모든 컴포넌트는 같은 프로세스와 스레드에서 실행된다. (기본 스레드)
애플리케이션 컴포넌트가 시작되었는데 해당 애플리케이션의 프로세스가 이미 존재하는 경우, 해당 컴포넌트는 프로세스 내에서 시작되고 같은 실행 스레드를 사용한다.
하지만 애플리케이션 내의 여러 가지 컴포넌트가 각자 별도의 프로세스에서 실행되도록 할 수도 있고, 어느 프로세스에나 추가 스레드를 만들 수 있다.

 

프로세스

각 유형의 컴포넌트에 대한 매니페스트 항목은 컴포넌트가 실행되는 프로세스를 지정할 수 있는 android : process 특성을 지원한다.

이러한 특성을 설정하여 각 컴포넌트를 자체 프로세스에서 실행하거나, 다른 컴포넌트를 제외한 일부 컴포넌트만 프로세스를 공유하게 할 수도 있다. 또한 android : process 를 설정하여 다른 애플리케이션의 컴포넌트를 동일한 프로세스에서 실행할수 있다.

단, 이는 애플리케이션이 동일한 Linux 사용자 ID를 공유하고 동일한 인증서로 서명되었을 경우에 한한다.

 

안드로이드 시스템은 어느 시점에서 프로세스를 종료하기로 결정할 수 있다.

  • 메모리 부족
  • 사용자에게 더욱 즉각적인 서비스를 제공하기 위해 다른 프로세스가 해당 프로세스를 중단 해야하는 경우
  • 등등..

중단된 프로세스에서 실행되고 있던 애플리케이션 컴포넌트도 따라서 소멸된다.

그와 같은 컴포넌트에 수행할 작업이 다시 생기면 그에 대한 프로세스도 다시 시작된다.

 

프로세스를 종료할 지 결정할 때, 안드로이드 시스템은 사용자에 대한 이들의 상대적 중요성을 가늠한다.

  • 눈에 보이는 액티비티를 호스팅하는 프로세스와 비교하여 화면에 보이지 않는 액티비티를 호스팅하는 프로세스를 쉽게 종료

종료를 결정하는 것은 해당 프로세스에서 실행되는 컴포넌트의 상태에 따라 다르다.

 

스레드

애플리케이션이 시작되면 안드로이드 시스템이 애플리케이션에 대한 실행의 스레드를 생성하며, 이를 "기본"이라고 한다.

이 스레드는 드로어블 이벤트를 포함하여 적절한 사용자 인터페이스 위젯에 이벤트를 발송하는 역할을 맡기 때문에 중요하다.

대부분의 경우 이 스레드는 안드로이드 UI 도구 키트의 컴포넌트(android.widget / android.view)개발자의 애플리케이션이 상호작용하는 스레드다.

따라서 기본 스레드는 UI 스레드라고 불릴 때도 있다. 그러나 특수한 상황에서 애플리케이션의 기본 스레드가 UI 스레드가 아닐 수도 있다. 

 

같은 프로세스에서 실행되는 모든 컴포넌트는 UI 스레드에서 인스턴스화되고 각 컴포넌트에 대한 시스템 호출은 해당 스레드에서 발송된다.

따라서 시스템 콜백에 응답하는 메서드는 항상 프로세스의 UI 스레드에서 실행된다.

*시스템 콜백에 응답하는 메서드 : 사용자 작업을 보고하는 onKeyDown() 또는 수명 주기 콜백 메서드

 

예를 들어,
사용자가 화면의 버튼을 터치하면, 애플리케이션 UI 스레드가 위젯에 터치 이벤트를 발송하고, 위젯은 눌린 상태를 설정한 뒤 이벤트 큐에 무효화 요청을 게시한다. UI 스레드가 이 요청을 큐에서 제거하고 위젯에 위젯을 다시 그려야한다고 알린다.

 

애플리케이션이 사용자 상호작용에 응답하여 리소스를 많이 소모하는 작업을 수행하는 경우?

이 단일 스레드 모델은 애플리케이션을 제대로 구현하지 않는다면 낮은 성능을 보일 수 있다.

특히 모든 것이 UI 스레드에서 발생할 경우, 네트워크 액세스데이터베이스 쿼리 등의 긴 작업을 수행할 때 전체 UI가 차단된다.

 

스레드가 차단되면 드로잉 이벤트를 포함하여 모든 이벤트가 발송되지 않는다.

사용자에게 애플리케이션이 중단된 것처럼 보일 것이다.

더욱 심각한 경우, UI 스레드가 몇 초 이상 차단되면 (현재 약 5초) 사용자에게 "애플리케이션이 응답하지 않습니다." (ANR) 라는 악명 높은 대화상자가 표시된다. 

그러면 사용자가 애플리케이션을 종료할 수도 있고, 불만족한 경우 앱을 제거할 수도 있다.

 

또한 Android UI 도구 키트는 스레드로부터 안전하지 않다.

따라서 UI를 작업자 스레드에서 조작해서는 안된다. 사용자 인터페이스 조작 작업은 모든 UI 스레드에서 해야만 한다.

결론적으로 Android의 단일 스레드 모델에는 단순히 두 가지 규칙이 있다

  1. UI 스레드를 차단하지 마라
  2. UI 스레드 외부에서 Android UI 도구 키트에 액세스하지 마라.

작업자 스레드

위에서 설명한 단일 스레드 모델이 적용되기 때문에 애플리케이션 UI가 반응하기 위해서는 UI 스레드를 차단하지 않는 것이 매우 중요하다.

수행은 해야 하지만 즉각적인 조치가 필요하지 않은 작업일 경우, 반드시 별도의 스레드에서 수행해야 한다.

  • 백그라운드
  • 작업자 스레드

 

그러나 UI 스레드나 "기본" 스레드를 제외한 다른 스레드에서 UI를 업데이트할 수 없다.

이 문제를 해결하기 위해서 안드로이드 시스템은 다른 스레드에서 UI 스레드에 엑세스하는 여러가지 방식을 제공한다.

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)

 

fun onClick(v: View) {
    Thread(Runnable {
        // a potentially time consuming task
        val bitmap = processBitMap("image.png")
        imageView.post {
            imageView.setImageBitmap(bitmap)
        }
    }).start()
}

위 코드 구현은 스레드로부터 안전하다.

네트워크 작업은 별도의 스레드에서 수행되는 반면 ImageView 는 언제나 UI 스레드에서 조작되기 때문이다.

그러나 작업이 복잡해질수록 이런 종류의 코드가 더 복잡해질 수 있고 유지관리하기 까다로워질 수 있다.

 

더 복잡한 상호작용을 작업자 스레드로 처리하려면, 작업자 스레드에서 Handler를 사용하여 UI 스레드에서 전달받은 메시지를 처리하는 방안을 고려해보면 좋다. 최선의 해결책은 AsyncTask 클래스를 확장하는 것이다.

AsyncTask 방법은 UI와 상호작용해야 하는 작업자 스레드 작업의 실행을 단순화한다.

 

AsyncTask

AsyncTask 를 사용하면 사용자 인터페이스에서 비동기식 작업을 수행할 수 있다.

 

이것은 작업자 스레드에서 차단 작업을 수행하고 그런 다음 그 결과를 UI 스레드에 게시하므로

개발자가 직접 스레드 또는 핸들러를 처리할 필요가 없다.

 

이를 사용하려면,
AsyncTask 를 하위 클래스로 지정한 다음, 백그라운드 스레드의 풀에서 실행되는 doInBackground() 콜백 메서드를 구현해야 한다.
UI를 업데이트 하려면 onPostExecute() 를 구현해야 한다.
이는 doInBackground() 에서 결과를 전달하고 UI 스레드에서 실행되므로, 안전하게 UI를 업데이트할 수 있다.
그런 다음 UI 스레드에서 execute() 를 호출하여 작업을 실행할 수 있다.

 

 

프로세스 간 통신

Android 시스템은 원격 프로시저 호출(RPC)를 사용한 프로세스 간 통신(IPC) 메커니즘을 제공한다.

 

여기서 메서드는 액티비티나 다른 애플리케이션 컴포넌트에 호출되지만,

원격으로 (또 다른 프로세스에서) 실행되고 결과는 모두 호출자에게 반환된다.

메서드 호출과 메서드의 데이터는 운영체제가 이해할 수 있는 수준으로 분해되어서, 로컬 프로세스와 주소 공간에서 원격 프로세스와 주소 공간으로 전송된 다음 다시 결합되어 여기서 호출에 다시 응답한다.

그런 다음 반환 값이 반대 방향으로 전송된다.

 

Android 시스템이 이와 같은 IPC 트랜잭션을 수행하는 데 필요한 모든 코드를 제공하므로, 개발자는 RPC 프로그래밍 인터페이스를 정의하고 구현하는데 만 집중하면 된다.

 

IPC를 수행하려면 bindService() 를 사용하여 애플리케이션을 서비스에 바인드해야 한다.

 

 

 

https://developer.android.com/guide/components/processes-and-threads?hl=ko 

 

프로세스 및 스레드 개요  |  Android 개발자  |  Android Developers

프로세스 및 스레드 개요 애플리케이션 구성 요소가 시작되고 애플리케이션에 실행 중인 다른 구성 요소가 없으면 Android 시스템은 하나의 실행 스레드로 애플리케이션의 Linux 프로세스를 시작

developer.android.com