안드로이드는 스윙과 마찬가지로 싱글 쓰레드 GUI 모델이 적용되어 있다.
즉 UI를 그리거나 갱신하는 쓰레드는 하나뿐이라는 것이다. 그 쓰레드는 바로 안드로이드의 주요 컴포넌트들이 실행되는 "main" 쓰레드이다. 모든 UI 관련 코드는 main 쓰레드에서 실행된다.
스윙에서 응답없음(unresponsive) 현상을 막기 위해 백그라운드에서 돌아가는 worker 쓰레드를 만든 것처럼, 안드로이드에서도 오래 걸리는 작업은 UI 쓰레드(= main 쓰레드)에서 처리하지 말고 별도의 쓰레드를 만들어 처리해야 한다. 그렇지 않으면 화면을 갱신하고자 하는 모든 코드는 block 당하여 ANR이 발생할 것이다.
오래 걸리는 작업에는 무엇이 있나?
- 파일 처리
- 네트워크 조회
- 다량의 DB 트랜잭션
- 복잡한 계산
그럼 백그라운드 쓰레드(Worker Thread)를 만드는 방법은?
- UI 쓰레드와 상호작용 없으면 그냥 Thread.start()에서 처리하면 된다.
- 그러나 작업후 결과를 UI에 반영(즉 UI 쓰레드와 통신)해야 한다면 Handler 등을 이용해야 한다.
다른 쓰레드에서 UI 쓰레드에 액세스하는 방법
activity 인스턴스에 액세스할 수 있는 경우 사용
처리할 수 있게 코드를 단순화해주는 유틸리티 클래스
Handler는 무엇인가?
- 쓰레드간 상호작용을 위한 일반적인 목적의 클래스
- 작업 쓰레드(=자식 쓰레드)에서 부모 쓰레드(=Handler 객체를 생성한 쓰레드)에 Message 및
Runnable(부모 쓰레드에서 처리할 작업) 전달(send/post 메소드)
- 자식 쓰레드에서 handler를 통해 전달되는 Message와 Runnable은 부모 쓰레드의 메시지큐에 들어감
- 내부적으로 Runnable도 결국은 Message로 변환(Message.callback=runnable)되어 메시지큐에 들어감
- Handler 객체를 생성한 쓰레드(부모 쓰레드)에서는 Looper를 통해 MessageQueue를 만들어 놓아야 함
- UI쓰레드(= main쓰레드)는 ActivityThread.main()에 의해 생성되는데, 여기서 Looper를 통해
UI 쓰레드용 메시지 큐가 이미 만들어져 있으므로 우리가 UI 쓰레드용 메시지큐를 만들 필요는 없음
- HandlerThread 클래스는 Looper를 가진 쓰레드를 쉽게 만들기 위한 용도
* 안드로이드 쓰레드에 대한 'i티거'님의 글 참조
http://tigerwoods.tistory.com/26 Thread 구현하기1
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
이와 같은 경우에는 Activity의 runOnUiThread 를 이용하여 해당 작업을 UI Thread 를 호출해 작업하면 문제를 회피할 수 있다.
--------------------------------------------------------------
new Thread(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable(){
@Override
public void run() {
// 해당 작업을 처리함
}
});
}
}).start();
--------------------------------------------------------------
Android Thread 내에서 UI 핸들링
아래의 Code는 Exception이 발생합니다.
public class BuggingService extends Service {
private Timer timer;
private int counter;
private TimerTask executeAgain = new TimerTask() {
Android에서는 Thread내에서는 UI이 변경을 직접적으로 하지 못하게 되어 있습니다. 위의 Code에서 Toast는 UI 요소이기 때문에 Exception이 발생하게 되는 것입니다.
해결 방법은 두 가지가 있습니다.
- Activity에서 Thread을 호출한다면? “runOnuiThread” Method를 이용합니다.
- 대부분은 “Handler.post” 방식을 이용합니다.
runOnuiThread 방식 예
activity.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(activity, "Hello", Toast.LENGTH_SHORT).show();
}
});
Handler.post 방식 예
final Handler handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
while (isInboxThreadRunning) {
handler.post(new Runnable() {
public void run() {
Toast.makeText(activity, "Hello", Toast.LENGTH_SHORT).show();
}
});
SystemClock.sleep(1 * 60 * 1000);
}
}
}).start();
+handlerMessage() 메소드로 메시지 처리, 안드로이드가 큐에서 메시지를 하나씩 뽑아낸다-sendMessage(), sendMessageAtFrontQueue(), sendMessageAtTime(), sendMessageDelayed()
Thread를 사용해서 프로그램 한다고 하면 일감 떠오르는 것은 "Thread에게는 Runnable객체가 필요하다" 는 사실입니다.Thread가 Thread로서 삶을 시작할 때 시작하는 일이 Runnable의 public void run() 메소드 를 수행하는 것이기 때문입니다. 즉,Runnable객체의 public void run() 메소드는 Thread로 하여금 원하는 일을 시킬수 있는 거의 유일한 방법인 셈입니다. Thread가 수행해야할 일을 Runnable의 public void run()에 써주는 겁니다.
관심이 있는 분들은 혹 아실지 모르지만 Thread클래스를 살펴보면 Thread 클래스에도 public void run()가 있습니다. 그렇다면 Thread도 Runnable인가요? 예 그렇습니다. 모든 Thread는 Runnable 입니다. 이것이 의미하는 뜻은 Thread가 생성자에서 Runnable 객체를 인자로 받게 되면 , Thread는 Thread로서 삶을 시작할 때 생성자에서 받은 Runnable의 public void run()을 수행합니다. 하지만 만약 생성자에서 Runnable객체를 인자 로 받지 못하면, 스스로가 Runnable객체이므로, Thread가 가진 public void run()을 실행 하게 된다는 뜻입니다.
이와 같이 어쨌든 Thread가 해야할 일을 지정해주는 것은 runnable 객체입니다. 따라서 Thread를 이용하는 프로그램을 해야 하는 경우 크게 두가지 방법이 있는데 그 첫째는 Thread가 수행해야할 일인 Runnable 객체를 별도로 만들어 public void run() 메소드를 원하는 대로 만드는 것이고, 둘째는 Thread 자체가 Runnable객체이므로 Thread를 상속한후 public void run() 메소드를 원하는 대로 고치는 겁니다. 즉, Runnable 객체를 만들지 않고 Thread를 상속해서 public void run()메소드를 오버라이딩 한다는 뜻입니다. 즉, Runnable객체의 public void run()에서 적어주여할 일들을 모두 적어 주게 되면 되는 것이지요.
물론 2가지 방법중 어떤 것을 사용해도 원하는 결과를 얻는데는 크게 다르지 않습니다. 하지만 필자는 수행해야 하는 일을 거의 Runnable객체를 정의하고 Thread는 Runnable 객체를 수행하는 도구로 사용하고만 있습니다. 여기에는 상당한 이유가 있는데, Runnable객체는 독립적으로 수행되야할 일의 개념화 및 모듈화를 의미합니다. Thread는 단순히 그 일을 독립적으로 수행하는 단순한 도구로써 사용하는 것입니다. 실제로 일을 하는 프로그램 코드(Runnable 객체에 담긴 내용)를코드와는 전혀 상관도 없고, 아무 연관관계도 없는 Thread 클래스에 합쳐서 관리할 이유가 없기 때문이고, 오히려 이런 관계없는 것을 철저히 분리시켜 놓는 것이 가독성이라던가 코드의 관리에 아주 도움이 되기 때문입니다. 하지만 이는 절대적인 것은 아니고, 가끔 편의성에 따라 필자도 Thread 클래스를 상속해서 사용하곤 합니다. 하지만 특별한 이유가 없는한 Runnable 객체를 이용하는 것을 먼저 고려하시라는 것입니다.
'Mobile > Android' 카테고리의 다른 글
BroadcastReceiver (0) | 2012.11.02 |
---|---|
Push에 대한 것! (0) | 2012.10.30 |
TweenAnimation (0) | 2012.09.24 |
AsyncTask (0) | 2012.09.21 |
Context (0) | 2012.09.21 |