[Android] 스레드, 핸들러와 루퍼

mingsso·2023년 7월 6일

Android

목록 보기
5/12

1️⃣ 안드로이드의 스레드

메인 스레드 (UI 스레드)

안드로이드 앱이 실행되면 안드로이드 시스템은 하나의 메인 스레드로 앱의 프로세스를 실행함. 이러한 스레드는 주로 UI 위젯들을 그리는 역할을 수행하기 때문에 UI 스레드 라고도 불림

🤔 왜 UI 작업은 오직 메인 스레드에서만 이루어져야 할까?

만약 우리가 뷰를 그릴 때, 이미지를 그리는 스레드 A, 버튼을 그리는 스레드 B, 텍스트를 그리는 스레드 C가 있을때, A-B-C의 스레드가 순차적으로 실행된다면 우리가 원하는 뷰를 그릴 수 있을 것이다

하지만 상대적으로 작은 크기인 C가 먼저 실행되고, 큰 데이터인 A가 가장 나중에 실행될 경우 원하지 않는 뷰가 나타날 수 있다!

또한 멀티 스레드 환경이라고 가정했을 때, 여러 스레드에서 TextView 의 텍스트를 변경하는 상황이 발생하면 어떤 결과가 나타날지 미지수이기 때문이다


대신 UI 이벤트 및 작업에 대해 수 초 내에 응답하지 않으면 안드로이드 시스템은 ANR 팝업창을 표시하기 때문에, 시간이 오래 걸리는 코드는 새로운 스레드를 생성해서 처리해야 함
(-> 메인 스레드와 백그라운드 스레드를 구분하는 이유)

백그라운드 스레드 (Worker 스레드)

백그라운드 스레드는 메인 스레드와 달리 여러 개가 존재할 수 있음

네트워크 작업, 파일 업로드/다운로드, 이미지 처리 등은 처리 시간을 미리 계산할 수 없음
때문에, 안드로이드 시스템은 메모리 이외의 다른 곳에서 데이터를 가져오는 작업은 백그라운드 스레드에서 처리하는 것을 권장함

단, 백그라운드 스레드는 UI 구성 요소에 접근하면 안됨

백그라운드 스레드 생성 방법

  1. Thead 객체 사용하기
    Thread 클래스를 상속받아 스레드를 생성할 수 있음
  class WorkerThread: Thread() {
      override fun run() {    // 스레드가 처리할 로직을 정의 
          var i = 0
          while (i < 10) {
              i += 1
              Log.i("WorkerThread", "$i")
          }
      }
  }

  var thread = WorkerThread()   // 스레드 생성 
  thread.start()   // run()에 정의된 로직을 생성된 스레드가 처리함 

  1. Runnable 인터페이스 사용하기
    인터페이스 내부에 메소드가 하나만 있는 경우는 람다식으로 변환이 가능함
  class WorkerRunnable: Runnable {
      override fun run() {
          var i = 0
          while (i < 10) {
              i += 1
              Log.i("WorkerRunnable", "$i")
          }
      }
  }

  var thread = Thread(WorkerRunnable())   // Thread 클래스의 생성자로 전달 
  thread.start()
  
  
  // 람다식으로 변환하기 
  // 방법 1
  Thread {
      var i = 0
      while (i < 10) {
          i += 1
          Log.i("LambdaThread", "$i")
      }
  }.start()


  // 방법 2
  thread(start=true) {
      var i = 0
      while (i < 10) {
          i += 1
          Log.i("KotlinThread", "$i")
      }
  }



2️⃣ 루퍼 Looper

안드로이드는 메인 스레드와 백그라운드 스레드 등 스레드 간의 통신을 위해 핸들러와 루퍼를 제공함

  • 하나의 스레드는 오직 하나의 Looper를 가지며, Looper는 오직 하나의 스레드만 담당함(1:1)
  • 안드로이드에서는 기본적으로 MainActivity가 실행됨과 동시에, 자동으로 메인 스레드의 Looper가 돌기 시작함
  • 각 스레드의 Looper 내부에는 Message Queue라는 것이 존재하는데, 여기에는 해당 스레드가 처리해야 할 동작들이 메시지 or Runnable 객체 형태로 쌓이게 됨
    이때, '메시지'는 하나의 작은 작업 단위임

즉, Looper는 MessageQueue에 들어오는 메시지들을 하나씩 꺼내 이를 적절한 Handler로 전달하는 역할을 함

기본적으로 Looper는 자신이 어떤 Handler에 메시지를 전달해야 하는지에 대한 참조를 갖고 있음
(기본값은 메인 스레드의 Handler)



3️⃣ 핸들러 Handler

스레드 간 메시지를 전달해주는 역할을 하는 클래스

핸들러는 핸들러 객체를 만든 스레드와 해당 스레드의 Message Queue에 바인딩됨
다른 스레드에게 메시지를 전달하려면, 메시지를 전달하려는 스레드에서 생성한 핸들러의 post()나 sendMessage() 등의 함수를 사용해야 함

Thead #1이 메인 스레드이고 Thread #2가 워커 스레드라고 가정하면,

  1. 워커 스레드가 UI 처리를 위해서 메인 스레드에서 생성된 핸들러의 sendMessage()를 통해 메시지를 전달함
  1. 해당 메시지는 메인 스레드의 Message Queue에 저장됨
  1. Looper는 차례대로 메시지를 꺼내며, 워커 스레드가 전달했던 UI 처리를 위한 메시지가 꺼내지면 handleMessage()로 전달됨
  1. handleMessage()는 실제 UI 작업을 수행함
    handleMessage()는 메인 스레드에서 생성된 핸들러에 의해 호출된 것이기 때문에 해당 작업은 메인 스레드에서 동작함 -> 문제 없이 UI 처리가 가능함

// UI 스레드에서 Handler 생성
Handler handler = new Handler();

// 백그라운드 스레드에서 UI 스레드로 메시지 전달
Thread backgroundThread = new Thread(new Runnable() {
    @Override
    public void run() {
        // 작업 수행
        // ...

        // UI 스레드로 메시지 전달
        handler.post(new Runnable() {
            @Override
            public void run() {
                // UI 업데이트 수행
                // ...
            }
        });
    }
});

backgroundThread.start();






참고 자료

도서 '이것이 안드로이드다 with 코틀린'
https://velog.io/@ho-taek/Android-%EC%8A%A4%EB%A0%88%EB%93%9C%EB%9E%80
https://velog.io/@haero_kim/Android-Looper-Handler-%EA%B8%B0%EC%B4%88-%EA%B0%9C%EB%85%90
https://brunch.co.kr/@mystoryg/84
https://salix97.tistory.com/82

profile
🐥👩‍💻💰

0개의 댓글