[Android] 안드로이드 정리 (11) - 핸들러
Android

[Android] 안드로이드 정리 (11) - 핸들러

728x90

 

 

출처 : Do it! 안드로이드 앱 프로그래밍 / 정재곤

 

 

핸들러 이해하기

 메인 액티비티는 앱이 실행될 때 하나의 프로세스에서 처리된다. 하지만 같은 프로세스 안에서 일련의 기능이 순서대로 실행될 때 대부분은 큰 문제가 없지만, 대기 시간이 길어지는 네트워크 요청 등의 기능을 수행할 때는 화면에 보이는 UI도 멈춤 상태로 있게 되는 문제가 생길 수 있다.

 이런 문제를 해결하기 위해 하나의 프로세스 안에서 여러 개의 작업이 수행되는 멀티 스레드 방식을 사용하게 된다. 하지만 스레드들이 동시에 리소스에 접근할 때 데드락이 발생하여 시스템이 비정상적으로 동작할 수도 있다.

 지연 시간이 길어질 수 있는 앱이라면 오랜 시간 작업을 수행하는 코드를 별도로 분리한 다음 UI에 응답을 보내는 방식을 사용한다. 이를 위해 안드로이드가 제공하는 두 가지 시나리오는 다음과 같다.

  • 서비스 사용하기 : 백그라운드 작업은 서비스로 실행하고 사용자에게는 알림 서비스로 알려준다. 만약 메인 액티비티로 결과 값을 전달하고 이를 이용해서 다른 작업을 수행하려고 한다면 브로드캐스팅으로 결과 값을 전달할 수 있다.
  • 스레드 사용하기 : 스레드는 같은 프로세스 안에 있기 때문에 작업 수행의 결과를 바로 처리할 수 있다. 그러나 UI 객체는 직접 접근할 수 없으므로 핸들러(Handler) 객체를 사용한다.

 안드로이드에서 UI를 처리할 때 사용되는 기본 스레드를 '메인 스레드'라고 부른다. 메인 스레드에서 이미 UI에 접근하고 있으므로 새로 생성한 다른 스레드에서는 핸들러 객체를 사용해서 메시지를 전달함으로써 메인 스레드에서 처리하도록 만들 수 있다.

 

 

스레드 사용하기

 안드로이드에서는 표준 자바의 스레드를 그대로 사용할 수 있다. 스레드는 new 연산자로 객체를 생성한 후 start() 메소드를 호출하면 시작할 수 있다. Thread 클래스에 정의된 생성자는 크게 파라미터가 없는 경우와 Runnable 객체를 파라미터로 갖는 두 가지로 구분할 수 있다.

> 스레드 동작 예제

package org.techtown.thread;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import org.w3c.dom.Text;

public class MainActivity extends AppCompatActivity {
    int value = 0;

    TextView textView;

    @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);

            }
        }
    }

}

 

 

 

 

핸들러로 메시지 전송하기

 앱을 실행할 때 프로세스가 만들어지면 그 안에 메인 스레드가 함께 만들어진다. 그리고 최상위에서 관리되는 앱 구성 요소인 액티비티, 브로드캐스트 수신자 등과 새로 만들어지는 윈도우를 관리하기 위한 메시지 큐를 실행한다. 메시지 큐를 사용하면 순차적으로 코드를 수행할 수 있는데, 이렇게 메시지 큐로 메인 스레드에서 처리할 메시지를 전달하는 역할을 핸들러 클래스가 담당한다.

 다음 그림은 핸들러의 메시지 처리 방법을 그림으로 표현한 것이다.

출처 : Do it! 안드로이드 앱 프로그래밍

 스레드 #1이 정보를 메인 스레드로 전달하기 위해서는 먼저 핸들러가 관리하는 메시지 큐에서 처리할 수 있는 메시지 객체 하나를 참조해야 한다. obtainMessage 메소드를 이용하여 메시지 객체를 반환받게 된다. 이 메시지 객체에 정보를 넣은 후 sendMessage 메소드를 이용해 메시지 큐에 넣을 수 있다.

 메시지 큐에 들어간 메시지는 순서대로 핸들러가 처리하게 되며 이때 handleMessage() 메소드에 정의된 기능이 수행된다. 이때 handleMessage()에 들어 있는 코드가 수행되는 위치는 새로 만든 스레드가 아닌 메인 스레드가 된다.

> 메인 스레드로 메시지 전송 예제

package org.techtown.thread;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import org.w3c.dom.Text;

public class MainActivity extends AppCompatActivity {
    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 {
        int value = 0;

        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(); // handler에서 obtainMessage 함수 호출
                Bundle bundle = new Bundle();
                bundle.putInt("value", value);
                message.setData(bundle);

                handler.sendMessage(message);
            }
        }
    }

    class MainHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

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

}

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

 

 

Runnable 객체 실행하기

 핸들러를 사용해서 메시지를 전송하고 순서대로 실행하는 방법은 가장 일반적이지만 개발자 입장에서는 코드가 복잡하게 보이는 단점이 있다. 핸들러 클래스는 메시지 전송 방법 이외에 Runnable 객체를 실행시킬 수 있는 방법을 제공한다. 즉, 새로 만든 Runnable 객체를 핸들러의 post() 메소드로 전달해주면 이 객체에 정의된 run() 메소드 안의 코드들은 메인 스레드에서 실행된다.

> Runnable 객체 예제

package org.techtown.thread;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import org.w3c.dom.Text;

public class MainActivity extends AppCompatActivity {
    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 {
        int value = 0;

        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() {
                    public void run() {
                        textView.setText("value 값 : " + value);
                    }
                });
            }
        }
    }
}
728x90