앱을 실행할 때 프로세스가 만들어지면 그 안에 메인 스레드가 같이 만들어진다.
그리고 최상위에서 관리되는 앱 구성 요소인 액티비티, 브로드캐스터 수신자 등과 새로 만들어지는 윈도우를 관리하기 위한 메시지 큐(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 객체를 핸들러의 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);
}
});
}
}
}
이렇게 하면 결과를 텍스트뷰에 보여주는 코드가 스레드 안에 있을 수 있으므로 좀 더 코드를 이해하기 쉬워진다.