Eclips 툴을 이용해 Java를 혼자 배울때 쓰레드를 공부하고 사용해본 적이 있다. 안드로이드 스튜디오를 통해 사용하는 쓰레드도 같은 기능이고 같은 방법으로 사용할까? 공부해보도록 하겠다.
◼️ 1. 쓰레드 ◼️
쓰레드 : 프로그램 안에서 실행을 담당하는 하나의 흐름 >> JVM이 동시에 수행할 수 있는 멀티 스레드를 할당해줌.(이를 통해 여러 작업 수행 가능)
Java에서 프로그램이 실행되면 Main 스레드가 최초의 스레드 >> 메인 스레드를 통해 여러 작업 스레드를 생성 가능하다.
쓰레드 생성 방법은 2가지(쓰레드 클래스 사용, Runnable 인터페이스 사용)
View는 무조건 메인스레드를 통해서만 변경 가능 >> 예를들어 메인 스레드가 아닌 작업 스레드에서 view를 접근하게 된다면?
>> android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 이러한 오류가 뜨게 되는데 에러 내용은 original thread 에서만 view를 건드릴 수 있다는 내용이다. 즉, 메인 스레드에서만 view(UI) 접근을 할 수 있다는 것이다. >> 즉, 쓰레드에서는 연산작업(콘솔에만 띄우기)만 가능하며 UI는 동기화 문제가 발생하기 때문에 건드릴 수 없다.(하나의 텍스트뷰에 서로 다른 쓰레드들의 값들이 들어가게 된다면 에러(동기화 문제)가 발생하기때문이다.) >> 이러한 쓰레드에서 연산된 값을 UI에 적용하기 위한 것, 즉 쓰레드간의 통신을 도와주는것이 바로 핸들러이다.
◼️ 2. 핸들러 ◼️
핸들러를 알기 위해선 Message와 MessageQueue와 Looper를 알아야 한다.
[ 참고 : https://black-jin0427.tistory.com/177 ]
Thread : 프로그램 안에서 실행을 담당하는 하나의 흐름
MessageQueue : Message를 담는 자료구조, 순차적으로 코드를 수행 가능
Message : Parcelable(여러 데이터가 하나의 꾸러미안에 담김,객체를 싸는 클래스) 형태의 객체로 Message 클래스를 보면 어떤 형태의 데이터가 전달되는 확인 할 수 있다.
◼️ 2-1. Message◼️
Message : 쓰레드 간 통신내용을 저장하는 객체이며 단순한 신호,명령 뿐 아니라 복잡한 추가정보도 전달 받을 수 있다.
- 메시지 수신 : 메시지가 도착하면 void hadleMessage(Message msg) 메소드가 호출되며 메시지를 수신한다.
- 메시지 발신 : 메세지를 보내는 쪽에서 전달하고자 하는 내용을 message객체에 저장하여 핸들러로 전송하는데, 아래 메소드를 사용
- boolean Handler.sendEmptyMessage(int what) : 간단하게 what 값을 통해서 메시지를 보낼 때 사용
- boolean Handler.sendMessage(Message msg) : 좀더 복잡한 Message객체를 보낼 때 사용boolean
- sendMessageAtFrontOfQueue(Message msg) : 메시지는 큐에 순서대로 쌓여 처리되나 급하게 처리해야할 메시지를 우선적으로 지정할때 사용
메세지를 공부 하면서 Parcelable에 대해 궁금하여 공부를 해보았다. Parcelable이란 간단하게 Activity에서 Activity로 한꺼번에 데이터 꾸러미를 전달되고 받아 볼 수 있게 해준 것이였다. Parcelable은 보통 Serializable와 비교를 많이 들 한다. 그래서 비교를 하면서 각각에 대해서 알아보자.
Parcelable vs Serializable (데이터 꾸러미가 A Activity에서 B Activity로 한꺼번에 전달되고 받아 볼 수 있게 해주는것들)
Parcelable(안드로이드)과 Serializable(자바) 둘 다 프로세스간 통신(IPC)를 위해 Bundle 클래스를 사용한다. 즉 둘 다, 직렬화를 시켜 bundle(Map(key, value))에 넣어주는것이고 특징이 다른 것이다. 특징이 다른점은 Parcelable을 이용하게 되면 Serializable과 다르게 리플랙션(구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드와 타입 그리고 변수들을 접근할 수 있도록 해주는 자바 API) 사용하지 않고, 필요한 부분만을 직렬화하고 비직렬화 할 수 있도록 만들어져 효율성을 향상시킨다.
Parcelable 단점 : Parcelable을 구현하는 것은 직접 구현해야 하기 때문에 비슷한 코드가 반복되는 것이 생기게 되며 Serializable에 비해 코드 작성 시간이 오래 걸리게 된다.
Serializable 단점 : 간단하게 구현한 만큼 그에 따른 리스크(내부적으로 리플렉션이 발생하게 되어 오브젝트가 많이 생성이 됨)에 따른 Garbage Collection이 발생하게 되어 성능저하 및 배터리를 많이 소모하게 된다.
또한 메세지 클래스의 대해서 알아보자.
◼️ 메시지 클래스의 주요 멤버 변수
- What : 메세지를 수신하는 핸들러가 식별할 수 있는 사용자 정의 메시지 , 핸들러는 이 멤버 변수를 참조하여 어떠한 처리를 요청하는 메시지인지 구별할 수 있다.
- arg1, arg2 : 간단한 저수값을 저장할 수 있는 멤버 변수
- Obj : 메시지를 수신하는 목적지 핸들러에 보낼 임의의 객체
- Target : 메시지가 전달될 목적지 핸들러
- Callback : 핸들러에 의해 처리될 때 실행되어야 할 Runnable 객체
◼️ 메시지 생성 방법 세 가지
- 방법 1 : Message msg = new Message();
- 방법 2 : Handler객체.obtainMessage();
- 방법 3 : Message msg = Message.obtain(); // 제일 효율, 하지만 나중에 메시지 풀에 Message객체를 밥납해야 재사용이 가능
◼️ 2-2. Looper ◼️
Looper : Looper별로 MessageQueue를 가지고 있다. MessageQueue에서 Message를 꺼내 Handler로 전달하는 작업을 처리한다.
메인스레드에서는 Looper를 이미 가지고 있어 개발자가 관여하지 않아도 되지만 작업스레드에서는 Looper를 직적 작성하고 실행 시켜야함.
Looper.prepare(): 작업스레드를 위한 루퍼 생성, Looper.loop(): 큐에서 메시지를 꺼내 핸들러로 전달.
◼️ 2-3. MessageQueue ◼️
MessageQueue : 프로세스 또는 프로그램 간에 데이터를 교환할 때 사용하는 통신 방법 중에 하나로, 메시지 지향 미들웨어(Message Oriented Middleware:MOM)를 구현한 시스템을 의미한다. 메시지 지향 미들웨어란 비동기 메시지를 사용하는 응용 프로그램들 사이에서 데이터를 송수신하는 것을 의미한다. 여기서 메시지란 요청, 응답, 오류 메시지 혹은 단순한 정보 등의 작은 데이터가 될 수 있다.
쉽게, 메시지 큐는 메시지를 임시로 저장하는 간단한 버퍼라고 생각하면 된다. 메시지를 전송 및 수신하기 위해 중간에 메시지 큐를 두는 것이다.
간단한 원리 : 메시지 전송 시 생산자(Producer)로 취급되는 컴포넌트가 메시지를 메시지 큐에 추가한다. 해당 메시지는 소비자(Consumer)로 취급되는 또 다른 컴포넌트가 메시지를 검색하고 이를 사용해 어떤 작업을 수행할 때까지 메시지 큐에 저장된다. 각 메시지는 하나의 소비자에 의해 한 번만 처리될 수 있는데, 이러한 이유로 메시지 큐를 이용하는 방식을 일대일 통신이라 부름.
사용하기 적합한 곳 : 메시지 큐는 소비자(Consumer)가 실제로 메시지를 어느 시점에 가져가서 처리하는 지는 보장하지 않는다. 언젠가는 큐에 넣어둔 메시지가 소비되어 처리될 것이라고 믿는 것이다. 이러한 비동기적 특성 때문에 메시지 큐는 실패하면 치명적인 핵심 작업보다는 어플리케이션의 부가적인 기능에 사용하는 것이 적합하다.
ex) 다른 곳의 API로 부터 데이터 송수신이 가능, 다양한 애플리케이션에서 비동기 통신 가능, 이메일 발송 및 문서 업로드가 가능, 많은 양의 프로세스들을 처리 가능
- 이메일 전송
어떤 웹 사이트의 비밀번호를 잊어버려서 이메일을 통해 임시 비밀번호를 받거나, 새로운 회원가입을 위한 인증 코드를 받아본 경험이 있을 것이다. 우리는 이러한 상황들에서 이메일이 즉각적으로 수신되기를 기대하지는 않는다. 아무리 성격이 급한 사람이라도 몇 분 안에 오겠거니 생각할 것이다. 어느 정도의 응답 지연이 허용되며, 어플리케이션의 핵심 기능은 아닌 경우이므로 메시지 큐는 이런 경우 도움이 될 수 있다.
이메일 전송 전용 서비스는 이메일이 어느 서비스로부터 생산되었는지와는 관계없이, 메시지 큐의 메시지를 하나씩 소비하고, 그저 이메일이 전송되어야 할 곳으로 이메일을 전송한다. - 블로그 포스팅
모든 블로그 사용자가 웹에 최적화되어 있거나, 용량이 작은 이미지만 업로드하진 않을 것이다. 블로그 사용자가 게시글에 업로드한 이미지의 용량이 매우 큰 경우를 생각해보자. 블로그 서비스의 응답 시간을 저해하지 않으면서 사용자들에게 유연성을 제공하는 방법으로, 사용자가 업로드한 모든 이미지를 게시 과정에서 즉각 처리하는 것이 아닌, 사후처리하며 최적화하는 방법이 있다. 사용자 경험에 약간의 영향을 미칠 수는 있지만, 최적화는 응용 프로그램에서 가장 중요한 것은 아니며 작업을 즉시 수행할 필요도 없다. 메시지 큐는 이러한 상황에서도 사용될 수 있다.
- 사용자가 고용량의 이미지가 포함된 블로그 포스팅을 한다.
- 이미지는 저장소에 전송된다.
- 업로드된 이미지에 대한 정보가 포함된 메시지를 이미지 최적화 서비스의 메시지 큐에 담는다.
- 이미지 최적화 서비스는 저장소에서 이미지를 가져와 최적화하고, 2번에서 저장해놨던 이미지를 대체한다.
메시지 큐의 이점
- 비동기(Asynchronous): Queue에 넣기 때문에 나중에 처리할 수 있습니다.
- 비동조(Decoupling): 애츨리케이션과 분리할 수 있습니다.
- 탄력성(Resilience): 일부가 실패 시 전체에 영향을 받지 않습니다.
- 과잉(Redundancy): 실패할 경우 재실행 가능합니다.
- 보증(Guarantees): 작업이 처리된걸 확인할 수 있습니다.
- 확장성(Scalable): 다수의 프로세스들이 큐에 메시지를 보낼 수 있습니다.
[ 참고 : https://tecoble.techcourse.co.kr/post/2021-09-19-message-queue/ ]
◼️ 2-4. Handler ◼️
Handler : 서로 다른 쓰레드간의 통신을 위한 장치, 메세지큐로 메인 스레드에서 처리할 메시지를 전달하는 역할, MessageQueue에 보낼 데이터를 넣고 Looper를 통해 처리할 데이터를 받고 보내는 중간 브로커 같은 역할, 기본 생성자를 통해 Handler를 생성하면 해당 Handler 를 호출한 스레드의 MessageQueue와 Looper에 자동 연결된다.
Handler 메시지 처리 방법 세 가지
- 방법 1 : obtainMessage()로 메세지 큐에 잇는 메시지 객체를 하나 참조 가능함
- 방법 2 : 메세지 객체에 필요한 정보를 넣은 후 sendMessage()로 메세지 큐에 넣을 수 있다.
//쓰레드 객체 생성하는 곳에서 핸들러로 sendEmptyMessage 함수를 통해 데이터를 보낸다.
//이것은 핸들러 객체 생성하는 곳에서 파라미터 msg.what으로 받을 수 있다. - 방법 3 : 메세지 큐에 들어간 메세지는 순서대로 핸들러가 처리하게 되며 이때 handleMessage() 메서드에 정의된 기능이 수행된다.
// 이 때 HandleMessage()에 들어있는 코드가 수행되는 위치는 메인 스레드이다.
정리하자면 핸들러는 항상 자신을 생성하는 쓰레드에 부착된다. 그 쓰레드의 메시지큐를 통해 다른 쓰레드와 통신을 진행한다.
보통의 경우 다른 쓰레드에서 보낸 메시지를 수신하지만, 자기자신이 보낸 메시지를 수신할 수 있다.
◼️ 3. 실제 Handler 이용 ◼️
내가 만든 프로젝트는 중고거래 앱이다. 여기서 UI를 이용하여 쓰레드를 사용하는 기능이 무엇이 있을 지 생각을 해보았다.
내가 생각한 기능은 가장 많이 본 제품(클릭을 가장 많이 한 제품)을 몇 분 마다 Toast 메세지로 핫한 상품으로 띄어주는 것을 생각한 것이다. 실제로 적용을 해보니 앱으로 한층 더 다가간 느낌이고 나중에 실무에 가서는 더 어렵고 신중하게 다룰 것이라 생각이 된다.