반응형

제가 안드로이드 개발을 처음 할 때 당시를 떠올려보면, 나의 방향성(?)은 안드로이드 플랫폼에 대한 제대로 된 이해보다는 "일단 동작하는 앱을 만들어보자!"에 더 가중치를 두었습니다. 그러다보니 무슨 새로운 앱을 만들어볼 때마다 항상 마주쳤던 에러가 있는데, 그것이 바로 NetworkOnMainThreadException 입니다.

 

아마 나뿐만 아니라, 안드로이드 개발을 자바 개발처럼 생각하는 학생 혹은 초보 개발자분들은 이 에러에 대해 상당수 경험해보았을 것입니다. 자, "처음 만난 에러는 실수고, 다시 만난 에러는 실력이다!" 라는 마음가짐을 갖고서 앞으로 다시는 이 에러를 만나지 않으리라는 목표로 핸들러에 대해 알아보겠습니다.

 

참고로 이 글에서는 쓰레드가 무엇인지, Runnable 객체가 무엇인지에 대해서는 안다고 가정하고 있기 때문에, 아직 해당 개념이 부족하신 분들은 먼저 자바의 ThreadRunnable에 대해 학습한 후에 읽을 것을 권장합니다.

 

안드로이드(Android)의 Thread

안드로이드에서 어플리케이션을 실행했을 때 특별히 다른 작업을 하게끔 구현한 것이 아니라면 기본적으로 안드로이드 시스템은 싱글 스레드를 갖는 Linux 프로세스를 생성합니다.(안드로이드가 Linux에 기반하고 있음)

안드로이드 어플리케이션에는 Activity, Service 등 다양한 컴포넌트가 존재하는데 별도의 설정을 해주지 않는다면 기본적으로 동일 프로세스의 동일 스레드에서 실행됩니다.(프로세스에 대해서도 다루고 싶은 내용이 많지만 오늘은 쓰레드에 관해서만 다루도록 하겠습니다.)

 

어플리케이션을 실행하면 안드로이드 시스템이 그 어플리케이션에 대한 쓰레드를 생성하는데, 이를 메인 쓰레드(Main Thread)라고 합니다. 중요한 것은 이 메인 쓰레드의 쓰임인데, 안드로이드의 메인 쓰레드는 안드로이드 UI와 어플리케이션이 상호작용하는 쓰레드입니다. 그래서 종종 Main Thread를 UI Thread 라고도 부릅니다.

 

앞서 언급한 것처럼 안드로이드의 컴포넌트들은 기본적으로 UI Thread에서 시작되기 때문에 시스템 콜백에 응답하는 메소드 또한 항상 UI Thread에서 실행됩니다.

예를 들어, 사용자가 화면의 버튼을 터치하는 등에 대한 이벤트가 발생한다면 UI 쓰레드가 해당 위젯에 이벤트를 발송합니다.

 

자, 생각해보세요.

우리는 앱을 사용할 때 엄청나게 많은 UI 이벤트를 발생시킵니다.

특히나 앱이 사용자와 상호작용 과정 중에 리소스를 많이 소모해야 하는 경우(네트워크 통신이나 DB 쿼리 등), 어플리케이션을 싱글 쓰레드로 구현하게 된다면 상당히 낮은 성능을 보이게 될 것입니다.

낮은 성능을 보이더라도 정상 작동이 되기만 한다면 그나마 다행인데, 싱글 스레드에서 UI와 관련 없는 긴 작업들을 처리하는 동안 UI Thread가 UI와 관련된 이벤트가 모두 차단되어 사용자에게 어플리케이션이 중단된 것처럼 보이는 것이 가장 심각한 문제입니다.

UI Thread가 몇 초 이상 차단되는 최악의 경우에는, 안드로이드 시스템이 사용자에게 그 유명한 ANR(Android Not Responding) 대화 상자를 표시합니다.

 

그래서 안드로이드는 UI Thread에게는 오직 UI와 관련된 처리만 할 수 있도록 하고, 네트워크나 DB 등 리소스를 많이 잡아먹는 작업에 대해서는 차단한다! 바로 이 때문에 NetworkOnMainThreadException 가 발생했던 것입니다.

