Skip to main content

[번역] A journey on the Android Main Thread - Part 1

본 문서는 square engineering blog 에 기재된 A journey on the Android Main Thread - Part 1 기사를 번역한 것 입니다.

coding horrer 에 왜 우리는 소스 읽는 법 을 배워야 하는가 에 대한 기사 가 있습니다.
안드로이드의 가장 큰 특징 중 하나는 오픈소스 생태계 라는 점 입니다.

PSVM (public static void main)

public class BigBang {
 
public static void main(String... args) {
   
// The Java universe starts here.
 
}
}

모든 자바 프로그램은 public static void main() 메소드를 호출하면서 시작합니다.
이는 자바 데스크탑 프로그램, JEE 서블릿 컨테이너, 안드로이드 애플리케이션 이 모두 동일 합니다.

안드로이드 시스템은 부팅 단계에서 ZygoyteInit 이라 불리는 리눅스 프로세스를 실행합니다. 이 프로세스는 달빅VM 으로, 쓰레드에 안드로이드 SDK 의 대부분의 클래스 를 로드 하고 대기합니다.

새로운 안드로이드 애플리케이션을 시작할 때, 안드로이드 시스템은 ZygoteInit 프로세스를 포크 하게 됩니다. 포크된 자식 프로세스의 쓰레드는 대기를 해제하고, ActivityThread.main() 메소드를 호출합니다.



위키피디아 에 정의된 zygote 란 수정란을 의미합니다.

Loopers

계속 진행하기 앞서, 우리는 Looper (이하 루퍼) 클래스를 살펴 볼 필요가 있습니다.
루퍼를 사용하는 것은 하나의 쓰레드가 메시지들을 연속해서 실행하도록 하는 좋은 방법 입니다.

각각의 루퍼는 메시지 객체의 큐를 지니고 있습니다. (이를 메시지 큐 MessageQueue 라고 합니다.)

루퍼는 큐에있는 각각의 메시지를 실행하고, 큐가 비었을 때에는 정지(차단)하는 loop() 메소드를 지니고 있습니다.

Looper.loop() 메소드는 아래와 유사합니다.

void loop() {
 
while(true) {
   Message message
= queue.next(); // blocks if empty.
   dispatchMessage
(message);
   message
.recycle();
 
}
}

각각의 루퍼는 하나의 쓰레드에 연결되어 있습니다. 새로운 루퍼를 생성하고 현제 쓰레드에 연결하기 위해서는 여러분은 반드시 Looper.prepare() 메소드를 호출해야 합니다.

루퍼들은 Looper 클래스안에 스테틱으로 정의된 ThreadLocal 에 저장 됩니다.
여러분은 Looper.myLooper() 메소드를 호출함으로 써 현제 쓰레드에 연결된 루퍼 를 검색할 수 있습니다.

HandlerThread 클래스는 여러분들을 위해 이 모든것을 대신해 줍니다.

HandlerThread thread = new HandlerThread("SquareHandlerThread");
thread
.start(); // starts the thread.
Looper looper
= thread.getLooper();

코드는 다음과 같습니다.

class HandlerThread extends Thread {
 Looper looper
;
 
public void run() {
   Looper
.prepare(); // 루퍼를 생성하고 ThreadLocal 에 저장 합니다.
   looper
= Looper.myLooper(); // Retrieve the looper instance from the ThreadLocal, for later use.
   Looper
.loop(); // Loop forever.
 
}
}

Handlers

핸들러는 루퍼와 매우 잘 어울립니다.

핸들러는 두가지 목적을 지니고 있습니다:

  • 다른 쓰레드로 부터 루퍼의 메세지 큐에 메세지를 전달 합니다.
  • 그 루퍼와 연결된 쓰레드에서 루퍼의 대기열의 메시지를 처리합니다.

// 각각의 핸들러는 하나의 루퍼와 연결되어 있습니다.
Handler handler
= new Handler(looper) {
 
public void handleMessage(Message message) {
   
// Handle the message on the thread associated to the given looper.
   
if (message.what == DO_SOMETHING) {
     
// do something
   
}
 
}
};
// Create a new message associated to that handler.
Message message
= handler.obtainMessage(DO_SOMETHING);
// Add the message to the looper queue.
// Can be called from any thread.
handler
.sendMessage(message);

