본문 바로가기

Android/Basic

[Android] Thread와 handler 그리고 Runnable

안드로이드 애플리케이션이 실행되면, 하나의 프로세스로 동작한다.

프로세스 안에는 여러가지 작업으로 분할되어 스레드들이 동시에 작업을 수행한다.

사용자가 직접 스레드를 생성하여 작업을 분할시켜 수행할 수 있다.


안드로이드 애플리케이션은 메인 스레드라는 것이 전체적인 UI 관리 담당 앱의 기본적인 작업들을 수행하고 있다.


표준 자바의 경우, 스레드는 교착상태와 같은 일이 발생하지 않는 경우 알아서 수행된다. 하지만 안드로이드는 다르게 개발자가 직접 제어해주어야한다.


스레드를 생성하여 직접 UI를 동시에 접근하고자 할 때 다음과 같은 에러가 발생한다.

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.


위와 같이 스레드들이 UI 동시접근을 하지 못하도록 막아놓은 이유는, 하나만 적용되고 나머지는 버려지는 결과가 발생한다.

그래서 이와 같은 동기화 이슈를 차단하기 위해서라고 한다.

출처 : https://academy.realm.io/kr/posts/android-thread-looper-handler/

위 사이트에 설명이 아주 자세히 나와있다!


그래서 안드로이드는 핸들러라는 것을 사용해서 메인스레드에게 UI 접근을 요청해야한다.

메인스레드에 붙어있는 핸들러에게 메시지 형식으로 요청하면, 메인스레드에서 동작하는 것과 같이 구현할 수 있다.

핸들러로 메시지를 보내면 큐잉되며, 큐의 특성을 따라 요청한 순서대로 작업한다.


아래 코드는 1초마다 숫자를 1씩 증가시켜, 텍스트뷰에 뿌려주는 예제이다.

스레드 클래스와 핸들러 클래스를 상속받아 메소드를 오버라이드해야한다.

스레드는 start 메소드를 호출하면 자동으로 run 메소드가 호출된다.

public class MainActivity extends AppCompatActivity {
TextView textView;

ValueHandler handler = new ValueHandler();

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

textView = findViewById(R.id.textView);

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

findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 메인스레드에서 UI 접근하는 중
//textView.setText("현재 값 : "+value);
}
});
}

class BackgroundThread extends Thread {
int value = 0;
boolean running = false;

public void run() {
running = true;
while(running){
value += 1;

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

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

class ValueHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// 메시지를 전달받으면 자동으로 호출되는 메소드
// 메시지를 받아서 메인스레드로 동작하므로, UI 접근 가능
super.handleMessage(msg);

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


하지만 위와 같은 코드는 상당히 복잡하다.

이는 Runnable 인터페이스를 이용해 간단하게 구현할 수 있다


스레드를 시작시키고자 할 위치에 다음과 같이 구현하면 된다.

new Thread(new Runnable() {
int value = 0;
boolean running = false;

@Override
public void run() {
running = true;
while(running){
value += 1;

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

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


직접 Handler 클래스를 상속받아 정의한 클래스가 아닌 기본 handler 클래스를 이용하면 된다.

'Android > Basic' 카테고리의 다른 글

[Android] Socket 통신 예제  (0) 2018.07.07
[Android] AsyncTask  (0) 2018.07.06
[Android] Navigation drawer (바로가기 메뉴)  (0) 2018.07.02
[Android] ViewPager + TitleStrip(타이틀스트립)  (0) 2018.07.01
[Android] ViewPager + Tab  (0) 2018.06.25