그러면 UI Thread가 차단하고 있는 작업들에 대해 대신 처리를 해주는 별도의 쓰레드를 생성해야 할 텐데, 이를 Worker Thread라고 합니다.

안드로이드는 UI Thread에게는 네트워크, DB 쿼리 등에 대한 작업을, Worker Thread에게는 UI 관련 작업을 못하게 하고 있습니다.

 

  Main(UI) Thread Worker Thread
작업 가능 UI 관련 작업 네트워크, DB 쿼리 등
작업 불가능 네트워크, DB 쿼리 등 UI 관련 작업

 

어플리케이션을 제작하다가 네트워크를 사용하여 서버로부터 받은 이미지를 내 화면에 ImageView로 뿌려주고 싶다면 어떻게 할까요? 즉, 네트워크 작업이 UI 변경으로 이어지는 작업을 해야 할 때 어떻게 해야 할까요?

생각해보자면 Worker Thread와 UI Thread 간에 통신을 한다면 가능할 것 같습니다.

Worker Thread는 서버로부터 이미지를 받아오는 것까지의 작업을 하고, 그 이미지에 대한 데이터를 UI Thread에 넘겨 화면에 그려내는 것은 UI Thread가 담당하게끔 하는 것입니다.

 

그렇다면 이러한 작업들을 어떻게 처리할 수 있을까요?

안드로이드는 이를 위해 대표적인 2가지 방법을 제공하는데 바로 HandlerAsyncTask입니다.

(AsyncTask에 대한 포스팅과 Handler와 비교한 포스팅도 추후 업로드할 예정입니다.)

 

자, 이렇게 핸들러가 왜 필요하고 어떤 역할을 하게 될지에 대한 배경에 대해 알아봤습니다.

이제부터 핸들러가 어떤 구조를 갖고 있고, 어떤 작업들을 할 수 있는지에 대해 알아보겠습니다.

 

 

 

 

 

 

핸들러(Handler)란?

핸들러는 쓰레드의 메시지큐(MessageQueue)와 관련된 Message와 Runnable 객체에 대해 전송하거나 실행할 수 있게 해주는 녀석입니다.

핸들러 인스턴스는 핸들러 인스턴스를 생성하는 쓰레드와 그 쓰레드의 메시지큐와 연관되어 있는데, 바로 이 때문에 핸들러가 그 메시지큐에 Message나 Runnable을 전달하기도 하고, 메시지큐에서 꺼내 실행하기도 할 수 있습니다.

 

다음 그림을 살펴보면 이해가 빠를겁니다.

 

<출처 : recipes4dev.tistory.com>

위 그림을 보면, Thread #1에서 생성된 Handler 객체가 Thread #1의 Looper와 Message Queue에 관련되어 있음을 알 수 있습니다. 그리고 Thread #2에서는 Thread #1의 Handler를 통해 MessageQueue에 작업(Message or Runnable)을 전달할 수 있습니다.

 

핸들러는 다음 두 가지 용도로 자주 쓰입니다.

1) 다른 쓰레드에서 수행되어야 하는 작업을 Message Queue에 enqueue

2) 미래 특정 시점에 실행되어야 하는 Message나 Runnable 객체 스케줄링

 

앞서 핸들러가 처리하는 작업 단위로 MessageRunnable이 있다고 했는데, 이 두 객체에 대해 Message Queue스케줄링을 지원하는 메소드가 각각 분리되어 있습니다.

Message 객체에 대해서는 sendMessage(), sendEmptyMessage(), sendMessageAtTime(), sendMessageDelayed() 등이 있고, Runnable 객체에 대해서는 post(), postAtTime(), postDelayed() 등이 있습니다.(파라미터는 생략)

 

그리고 이렇게 Message Queue에 들어가 있는 Message 객체들을 꺼내어 실행하는 것이 바로 위 그림에도 나와있는 handleMessage() 메소드입니다. 꺼내온 Message에 대해 어떻게 처리를 할 것인가에 대해서는 구현이 되어있지 않기 때문에, 사용자가 반드시 재정의(Override)를 해야 합니다.

 

Runnable 객체를 다룰 때는 별도로 재정의해야하는 메소드 없이 post() 메소드로 처리하면 됩니다. 왜냐하면 Runnable 객체 자체가 어떤 행동을 정의했기 때문이죠 :)

 

반응형
반응형