여러분은 하나의 루퍼에 여러 핸들러를 연결할 수 있습니다. 루퍼는 message.target 에 메시지를 전달 합니다.

핸들러를 사용하는 인기있고 간편한 방법은 Runnable 을 전달하는 것 입니다.

// Create a message containing a reference to the runnable and add it to the looper queue
handler
.post(new Runnable() {
 
public void run() {
   
// Runs on the thread associated to the looper associated to that handler.
 
}
});

또한 핸들러는 루퍼를 제공하지 않고도 생성이 가능합니다.

// DON'T DO THIS
Handler handler
= new Handler();

인자가 없는 핸들러 생성자는 Looper.myLooper() 를 호출하게 되고, 현재 쓰레드와 연결된 루퍼를 검색하게 됩니다.
이는 여러분인 핸들러로 연결하고 싶은 쓰레드일 수도 있고 아닐수도 있습니다.

대개, 여러분은 단지 메인 쓰레드에 전달하는 핸들러를 생성하고 싶으실 것 입니다.

Handler handler = new Handler(Looper.getMainLooper());

Back to PSVM

다시 ActivityThread.main() 을 살펴봅시다. 기본적으로 수행하는 것은 아래와 같습니다.

public class ActivityThread {
 
public static void main(String... args) {
   Looper
.prepare();

   
//이제 여러분은 Looper.getMainLooper() 를 호출함으로써 언제든지 메인 루퍼를 검색 할 수 있습니다.
   Looper
.setMainLooper(Looper.myLooper());

   
// 루퍼에 첫번째 메시지를 전달합니다.
   
// { ... }

   Looper
.loop();
 
}
}

이제 여러분은 쓰레드가 메인 쓰레드에서 호출되는 이유에 대해 이해하셨을 것 입니다.

Note : 예상하신 바와 같이, 메인 쓰레드가 첫번째로 할 동작 중 하나는 Application 을 생성하고 Application.onCreate() 를 호출 하는 것 입니다.

다음 시간에는 안드로이드 생명주기와 메인 쓰레드 간의 관계에 대해 살펴 보면서, 어떻게 복잡한 버그를 만들어 내는지도 살펴 볼 것 입니다.

Comments

Popular posts from this blog

[Android Application Testing Guide] Chapter3 jar file

Android Application Testing Guide(에이콘) 3장 Sample 을 실행시키기 위해서는 libdummy-0.0.1.jar 가 필요한데, sample file 에는 프로젝트만 존재한다. 결국 jar 를 만들어야 되는데.. ant 빌드다. 어허허... jar 파일이 없으면 예제 진행이 어려우므로. ant build 한 jar 를 첨부. libdummy-0.0.1.jar download

[Cobertura] cobertura 2.0.2 script fix

cobertura 2.0.2 버전을 다운받고 실행 시키려고 하면 엄청난 에러가 뿜어져 나온다. ㅠ 달리 설정을 틀리게 한 것도 없는데.. 하면서 script 를 열어보니 아래와 같이 작성되어 있다. - cobertura-instrument.sh java -cp `dirname $0`/cobertura.jar:`dirname $0`/lib/asm-3.3.1.jar:`dirname $0`/lib/asm-tree-3.3.1.jar:`dirname $0`/lib/asm-commons-3.3.1.jar:`dirname $0`/lib/log4j-1.2.9.jar:`dirname $0`/lib/jakarta-oro-2.0.8.jar net.sourceforge.cobertura.instrument.Main $* - cobertura-instrument.bat java -cp "%COBERTURA_HOME%cobertura.jar;%COBERTURA_HOME%lib\asm-3.3.1.jar;%COBERTURA_HOME%lib\asm-tree-3.3.1.jar;%COBERTURA_HOME%lib\asm-commons-3.3.1.jar;%COBERTURA_HOME%lib\log4j-1.2.9.jar;%COBERTURA_HOME%lib\jakarta-oro-2.0.8.jar" net.sourceforge.cobertura.instrument.Main %CMD_LINE_ARGS% 그런데 막상 다운받은 folder 구조를 보면.. cobertura-2.0.2.jar /lib     - asm-4.1.jar     - asm-commons-4.1.jar     - sam-tree-4.1.jar     - oro-2.0.8.jar .... script 왜 이렇게 되있지... 결국 cobertura 를 실행 시키기 위해서는 script 에서 버전 및 파일명을 다운받은 폴더...