Posts 메인 스레드와 Handler - (1)
Post
Cancel

메인 스레드와 Handler - (1)

UI 처리를 위한 메인 스레드

  • 어플리케이션은 성능을 위해 멀티 스레드를 많이 활용하지만 , UI를 업데이트하는 데는 단일 스레드 모델이 적용된다. 멀티 스레드로 UI를 업데이트를 하게 될 경우 교착 상태, 경합 상태등 여러 문제가 생길 수 있어 UI를 업데이트하는 것은 메인 스레드에서만 허용한다.
  • 컴포넌트 (액티비티, 서비스, 브로드캐스트 리시버, Application)의 생명주기 메서드와 그 안의 메서드 호출은 기본적으로 메인 스레드에서 실행된다.

자바 애플리케이션에서 메인 스레드

  • 일반적인 자바 애플리케이션에서 main() 메서드로 실행되는 것이 바로 메인 스레드이다.

안드로이드 애플리케이션에서 메인 스레드

  • 안드로이드 프레임워크 내부 클래스인 android.app.ActivityThread가 애플리케이션 메인 클래스이고, ActivityThread의 main()메서드가 애플리케이션의 시작 지점이다.

  • ActivityThread는 Activity만 관련되어 있는것이 아니라 모든 컴포넌트들이 다 관련되어 있다. (Thread를 상속 받은 class가 아니다.)

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    public static void main(String[] args){
      SamplingProfilerIntegration.start();
    
      ClosedGuard.setEnabled(false);
    
      Environment.initForCurrentUser();
    
      EventLogger.setReporter(new EventLoggingReporter());
    
      Process.setArgV0("<pre-initialized>");
    
      Looper.prepareMainLooper(); // main Looper를 준비한다.
    
      ActivityThread thread = new ActivityThread();
         thread.attach(false);
    
      if(sMainThreadHandler == null){
          sMainThreadHandler = thread.getHandler();
          }
    
       AsyncTask.init();
    
       Looper.loop(); // UI Message 처리한다. Looper.loop() 메서드에 무한 반복문이 있어 main() 메서드는 프로세스가 종료될 때 까지 끝나지 않는다.
    
       throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    

Looper class

스레드별로 Looper 생성

  • Looper는 TLS(Thread Local Storage)에 저장되고 꺼내진다.
  • ThreadLocal set() 메서드로 새로운 Looper을 추가, get() 메서드로 Looper를 가져올때 스레드별로 다른 Looper가 반환된다.
  • Looper.prepare() 에서 스레드별로 Looper를 생성한다.
  • 스레드의 메인 Looper는 Looper.prepareMainLooper()를 호출하여 생성, Looper.getMainLooper()를 사용하면 어디서든 메인 Looper를 가져올 수 있다.

Looper 별로 MessageQueue 가짐

  • Looper는 각각의 MessageQueue를 가진다. 특히 메인 스레드에서는 이 Message Queue를 통해서 UI 작업에서 경합 상태를 해결한다.

Looper.loop() 메서드의 주요 코드

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    public static void loop(){
      final Looper me = myLooper();
      if(me == null){
          throw new RuntimeException(
              "No Looper; Looper.prepare() wasn't called on this thread.");
      final MessageQueue queue = me.mQueue;
      for( ; ; ){
          Message msg = queue.next(); // MessageQueue에서 다음 Message를 꺼낸다.
          if(msg == null){ // Message 가 null 일때 return (Looper가 종료될때 quit() , quitSafely() 메소드 통해)
              return;
           }
           msg.target.dispatchMessage(msg); // Message를 처리한다. target은 Handler인스턴스이고 결과적으로 Handler 의 dispatchMessage() 메서드가 Message를 처리한다.
           msg.recycle();
          }
    }
    
    public void quit(){
       mQueue.quit(false);
    }
    
    public void quitSafely(){
       mQueue.quit(true);
    }
    

quit()과 quitSafely() 메서드 차이

  • quit() : 아직 처리되지 않은 Message를 모두 제거한다.
  • quitSafely() : sendMessageDelayed() 등을 써서 실행 타임스탬프를 뒤로 미룬 지연 Message를 처리, quickSafely() 메서드를 실행하는 시점에 현재 시간보다 타임스탬프가 뒤에 있는 Message를 제거 앞에 있는 Message는 계속해서 처리 (젤리빈 API level 18이상 부터 사용할 수 있다.)

Message와 MessageQueue

  • MessageQueue는 Message를 담는 자료구조
  • MessageQueue에는 Message가 실행 타임스탬프순으로 삽입되고 링크로 연결되어, 실행 시간이 빠른 것부터 순차적으로 꺼내어진다.

Message class

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    public final class Message implements Parcelable{
      public int what;
    
      public int arg1;
    
      public int arg2;
    
      public Object obj;
    
      public Messenger replyTo;
    
      ...
    
      /* package */ long when;
    
      /* package */ Bundle data;
    
      /* package */ Handler target;
    
      /* package */ Runnable callback;
    
      /* package */ Message next;
    
      ...
    }
    
  • Message를 보낼때 변수에다가 값을 넣어서 보낸다.

  • android.os 패키지 아래에 Looper, Message, MessageQueue, Handler는 클래스에서 Message의 패키지 private 변수에 직접 접근한다.

    • Handler 접근 메소드 : target, callback
    • postXxx(), sendXxx() 메서드를 호출할때 Message에 담겨서 MessageQueue에 들어간다.
    • postXxx(), sendXxx() 메서드에서 실행 시간 (when)이 전달되고, 나중에 호출한 것이라도 타임스탬프가 앞서면 큐 중간에 삽입된다. 이것이 삽입이 쉬운 링크 구조를 사용한 이유이다.

obtain() 메서드를 통한 Message 생성

  • Message를 생성할 떄는 오브젝트 풀(object pool)에서 가져온는 Message.obtain() 메소드나 Handler의 obtainMessage() 메서드 사용을 권장
  • 오브젝트 풀은 Message에 정적 변수로 있고 Message를 최대 50개까지 저장한다.
  • Looper.loop() 메서드에서 Message를 처리하고 나서 recycleUncChecked() 메서드를 통해서 Message를 초기화해서 재사용한다.
  • 오브젝트 풀이 최대 개수에 도달하지 않았다면 오브젝트 풀에 Message를 추가한다.
  • new Message()와 같이 기본 생성자로 생성해서 값을 채워도 동작에는 문제가 없어보이지만 Message 처리가 끝나면 불필요하게 풀에 Message 처리가 끝나면 불필요하게 풀에 Message를 추가하면서 금방 풀의 최대 개수에 이른다.
This post is licensed under CC BY 4.0 by the author.

-

메인 스레드와 Handler - (2)

Comments powered by Disqus.