2012년 6월 27일 수요일

Handler story 1/2



Android Handler Story
-Handler & Looper-

Handler 와 Looper : http://huewu.blog.me/110115454542
Messenger Service : http://huewu.blog.me/110116293622

시작하기에 앞서
 안드로이드의 Handler 클래스 자주 사용하시나요? Handler 는 안드로이드 어플리케이션 프레임워크 상에서 제공되는 가장 유용한 클래스의 하나로, Message 나 Runnable 오브젝트를 한 곳에서 다른 곳으로, 이리 저리 전달하고 처리하는 역할을 수행합니다. 한 곳에서 발생한 힘을 다른 곳으로 매끄럽게 전달해주는, 마치 톱니바퀴와 같은 역할이라고 할까요? 실재 많은 어플리케이션 개발자 분들이 별도의 스레드에서 다운로드 받은 이미지를 UI 에 표시하는 등의 기능을 구현하고자 할 때, AsyncTask 와 더불어 자주 사용하시는 클래스가 아닐까 싶네요.

 그런데, 이 Handler 클래스에는 많은 분들이 간과하고 계신 깨알 같은 비밀이 숨겨져 있습니다.두 번의 포스트에 걸쳐서 이와 관련된 이야기를 풀어보려고 하는데요, 그 첫번째로 Handler 의 동작 원리를 살펴보며, 사람들의 뇌리에서는 어느샌가 사라져버린 비운의 여인.Looper 에 관한 이야기를 해보려고 합니다.

1. Handler 의 동작 원리.
 모든 이야기에는 훌륭한 삽화가 필요한 법. 이번 포스트를 통해 풀어볼 이야기를 한 장의 그림으로 표현해 보았습니다. 낑낑대며, Handler 의 동작 방식을 나름대로 정리해 본 그림입니다. 

 Handler 는 말그대로 무언가를 처리하는 녀석입니다. 이 녀석이 처리하는 대상은 두 가지인데, 하나는 Message 오브젝트 이고 다른 하나는 Runnable 오브젝트 입니다. Runnable 오브젝트는 run() 메서드를 호출하여 처리하고, Message 는 handleMessage() 메서드를 호출하여 처리합니다. (앞으로 두 개  합쳐 메세지로 통일하도록 하겠습니다.) 그리고 개발자는 handleMessage() 메서드를 오버라이드 하여, 원하는 메세지를 처리하는 루틴을 구현 할 수 있습니다.

 하지만, 이 설명 만으로는 부족한 점이 있습니다. Handler 가 처리하는 메세지는 어디로 부터 와서, 어떻게 Handler 에 전달 되는 것일 까요? 수수께끼의 답은 바로 그림에 숨어 있습니다.
  • 메세지는 바로 '어떤 스레드'에 속해있는 MessageQueue 에서 나옵니다.
  • MessageQueue 에 메세지를 넣을 때는, 바로 Handler 의 sendMessage() 를 사용합니다.
  • Looper 는 MessageQueue 의 메세지를 꺼내 Handler 에 전달해 줍니다.
  • 그제서야 Handler 는 handleMessage 를 통해 메세지를 처리합니다.
 말하자면 Handler 는 메세지가 들어가는 입구이자 출구 역할을 수행하며, 이를 위한 두 종류의 메서드를 갖고 있는 클래스라고 할 수 있겠습니다. 입구 역할을 메서드는 일반적으로 Handler 가 속한 스레드가 아닌 외부 스레드에서 호출되며, 출구 역할을 하는 메서드는 항상 Handler 가 속한 스레드 내부에서 호출 되게 됩니다. 

<Looper 없는 Handler 는 무용 지물. 어미새 없는 아기새에 불과합니다.>

 바로 그렇기 때문에, Handler 는 결코 혼자 존재 할 수 없습니다. 연결된 MessageQueue 가 없는 Handler 는 그야말로 밑빠진 독. 대답업는 메아리라고 할 수 있습니다. MessageQueue 에서 메세지를 꺼내 넘겨줄 Looper 가 없는 Handler 는 어미 새 없는 아기 새라고 할 수 있겠지요. (굶어죽을 운명입니다.) 결과적으로 모든 Handler 는 항상 특정 스레드와 연결되어 있어야 하며, 그리고 그 스레드는에는 메세지를 담을 수 있는 MessageQueue 와 해당 메세지를 Handler 로 전달해줄 Looper 가 존재해야 합니다. 

