관리 메뉴

드럼치는 프로그래머

[안드로이드] 안드로이드의 스레드(Thread) - B. 핸들러 본문

★─Programing/☆─Android

[안드로이드] 안드로이드의 스레드(Thread) - B. 핸들러

드럼치는한동이 2011. 11. 3. 16:25

B. 핸들러

 

핸들러(Handler)

 

  한 스레드는 그 내부의 연산만 가능하며 다른 스레드의 UI를 건드릴 수 없습니다. 그런데 만약 스레드들이 서로 영향을 줄 수 없다면 스레드의 존재 이유가 없겠죠.

 

 이런 서로 다른 스레드 간의 참조를 위해서 스레드 간에 통신할 수 있는 장치를 만들었는데 그것이 핸들러입니다. 핸들러는 스레드 간에 메시지 객체나 러너블 객체를 통해 통신할 수 있는 장치이며, 하나의 핸들러는 하나의 스레드와 관련을 맺습니다. 핸들러는 자신이 생성된 스레드에 짝이 되며 다른 스레드와 통신을 수행하게 됩니다.

 

핸들러의 메시지 수령

 

핸들러에 메시지가 도착하게 되면 아래의 메서드가 호출됩니다.

public void handleMessage(Message msg)

인수로 메시지 객체를 전달받는데 이는 스레드 간에 통신을 해야 할 내용에 관한 객체입니다. 몇 가지 정보가 추가 될 수 있기 때문에 여러 개의 필드를 가지고 있습니다.

 

메시지의 필드

설명

int what

메시지의 ID라고 볼 수 있다.

int arg1

메시지가 보낼 수 있는 정보이다.

int arg2

메시지가 보낼 수 있는 또 다른 정보이다.

Object obj

Integer로 표현 불가능 할 경우 객체를 보낸다.

Messenger replyTo

응답을 받을 객체를 지정한다.

 

핸들러의 메시지 전송

 

메시지를 전송할 때는 다음의 메서드를 사용합니다.

 

boolean Handler.sendEmptyMessage(int what)

메시지의 ID에 해당하는 값을 전달할 때 사용할 수 있습니다.

 

boolean Handler.sendMessage(Message msg)

ID만으로 불가능하고 좀 더 내용이 있는 정보를 전송할 때 사용합니다.

 

boolean sendMessageAtFrontOfQueue(Message msg)

메시지가 큐에 순서대로 쌓여서 FIFO(First In First Out)형태로 처리되지만 이 메서드를 사용하면 노래방에서 우선예약 하듯이 사용가능합니다.

 

핸들러의 발송과 수령 예제

 

public class HandlerTest extends Activity {

                  int mMainValue = 0;

                  int mBackValue = 0;

                  TextView mMainText;

                  TextView mBackText;

 

                  public void onCreate(Bundle savedInstanceState) {

                                   super.onCreate(savedInstanceState);

                                   setContentView(R.layout.thread_threadtest);

 

//두 개의 텍스트 뷰와 하나의 버튼을 준비합니다.

//버튼을 클릭할 경우 클릭 리스너는 mMainValue를 1씩 증가 시키게 되지요.

//그리고 그 값은 MainValue 텍스트 뷰에 출력이 되도록 작성이 되어 있습니다.

 

                                   mMainText = (TextView)findViewById(R.id.mainvalue);

                                   mBackText = (TextView)findViewById(R.id.backvalue);

                                   Button btn = (Button)findViewById(R.id.increase);

                                   btn.setOnClickListener(new Button.OnClickListener() {

                                                     public void onClick(View v) {

                                                                       mMainValue++;

                                                                       mMainText.setText("MainValue : " + mMainValue);

                                                     }

                                   });

//디폴트로 스레드를 선언했군요. 그렇다면 아래에 스레드를 extends로 상속 받은 클래스가 있겠지요.

//이후 스레드를 메인 스레드와 종료 동기화 시키고, 스레드를 시작시킵니다.

 

                                   BackThread thread = new BackThread();

                                   thread.setDaemon(true);

                                   thread.start();

                  }

// 서브 스레드에서는 1초마다 mBackValue를 1씩 증가 시킵니다.

// 도중에 핸들러로 메시지를 전송하는게 보입니다.

// sendEmptyMessage를 쓰게 될 경우 딱히 보내 줄 메시지는 없지만

//어떤 이벤트가 발생했다는 것을 알리기만 할 경우에 유용합니다.

 

                  class BackThread extends Thread {

                                   public void run() {

                                                     while (true) {

                                                                       mBackValue++;

                                                                       mHandler.sendEmptyMessage(0);

                                                                       try { Thread.sleep(1000); } catch (InterruptedException e) {;}

                                                     }

                                   }

                  }

 

// 핸들러를 선언하고 핸들러를 받았을 경우 호출되는 메서드를 구현해줍니다.

// 이렇게 되면 sendEmptyMessage를 보낼때 마다 msg를 체크해서 mBackValue의 값을

// 텍스트 뷰에 넣어줄 수 있지요.

 

                  Handler mHandler = new Handler() {

                                   public void handleMessage(Message msg) {

                                                     if (msg.what == 0) {

                                                                       mBackText.setText("BackValue : " + mBackValue);

                                                     }

                                   }

                  };

}

 

 

 

핸들러의 객체 전송

 

앞선 메서드로 특정 정보를 보낼 수 있지만 메시지를 보내는 대신에 객체를 보낼 수도 있습니다.

boolean post(Runnable r)

핸들러로 다음의 매서드를 통해 Runnable 객체를 보내면 해당 객체의 run 메서드가 실행됩니다. 이럴 경우 메시지를 받는 쪽은 다른 것을 정의하지 않고 핸들러만 정의하면 해당 내용을 수행할 수 있습니다.

 

