본문 바로가기

안드로이드

Handler, Looper, Message

Handler
 
뭐하는 애일까?
서브 스레드에서 UI 관련 작업을 할 수 있도록 도와주는 클래스다.
안드로이드에서 ui관련 작업은 메인스레드에서만 가능하다.
 
스레드간의 동시성 문제 때문에 ui관련 작업에 한해서는 그렇게 하기로 정했다.
서브스레드에는 TextView에서 '로딩중' 이라는 글자를 입력하려고 하고 메인 스레드에서는 '로딩 완료!' 라는 텍스트를 입력하려고 한다고 생각한다면 그냥 마지막에 실행되는 스레드가 뭐냐에  따라서 보여지는 결과가 다를 것이다. 이런 혼돈을 막고자 메인 스레드에서만 ui 관련 작업을 할 수 있도록 설정했다.
 
핸들러를 사용해서 메세지를 전송하고 Message를 처리할 수 있다.
토막상식 : 스레드끼리는 메세지를 통해서 의사소통을 한다.
워커 스레드가 메인 스레드에게 메세지를 던지면 이를 수신받은 메인 스레드는 수신 받은 메세지를 통해서 ui 관련 작업을 처리할 수 있다.
아래 그림을 참조하면 쉽게 이해하는게 가능할듯.
Thread1에 메인 스레드이고 Thread2가 서브 스레드(워커 스레드) 라고 가정해보자.
서버스레드에서 handler를 사용해서 ui 관련 동작을 시키기 위한 일련의 과정은 다음과 같다.
(사진을 참고하면 더욱 이해가 쉽다.)
 
1.워커 스레드에서 sendMessage 또는 post메소드를 사용해서 메인 스레드에 어떤 UI동작이 일어났는지 알려준다.
2.Main스레드의 MessageQueue에 Thread2에서 보낸 메시지를 수신 받는다. (메시지큐는 FIFO구조로 되어있다.)
3.Looper는 MessageQueue에 있는 메세지를 순차적으로 처리해준다.
4.메인 스레드의 handleMessage에서 UI관련 작업을 수행한다.
(혼자 정리하면서 착각했는데 handleMessage는 MessageQueue에 있는게 아니라 Handler에 있는거다.)

Handler 관련 주요 함수
Handler.sendMessage(Message msg)
Message 객체를 message queue에 전달하는 메소드
 
Handler.sendEmptyMessage(int what)
Message의 what 필드를 전달하는 함수
 
Handler. post(new Runnable())
post 메소드는 Message 객체가 아닌 Runnable 객체를 MessageQueue에 전달한다.
 
post를 통해서 전달된 Runnable 객체는 해당 핸들러가 연결된 스레드에서 실행된다.
예시를 들어서 어떻게 사용하는지 보자.
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Handler handler = new Handler();
        TextView textView = findViewById(R.id.text);

        new Thread(new Runnable() {
            @Override
            public void run() {
                
                // .. 서버에서 데이터를 받아오는 코드가 있고 그 결과값이 아래 resultCode에 담긴다고 가정해보자.
                int httpResultCode = 200;
                
                // 워커 스레드 내부에서 메인 스레드의 핸들러를 통해서 Runnable 객체를 전달한다.
                //메인 스레드의 Message Queue에 Runnable 객체를 저장한다. (왜냐하면 여기가 메인스레드니까)
                //여기까지 진행하면 위에서 개념을 정리한 것 대로 메인 스레드의 Looper가 Runnable객체를 꺼내서                     run()을 실행한다.
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        if(httpResultCode == 200){
                            textView.setText("response 받아오기 성공!");    
                        }else {
                            textView.setText("response 받아오기 실패!");
                        }
                    }
                });
            }
        }).start();
    }
}
메세지를 받아서 처리하는 handleMessage() 메소드는 Handler에서 사용할 수 있는데 위의 코드에서는 해당 내용이 없다. 좀 의아할 수 있는데 안드로이드 프로세스엔 1개의 메인 스레드가 존재하고 내부적으로 루퍼를 통해 메세지 큐에 핸들러를 통해 데이터를 읽고 그걸 다시 핸들러를 통해 보내준다.
안드로이드 메인 스레드에는 기본적으로 루퍼가 내재되있다.
즉 내가 따로 구현안해도 이미 구현되어 있다. 루프 안에서 메시지 큐의 메시지를 꺼내서 처리하도록 되있다.

제대로 내용 이해했다면 worker스레드에서 생성한 handler 객체를 사용하면 안된다는 것을 알 것이다.
왜 사용하면 안되는건지 스스로 설명할 수 있는가?
정답은 아래 공부자료 출처 블로그에 있긴하다.
 
공부자료 출처
 
Looper
스레드의 메세지루프를 실행하기 위한 클래스다. 의역해서 표현하자면...
스레드의 UI 동작을 동작시켜주기 위한 녀석이다.
MessageQueue에 Message, Runnable이 도착한다. Mesage와 Runnable에는 메인 ui에서 어떤 동작을 해야하는지 명시가 되어 있고 Looper는 반복문이 계속 동작하면서 관련 작업을 진행시켜준다.
위에서 언급했던 사진을 다시 떠올려보면 이해가 더 잘될 것이다.

핸들러는 의존적이다. 메시지 큐가 있어야 하고, 메시지를 전달할 루퍼가 없으면 핸들러의 handleMessage를 사용할 수 없다. (당연한게 루퍼가 반복문을 돌면서 MessageQueue에 있는 message나 Runnable을 Handler로 넘겨주는 역할을 해줘서 그렇다.)
핸들러를 얻기 위해서는 new 키워드를 써서 Handler 클래스의 객체를 만들기만 하면 된다.
 
객체를 만들면 자동적으로 handler 인스턴스는 자동으로 해당 쓰레드와 메시지 큐에 연결되고 이 시점부터 핸들러를 통해 메시지를 주고 받는다.
여기서부터는 공식문서 내용이다.
스레드의 메세지 루프를 실행하기 위한 클래스다. 의역하면 handler로 부터 받아오는 메세지나 Runnable을 받기 위한 녀석이라는 것이다. (받아서 처리해주는 것은 메시지큐가 아니라 handler다. 헷갈리지 말자.)
스레드는 기본적으로 스레드 자신과 관련된 메세지 루프를 갖고 있지 않다.
(그래서 임의로 설정을 해줘야 한다.)
설정을 하기 위해서는 loop를 실행하는 역할을 해주는 prepare메소드를 호출하고 loop() 메소드를 실행한다.
loop메소드는 looper가 멈출때까지 계속 메세지를 받는 역할을 해준다.
 
아래는 이와 관련된 기본 예제다.
이 예제는 Looper를 구현한 전형적인 예시다.
prepare()와 loop()메소드를 사용하는 handler를 초기화 한다. 이 handler는 Looper와 통신을 하는게 목적이다.
  class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler(Looper.myLooper()) {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }
Looper 를 이해하는데 큰 도움이 된 사이트
(메인 스레드에 handleMessage가 없는 것도 이상했는데 이거 보고 해결됐다.)
Looper 관련공식 문서
Message
Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.