2. Handler 생성자
 다시 강조하지만, Handler 는 결코 홀로 존재할 수 없습니다. 그런데, Handler 를 단독으로 사용가능한 클래스로 착각하는 개발자들이 종종 있습니다. 가장 큰 원인은 안드로이드 개발 팀이 개발자 편의를 위해, Handler 의 기본 생성자 (Constructor) 를 제공 해주고 있기 때문입니다. 
Handler handler = new Handler();
handler.sendEmptyMessage(100);
 바로 위의 구문 처럼 말입니다. 혼자 살 수 없는 Handler 가 어떻게 아무런 인자도 없이 바로 생성되어 사용될 수 있을까요. (심지어 위 구문은 정상적으로 동작합니다.) Handler 의 기본 생성자에 관하여, 안드로이드 개발자 문서의 관련 내용이 있습니다. 요약해 보면 이렇습니다. 
기본 생성자를 통해 Handler 를 생성자면, 새롭게 생성된 Handler 는 해당 Handler 의 생성자가 호출된 바로 그 스레드의 MessageQueue 그리고 Looper 에 자동으로 연결됩니다.
 알고보니 간단한 이야기이조? 그렇기 때문에 아래와 같이, 어플리케이션 메인 스레드 상에서 Handler 를 생성 한 후, 별도의 스레드에서 해당 Handler 를 통해 메세지를 전달하면(아래서는 Runnable 오브젝트) 해당 구문은 어플리케이션의 메인 UI 스레드에서 동작하게 됩니다.
public class Sample extends Activity{
Handler mHandler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler();
  
Thread t = new Thread(new Runnable(){
@Override
public void run() {
//여기서 UI 작업을 수행하면 Exception 발생 함.
mHandler.post(new Runnable(){
@Override
public void run() {
//여기서는 UI 작업 가능.
}
});
}
});
t.start();
}
};
3. Looper 양의 기구한 사연.
 만일 Handler 와 연결된 Thread 를 명시적으로 지정하고 싶을 때는, 제공되는 Handler (Looper looper) 생성자를 사용하실 수도 있습니다. 그런데, 이때 사용되는 Looper 클래스는 정확히 어떤 역할을 수행할까요?
ERROR/AndroidRuntime(15412): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
 만일 정상적인 Looper 가 없는 스레드에서 Handler 를 생성하려고 할 때는, 위와 같은 예외가 발생하게 됩니다. 그렇다면 Looper 는 무슨 일을 할까요? Looper 는 한 마디로 말해 하나의 스레드를 점유하여(하나의 스레드는 하나의 Looper 만 갖을 수 있습니다. Looper 도 오직 하나의 스레드에만 연결될 수 있구요.) MessageQueue 만을 바라보는 해바라기 입니다. MessageQueue 가 비어 있는 동안은 아무일도 안하고 계속 잠만 자고 있다가, 메세지가 들어오면 해당 메세지를 꺼내 적절한 Handler 로 전달해 주지요. 이 역할을 끊도 없이 반복적으로 수행하기 때문에 Looper 라는 이름이 붙었습니다.

 따라서, 이 Looper 가 동작하고 있는 스레드는 메세지를 처리하는 일 외에 다른 일을 수행할 수가 없습니다. 그리고 하나의 메세지를 처리하는데 너무 오랜 시간이 걸리는 작업을 수행해서도 안됩니다. 하나의 메세지를 처리하는 동안에는 해당 Looper 를 향해 전달된 다른 메세지들은 처리되지 못하고, MessageQueue 에 산더미 처럼 쌓이고 자신의 순서가 돌아오기만을 기다리게 되기 때문입니다. 만일 어플리케이션 메인 스레드의 Looper 에서 이런 일이 일어난다면? 네.. 많은 개발자들을 우울하게 만드는 ANR(Application Not Responding) 예외가 발생하게됩니다. (메인 스레드가 아니면 ANR 이 발생하지 않습니다~~)