핸들러의 객체 전송 예제

 

//앞선 예제와 다른 점은 Handler에서 post 메서드를 수행하고

//보내는 내용은 Runnable객체로 바꾸는 겁니다. 이러면 어차피 메시지가 필요없이

//객체가 해당 내용을 수행하게 됩니다. 때문에 메인 스레드에선 핸들러를 선언하고 따로 할 일이 없어집니다.

 

                  class BackThread extends Thread {

                                   public void run() {

                                                     while (true) {

                                                                       mBackValue++;

                                                                       mHandler.post(new Runnable() {

                                                                                        public void run() {

                                                                                        mBackText.setText("BackValue : " + mBackValue);

                                                                                        }

                                                                       });

                                                                       try { Thread.sleep(1000); } catch (InterruptedException e) {;}

                                                     }

                                   }

                  }

 

                  Handler mHandler = new Handler();

}

 

클래스의 분리 시 메시지 교환

 

앞선 예제처럼 스레드가 이너 클래스로 구현되었을 경우 멤버의 공유가 가능하지만 그렇지 않고 분리된 클래스의 경우에는 더 상세한 코드의 구현이 필요합니다.

이 경우 스레드는 전달 받은 핸들러를 자신의 멤버로 따로 저장해야 하고 (공유가 불가능 하기 때문입니다) 메시지 객체에 추가 정보를 저장해서 보내야 합니다. 예제를 보는게 더 이해하기 편하죠.

 

 

클래스의 분리 시 메시지 교환 예제

 

public class HandlerTest extends Activity {

                  int mMainValue = 0;

                  TextView mMainText;

                  TextView mBackText;

                  BackThread mThread;

 

                  public void onCreate(Bundle savedInstanceState) {

                                   super.onCreate(savedInstanceState);

                                   setContentView(R.layout.thread_threadtest);

 

                                   mMainText = (TextView)findViewById(R.id.mainvalue);

                                   mBackText = (TextView)findViewById(R.id.backvalue);

                                   Button btn = (Button)findViewById(R.id.increase);

                                    btn.setOnClickListener(new Button.OnClickListener() {

                                                     public void onClick(View v) {

                                                                       mMainValue++;

                                                                       mMainText.setText("MainValue : " + mMainValue);

                                                     }

                                   });                              

                                   mThread = new BackThread(mHandler);

                                   mThread.setDaemon(true);

                                   mThread.start();

                  }

 

                  Handler mHandler = new Handler() {

               public void handleMessage(Message msg) {

                                 if (msg.what == 0) {

                                                                       mBackText.setText("BackValue : " + msg.arg1);

                                 }

               }

    };

}

// 이 부분까지는 거의 같은 코드입니다. 다른 것은 스레드 선언 시에 핸들러를 전해 줍니다.

// 하지만 서브 스레드를 이너 클래스로 구현했던 앞선 예제와는 달리

// 외부의 클래스로 따로 구분하게 됩니다.

 

class BackThread extends Thread {

                  int mBackValue = 0;

                  Handler mHandler;

 

//따로 핸들러 속성을 지정해 줍니다.

//왜냐면 이너클래스가 아니기 때문에 속성을 공유하지 않습니다.

//따라서 생성자가 속성 지정을 해줍니다.

//이 후에 msg를 전해줄 때 비어있는 메시지를 전송했던 것과 다르게

//메시지 객체에 argument를 추가해줍니다.

//그 후 메시지를 보내게 됩니다.

 

                  BackThread(Handler handler) {

                                   mHandler = handler;

                  }

 

                  public void run() {

                                   while (true) {

                                                     mBackValue++;

                                                     Message msg = new Message();

                                                     msg.what = 0;

                                                     msg.arg1 = mBackValue;

                                                     mHandler.sendMessage(msg);

                                                     try { Thread.sleep(1000); } catch (InterruptedException e) {;}

                                   }

                  }

}

 

메시지 풀의 사용

 

매번 핸들러를 구현할 때마다 new 연산자로 새로 생성한다면 메모리도 계속해서 사용할 것이고 그에 다른 가비지 컬렉션의 작업도 생길 것 입니다. 이러면 속도도 느려지는 결과가 발생합니다. 이런 경우를 보완하고자 메시지 풀이라는 임시 캐쉬를 유지시켜서 빠른 작업이 가능하게 합니다.

static Message obtain(Message orig)

static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)

obtain 메서드는 메시지 풀에서 비슷한 메시지가 있다면 이를 재사용하게 합니다.

void recycle()

사용한 메시지를 풀에 집어 넣는 역할을 하는데 풀에 한번 집어 넣게 되면 시스템이 관리하므로 더 이상 관여할 수 없습니다.

 

메시지 풀의 사용 예제

...

...

...

class BackThread extends Thread {

                  int mBackValue = 0;

                  Handler mHandler;

 

                  BackThread(Handler handler) {

                                   mHandler = handler;

                  }

 

                  public void run() {

                                   while (true) {

                                                     mBackValue++;

 

// 앞서서 new Message를 선언해서 what과 argument를 전해 주었던 것과 달리

// 메시지 풀에서 what과 argument가 있는지 찾아보고 가져오게 됩니다.

// 얼마 안되는 횟수라면 상관없지만 몇백, 몇천번 반복하는 연산이라면 의미가 있겠죠.

 

                                                     Message msg = Message.obtain(mHandler, 0, mBackValue, 0);

                                                     mHandler.sendMessage(msg);

                                                     try { Thread.sleep(1000); } catch (InterruptedException e) {;}

                                   }

                  }

}


Comments