posted by Kyleslab 2012. 9. 21. 10:18

안드로이드는 스윙과 마찬가지로 싱글 쓰레드 GUI 모델이 적용되어 있다. 
즉 UI를 그리거나 갱신하는 쓰레드는 하나뿐이라는 것이다. 그 쓰레드는 바로 안드로이드의 주요 컴포넌트들이 실행되는 "main" 쓰레드이다. 모든 UI 관련 코드는 main 쓰레드에서 실행된다.

스윙에서 응답없음(unresponsive) 현상을 막기 위해 백그라운드에서 돌아가는 worker 쓰레드를 만든 것처럼, 안드로이드에서도 오래 걸리는 작업은 UI 쓰레드(= main 쓰레드)에서 처리하지 말고 별도의 쓰레드를 만들어 처리해야 한다. 그렇지 않으면 화면을 갱신하고자 하는 모든 코드는 block 당하여 ANR이 발생할 것이다.

오래 걸리는 작업에는 무엇이 있나?
- 파일 처리
- 네트워크 조회
- 다량의 DB 트랜잭션
- 복잡한 계산

그럼 백그라운드 쓰레드(Worker Thread)를 만드는 방법은?
- UI 쓰레드와 상호작용 없으면 그냥 Thread.start()에서 처리하면 된다.
- 그러나 작업후 결과를 UI에 반영(즉 UI 쓰레드와 통신)해야 한다면 Handler 등을 이용해야 한다.

다른 쓰레드에서 UI 쓰레드에 액세스하는 방법

  • Handler : 일반적인 쓰레드간 상호작용
  • Activity.runOnUiThread(Runnable) : UI 쓰레드의 메시지큐에 작업을 전달(post). 
       activity 인스턴스에 액세스할 수 있는 경우 사용
  • View.post(Runnable) : UI 쓰레드의 메시지큐에 작업을 추가. 해당 view 객체에 접근가능한 경우 사용
  • View.postDelayed(Runnable, long) : 일정시간 후에 작업 처리
  • AsyncTask : 쓰레드나 Handler에 대한 지식없이 백그라운드 작업 후 UI 쓰레드에서 화면갱신 작업을 
       처리할 수 있게 코드를 단순화해주는 유틸리티 클래스
  • AsyncQueryHandler : 비동기적으로 쿼리를 수행할 수 있게 해주는 도우미 클래스
  • NotifyingAsyncQueryHandler
  • WeakAsyncTask

  • 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

    http://tigerwoods.tistory.com/28 Thread 구현하기2



    안드로이드 에서는 UI Thread 외부에서 UI 관련 작업을 호출 하면 Exception이 발생한다.

    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() {

    @Override
    public void run() {
    Toast.makeText(getApplicationContext(), "I poop on you", Toast.LENGTH_LONG).show();
    }
    };

    Android에서는 Thread내에서는 UI이 변경을 직접적으로 하지 못하게 되어 있습니다. 위의 Code에서 Toast는 UI 요소이기 때문에 Exception이 발생하게 되는 것입니다.

    해결 방법은 두 가지가 있습니다.
    1. Activity에서 Thread을 호출한다면? “runOnuiThread” Method를 이용합니다.
    2. 대부분은 “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();

    출처 : http://daddycat.blogspot.kr/2011/05/android-thread-ui.html

    Handler 객체
      +Handler 인스터스를 생성하면 안드로이드 스레드 시스템에 자동 등록
      +사용자 인터페이스에서 일어나는 모든 작업을 담당
      +백그라운드 스레드와 통신
      +통신방법 : Message, Runnable
    -Message 객체
      +Handler 인스턴스에서 Message를 전달하려면 obtainMessage() 메소드를 호출해 메세지 풀에서 인스턴스를 확보해야 한다
      +Message를 Handler에게 전달할 때는 메시지큐에 쌓는 구조로 동작
      -sendMessage(), sendMessageAtFrontQueue(), sendMessageAtTime(), sendMessageDelayed()
      +handlerMessage() 메소드로 메시지 처리, 안드로이드가 큐에서 메시지를 하나씩 뽑아낸다
    start()와 run()에 대한 차이와 쓰레드가 실행되는 과정
    run()을 호출하는 것은 생성된 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 속한 메서드 하나를 호출하는 것이다. 반면에 start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생성한 다음에 run()을 호출해서 생성된 호출스택에 run()이 첫번째로 저장되게 한다.

    1. main 메서드에서 쓰레드의 start메소드를 호출한다.
    2. start메서드는 쓰레드가 작없을 수행하는데 사용될 새로운 호출 스택을 생성한다.
    3. 생성된 호출 스택에 run메서드를 호출해서 쓰레드가 작업을 수행하도록 한다.
    4. 이제는 호출스택이 2개 이기 때문에 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행된다.

    한쓰레드에서 예외가 발생해서 종료되어도 다른 쓰레드의 실행에는 영향을 미치지 않는다.


    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가 해야할 일을 지정해주는 것은 r
    unnable 객체입니다. 따라서 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 객체를 이용하는 것을 먼저 고려하시라는 것입니다.


    Runnable객체는 할일이라고 생각하자!!

    obtainMessage 는 메세지 풀에서 메세지를 가져오는 거고 
    (이미 사용된 message 를 재 사용하거든요. 소켓 풀 생각하시면 됩니다.)
    sendToTarget 은 Lopper Queue 에 메세지를 보내는 메서드입니다.

    메시지 큐!

    메시지는 스레드간의 신호입니다. 메서드처럼 호출한다고 바로 실행되는 것이 아니라 순서대로 처리된다. 메시지를 쌓아두는 공간이 바로 메시지 큐이다.

    루퍼
    루퍼는 메시지 큐에서 메시지를 꺼내어 핸들러로 전달하는 작업을 수행한다.
    메인스레드의 경우 기본적으로 루퍼를 가지지만 일반 작업을 수행하는 스레드의 경우 기본적으로 루퍼를 가지지 않는다. 이런 스레드가 메시지를 받아야할경우 루퍼를 직접 생성시켜야한다.
    루퍼는 무한히 실행되는 메시지 루프를 통해 쿠에 메시지가 들어오는지 감시하며 들어온 메시지를 처리할 핸들러를 찾아 handle Message메소드를 호출한다.UI를 관리하는 메인스레드는 기본적으로 루퍼를 가지고 이미 동작하므로 별다른 신경을 쓸 필요가 없다.
    루퍼를 직접 프로그래밍 해야하는 경우는 작업 스레드가 메시지를 받아야할때이다. 

    내가 정의한 핸들러에서 쏜메시지가 어떻게 알고 메인쓰레드의 메시지 큐에 쌓이는 걸까?
    메인쓰레드 영역안에서 선언해서?
    그렇다면 자신이 내가 정의한 handler.sendmessage()를 하면 루퍼를 구현하지 않았다면 메인쓰레드의 메시지큐에 메시지가 쌓이고 메인쓰레드의 루퍼가 이메시지를 꺼내 핸들러(내가 정의한 핸들러)로 보내서 처리한다.

    '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