<하나의 Looper 는 다 수의 Handler 와 연결될 수 있습니다.>

  이 처럼 MessageQueue 와 스레드에는 일편단심인 Looper 이지만, Handler 를 상대할 때는 조금 콧대가 높아집니다. 하나의 Handler 는 오직 하나의 Looper 에만 연결 될 수 있습니다. 반면에, 하나의 Looper 는 여러 Handler 를 거느릴 수 있지요. 예를 들어, 어플리케이션 UI 스레드에는 사용자 키 입력등의 이벤트를 처리하기 위한 Handler 가 이미 존재함에도 불구하고 개발자들이 원하는 수 만큼 새로운 Handler 를 추가로 생성 할 수 있습니다. 이게 가능한 이유는, 바로 Looper 로 전달되는 메세지들은 반드시 개별 Handler 의 sendMessage 혹은 post 메서드를 통해 전달되며, 이 때, 모든 메세지에는 해당 메세지가 어떤 Handler 부터 전달되어 온 메세지인지 원산지 표시가 되어 있기 때문입니다. 따라서 Looper 는 결코 목적지를 헷갈리지 않고 정확한 Handler 에 메세지를 전달해 줄 수 있습니다.

4. Looper 를 만드는 두 가지 방법.
 바로 위에서, Looper 를 포함하고 있는 스레드만이 Handler 와 연동될 수 있다고 알려드렸습니다. 그렇다면 Looper 를 포함한 스레드란게 도데체 어떤 스레드를 말하는 걸 까요? 안드로이드상에서는 Looper 를 포함한 스레드를 만드는 두 가지 방법을 제공해 주고 있습니다.

첫 번째는 임의의 스레드에서 직접 Looper 를 생성하는 방법입니다.
Thread t = new Thread(new Runnable(){
@Override
public void run() {
Looper.prepare();
handler = new Handler();
Looper.loop();
}
});
t.start();  
 특정 스레드 내부에서 Looper.prepare() 메서드를 통해 MessageQueue 를 우선 준비 한 후, 원하는 Handler 를 생성합니다. 그리고 run() 메서드의 마지막에 잊지 마시고, Looper.loop() 메서드를 호출 하시면, MessageQueue 에 메세지가 전달되기를 기다리는 Looper 의 하품나는 작업이 시작되게 됩니다. 이 때, 한 가지 주의 점. loop 메서드는 기본적으로 무한 루프를 도는 작업임으로, 생성된 스레드는 외부 스레드에서 Looper 의 quit 메서드를 호출해 주지 않는 이상 종료되지 않을 것 입니다.

 그리고 두 번째는 이 보다 훨씬 편리한 방법. 안드로이드에서 제공하는 HandlerThread 클래스를 활용하는 방법입니다. 
HandlerThread t = new HandlerThread("My Handler Thread");
t.start();
handler = new Handler(t.getLooper());
 HandlerThread 는 기본적으로 Looper 를 갖고 있으며, getLooper() 메서드를 통해 포함된 Looper 를 얻어오거나, quit() 메서드를 통해 Looper 의 무한 루프를 정지시킬 수 있는 메서드를 제공해 줍니다. (단, 이 quit() 메서드는 MessageQueue 에서 새로운 메세지를 꺼내 오는 루프를 중단할 뿐이며, 연결되어 있는 Handler 단에서 진행중인 작업이 중간에 종료되는 것은 아닙니다.) 엄청 편리합니다. 그러니 명시적인 Looper 가 필요할 경우에는 무조건 HandlerThread 를 애용하시면 좋겠습니다,

결론
 Handler 는 굉장히 유용하지만, 혹시라도 실수를 하게 되면 전혀 예상할 수 없는 결과을 일으킬 수 있는 위험성을 갖고 있는 클래스입니다. (주요 스레드를 너무 오래 점유하게 되면, 성능과 직결된 문제가 발생 할 수 있기 때문에 그렇습니다.)  Handler 는 혼자서는 결코 존재할 수 없는 클래스라는 점을 꼭 명심하시고, 여러분이 생성한 Handler 가 과연 어떤 Thread 의 Looper 및 MessageQueue 에 연결되어있는지 확인하시면 좋겠습니다. 그리고 앞으로 이어질 Handler 이야기 제 2 탄. 멀리 멀리 떠나는 Message. Messenger Service 편도 기대해 주시기 바랍니다~

댓글 없음:

댓글 쓰기