Android - 핸들러

유의선·2023년 7월 23일
0

앱을 실행할 때 프로세스가 만들어지면 그 안에 메인 스레드가 같이 만들어진다.
그리고 최상위에서 관리되는 앱 구성 요소인 액티비티, 브로드캐스터 수신자 등과 새로 만들어지는 윈도우를 관리하기 위한 메시지 큐(Message Queue)를 실행한다.

메시지 큐를 사용하면 순차적으로 코드를 수행할 수 있는데, 이렇게 메시지 큐로 메인 스레드에서 처리할 메시지를 전달하는 역할을 핸들러 클래스가 담당한다.

결국 핸들러는 실행하려는 특정 기능이 있을 때 핸들러가 포함되어 있는 스레드에서 순차적으로 실행시킬 때 사용하게 된다.
핸들러를 이용하면 특정 메시지가 미래의 어떤 시점에 실행되도록 스케쥴링 할 수도 있다.


새로 만든 스레드가 수행하려는 정보를 메인 스레드로 전달하기 위해서는 먼저 핸들러가 관리하는 메시지 큐에서 퍼리할 수 있는 메시지 객체 하나를 참조해야 한다.
이 첫 번째 과정에서 obtainMessage 메소드를 사용할 수 있으며 호출의 결과로 메시지 객체를 반환받게 된다.

이 메시지 객체에 필요한 정보를 넣은 후 sendMessage 메소드를 이용해 메시지 큐에 넣을 수 있다.

메시지 큐에 들어간 메시지는 순서대로 핸들러가 처리하게 되며 이때 handleMessage 메소드에 정의된 기능이 수행된다.


핸들러로 메시지 전송하기

스레드에서 만든 코드에 핸들러를 적용해보았다.

MainActivity.java 파일을 다음과 같이 수정하였다.

public class MainActivity extends AppCompatActivity {
    int value = 0;
    TextView textView;

    MainHandler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.textView);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                BackgroundThread thread = new BackgroundThread();
                thread.start();
            }
        });

        handler = new MainHandler();
    }

    class BackgroundThread extends Thread {
        public void run() {
            for(int i = 0; i < 100; i++) {
                try{
                    Thread.sleep(1000);
                } catch (Exception e){}

                value += 1;
                Log.d("Thread", "value : " + value);

                Message message = handler.obtainMessage();
                Bundle bundle = new Bundle();
                bundle.putInt("value", value);
                message.setData(bundle);

                handler.sendMessage(message);
            }
        }
    }

    class MainHandler extends Handler {

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);

            Bundle bundle = msg.getData();
            int value = bundle.getInt("value");
            textView.setText("value 값 : " + value);
        }
    }
}

Handler 클래스를 상속한 MainHandler 클래스를 새로 정의하였다.

    class MainHandler extends Handler {

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);

            Bundle bundle = msg.getData();
            int value = bundle.getInt("value");
            textView.setText("value 값 : " + value);
        }
    }

Handler 클래스에는 handleMessage 메소드가 들어 있어 이 메소드를 다시 정의하면 메시지가 메인 스레드에서 수행될 때 필요한 기능을 넣어둘 수 있다.


이렇게 정의한 핸들러는 onCreate 메소드에서 액티비티가 초기화될 때 new 연산자를 이용해 객체로 만들어진다.

public class MainActivity extends AppCompatActivity {
	
    ...
    
    MainHandler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
		
        ...

        handler = new MainHandler();
    }

새로 만든 스레드 객체에서 수행한 작업의 결과가 나왔을 때는 핸들러 객체의 obtainMessage로 메시지 객체를 하나 참조한 후 sendMessage 메소드를 이용해 메시지 큐에 넣게 된다.

    class BackgroundThread extends Thread {
        public void run() {
            for(int i = 0; i < 100; i++) {
                try{
                    Thread.sleep(1000);
                } catch (Exception e){}

                value += 1;
                Log.d("Thread", "value : " + value);

                Message message = handler.obtainMessage();
                Bundle bundle = new Bundle();
                bundle.putInt("value", value);
                message.setData(bundle);

                handler.sendMessage(message);
            }
        }
    }

데이터를 전달하고자 할 때는 Message 객체의 Bundle 객체에 putOOO 메소드로 데이터를 넣고 getOOO 메소드로 데이터를 가져와 사용한다.


Runnable 객체 실행하기

핸들러를 사용해 메시지를 전송하는 방법은 일반적이지만 코드가 복잡하게 보일 수 있다.

핸들러 클래스는 메시지 전송 방법 외에 Runnable 객체를 실행시킬 수 있는 방법을 제공한다.
새로 만든 Runnable 객체를 핸들러의 post 메소드로 전달해주면 이 객체에 정의된 run 메소드 안의 코드들은 메인 스레드에서 실행된다.

앞에서 만든 코드를 메시지 전송 방식에서 Runnable 객체 실행 방식으로 바꿔보았다.

public class MainActivity extends AppCompatActivity {
    int value = 0;
    TextView textView;

    Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.textView);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                BackgroundThread thread = new BackgroundThread();
                thread.start();
            }
        });
    }

    class BackgroundThread extends Thread {
        public void run() {
            for(int i = 0; i < 100; i++) {
                try{
                    Thread.sleep(1000);
                } catch (Exception e){}

                value += 1;
                Log.d("Thread", "value : " + value);

                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("value 값 : " + value);
                    }
                });
            }
        }
    }
}

새로 정의했던 MainHandler 클래스를 지우고 일반적으로 사용하는 Handler 클래스로 객체를 생성하였다.

    Handler handler = new Handler();

Handler 객체를 만들어 변수에 할당해두면 이 객체의 post 메소드를 호출할 수 있다.


스레드 안에서 결과를 텍스트뷰에 표시하려면 post 메소드를 호출하면서 Runnable 객체를 만들어준다. 그리고 그 안에 택스트뷰에 접근하는 코드를 넣어준다.

    class BackgroundThread extends Thread {
        public void run() {
            for(int i = 0; i < 100; i++) {
                try{
                    Thread.sleep(1000);
                } catch (Exception e){}

                value += 1;
                Log.d("Thread", "value : " + value);

                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("value 값 : " + value);
                    }
                });
            }
        }
    }

이렇게 하면 결과를 텍스트뷰에 보여주는 코드가 스레드 안에 있을 수 있으므로 좀 더 코드를 이해하기 쉬워진다.

0개의 댓글