Skip to content

Commit 6afc27a

Browse files
committed
[feature/WEEK 3] update WEEK3
1 parent 89d7fc0 commit 6afc27a

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# 백그라운드 스레드 part1 - HandlerThread 클래스
2+
3+
오늘은 백그라운드 스레드를 구현하는데 많이 사용되는 [HandlerThread](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/HandlerThread.java;l=40?q=handlert&sq=&hl=ko) 이 클래스에 대해 알아보면서, 지난 글에서 다뤄보았던 [Handler와 Looper 그리고 MessageQueue의 동작 방식](https://medium.com/write-android/%EB%A9%94%EC%9D%B8-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-handler-part-1-handler%EC%99%80-looper-%EA%B7%B8%EB%A6%AC%EA%B3%A0-messagequeue%EC%9D%98-%EB%8F%99%EC%9E%91-%EB%B0%A9%EC%8B%9D-f0bee443d71e) 에 대한 이해를 심화시켜보는 시간을 가져보려고 합니다. HandlerThread의 멤버에 대해 이해하고, HandlerThread가 필요한 이유를 이해해보도록 하겠습니다.
4+
5+
더불어 이 글은 아래 문서들에 대한 자세한 설명입니다. 아래 공식 문서와 FW 코드 전문을 참고하시어 보시길 추천드립니다.
6+
7+
- [HandlerThread 공식 문서](https://developer.android.com/reference/android/os/HandlerThread)
8+
9+
- [HandlerThread 오픈 소스 전문](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/HandlerThread.java;l=40?q=handlert&sq=&hl=ko)
10+
11+
## HandlerThread의 멤버
12+
13+
### HandlerThread 클래스
14+
15+
------
16+
17+
<img src="https://user-images.githubusercontent.com/59532818/138552384-4b321918-2698-4713-a147-e8acaa119859.png" alt="image" style="zoom:50%;" />
18+
19+
- `HandlerThread``Thread` 클래스를 상속받고, 내부적으로 `looper.prepare()``looper.loop()` 을 실행하는 Looper 스레드이다. (Looper 스레드란 Looper를 갖는 스레드를 의미합니다. )
20+
- `Handler`를 가진 스레드가 아닙니다. `HandlerThread`**`Looper` 스레드이면서 `Handler`에서 사용하기 위한 스레드** 입니다.
21+
- `Handler``HandlerThread` 에서 생성한 Looper에 연결하는데, 이때 `Handler` 의 세번째 생성자 `Handler(Looper looper, Handler.Callback callback)` 을 사용합니다.
22+
23+
### 필드
24+
25+
### [`mPriority`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/HandlerThread.java;l=29?hl=ko)
26+
27+
> The priority to run the thread at.
28+
29+
- 스레드를 실행할 우선 순위입니다.
30+
31+
### [`mTid`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/HandlerThread.java;l=30?hl=ko)
32+
33+
- 현재 스레드의 아이디입니다.
34+
- -1이 디폴트 값으로 설정되어있습니다.
35+
- `run()` 내부에서 `Process.myTid()` 로 초기화됩니다.
36+
- `loop()` 함수 종료 후 -1로 초기화됩니다.
37+
38+
### [`mLooper`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/HandlerThread.java;l=31?hl=ko)
39+
40+
- 현재 스레드와 연결된 루퍼입니다.
41+
- HandlerThread의 `run()`에서 자동적으로 연결됩니다.
42+
43+
### [`mHandler`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/HandlerThread.java;l=32?hl=ko)
44+
45+
- 현재 스레드의 루퍼와 연결된 핸들러입니다.
46+
- `mLooper`와는 달리, 개발자가 직접 연결해주어야하는 부분입니다.
47+
48+
### 메서드
49+
50+
```java
51+
public HandlerThread(String name) {
52+
super(name);
53+
mPriority = Process.THREAD_PRIORITY_DEFAULT;
54+
}
55+
56+
57+
public HandlerThread(String name, int priority) {
58+
super(name);
59+
mPriority = priority;
60+
}
61+
```
62+
63+
### `HandlerThread 생성자`
64+
65+
- `mPriority` 값 설정 여부로 생성자가 2개로 나뉩니다.
66+
67+
- `mPriority` 매개변수가 없는 경우엔 0로 초기화 됩니다.
68+
69+
<img src="https://user-images.githubusercontent.com/59532818/138552387-9f5f16eb-80ea-4337-a974-afea592bfd82.png" alt="image" style="zoom: 50%;" />
70+
71+
```java
72+
public void run() {
73+
mTid = Process.myTid();
74+
Looper.prepare();
75+
synchronized (this) {
76+
mLooper = Looper.myLooper();
77+
notifyAll();
78+
}
79+
Process.setThreadPriority(mPriority);
80+
onLooperPrepared();
81+
Looper.loop();
82+
mTid = -1;
83+
}
84+
public Looper getLooper() {
85+
**if(!isAlive()) {**
86+
return null;
87+
}
88+
89+
synchronized(this) {
90+
**while(isAlive() && mLooper == null) {**
91+
try {
92+
**wait(); // (5)**
93+
} catch (InterruptedException e) {
94+
}
95+
}
96+
}
97+
return mLooper;
98+
}
99+
public boolean quit() {
100+
Looper looper = getLooper();
101+
if (looper != null ){
102+
looper.quit()
103+
return true
104+
}
105+
return false
106+
}
107+
108+
public boolean quitSafely() {
109+
Looper looper = getLooper();
110+
if (looper != null) {
111+
looper.quitSafely();
112+
return true;
113+
}
114+
return false;
115+
}
116+
@NonNull
117+
public Handler getThreadHandler() {
118+
if (mHandler == null) {
119+
mHandler = new Handler(getLooper());
120+
}
121+
return mHandler;
122+
}
123+
public int getThreadId() {
124+
return mTid;
125+
}
126+
```
127+
128+
### `run()`메서드
129+
130+
- `Looper.prepare()`, `Looper.loop()` 호출은 물론 멤버변수인 `mLooper``Looper.myLooper()의 반환값` 을 할당하는 일도 합니다.
131+
132+
- `getLooper()` 는 바로 `mLooper`를 반환하지 않으며, `quit()` 에서도 `mLooper.quit()` 으로 바로 Looper를 중지시키는 것이 아니라, `getLooper()` 를 통해서 얻은 Looper에 `quit()`을 호출합니다.
133+
134+
- 왜 이렇게 할까? `HandlerThread`는 Looper를 멤버 변수로 갖는다. 만약 Looper가 생성되기 전에 `getLooper()` 의 반환값은 null일 것이고, 이는 NPE로 이어질 수 있습니다. `quit()`에서도 마찬가지로 `mLooper.quit()` 을 바로 호출한다면, Looper가 아직 생성되지 않았다면 NPE가 발생합니다.
135+
136+
- 따라서 `getLooper()` 에서는 `mLooper`가 할당될 때까지 스레드를 대기시키는 작업을 하고, `mLooper`가 할당되었을 때 `mLooper`를 반환하도록 합니다.
137+
- `quit()` 에서도 `getLooper()` 에서 반환된 Looper를 가지고 `quit()` 을 하도록 하여 NPE가 발생하지 않도록 합니다.
138+
139+
### `getLooper()` 메서드
140+
141+
- `isAlive()` 호출을 통해서 `HandlerThread`에서 `start()` 메서드가 호출되었는지 체크합니다. 즉, Thread가 시작되었는지 체크합니다.
142+
143+
- `isAlive()`는 스레드가 `start()`메서드로 시작되었고, 아직 종료되지 않았을 때 `true`를 반환합니다. 종료되었을 때는 `false` 를 반환합니다.
144+
- `HandlerThread` 를 사용할 때는 항상 `start()`를 호출하여 스레드가 시작된 후에 사용해야 합니다. (특히 `getLooper()` 를 사용할때는 더욱더 유의!)
145+
146+
- `HandlerThread`가 시작된 상태라면, `mLooper`가 null 인지 체크합니다.
147+
148+
- `mLooper`가 null이라면 `wait()` 을 호출해서 `mLooper`가 할당될 때까지 `HandlerThread`를 블락상태로 만듭니다. 이 블락 상태는
149+
150+
`synchronized` 구문으로 이뤄집니다.
151+
152+
- `wait()` 메서드는 `Object` 클래스에 속한 메서드입니다.
153+
- 외부에서 `wait()`가 호출된 객체의 의 `notify()` 혹은 `notifyAll()` 를 호출할 때까지 thread가 블락상태가 됩니다.
154+
155+
- 이렇게 하는 이유?
156+
157+
`HandlerThread``start()` 메서드가 호출되고 나서, `HandlerThread``run()` 메서드가 호출되는데, 이 `run()` 실행되는 시점을 알 수 없습니다. 따라서 `getLooper()`가 실행되는 시점에 mLooper가 null일 수 있습니다.
158+
159+
- 따라서 `mLooper`에 Looper가 할당될때까지 대기하기 위해서 while 문을 돌면서 `mLooper`가 null인지를 계속해서 체크합니다.
160+
- `Thread`에서 `start()` 이 호출되면, JVM에서 해당 `Thread``run()` 를 호출합니다.
161+
- 호출 결과: 두 스레드(`start()` 를 호출한 스레드, `run()` 를 실행하는 스레드)가 동시에 실행되는 상태가 됩니다.
162+
163+
- `getLooper()``mLooper`가 직접적으로 참조되는 유일한 곳이다. 즉, `mLooper`를 사용하려면 `getLooper()`를 통해서만 얻을 수 있기에, `mLooper`로 인한 NPE가 방지됩니다.
164+
165+
1. 블락 상태인 `Thread`를 깨우는 곳: 에서 `wait()` 를 호출하여 `mLooper`가 할당될때가지 기다리다가, (2)에서 `mLooper` 가 할당된 후에 `notifyAll()` 을 통해서 `Thread`를 깨웁니다.
166+
167+
### `quit()` 메서드 `quitSafely()` 메서드
168+
169+
해당 메서드들에 대한 설명은 [직전 글의 *Looper의 동작* 부분](https://medium.com/write-android/메인-스레드와-handler-part-1-handler와-looper-그리고-messagequeue의-동작-방식-f0bee443d71e) 에서 자세히 설명하고 있으니 참고 부탁드립니다.
170+
171+
### `getThreadHandler` 메서드
172+
173+
- 현재 스레드의 루퍼와 연결된 핸들러가
174+
- 있다면 → 리턴해준다. `mHandler`를 리턴합니다.
175+
- 없다면 → 새로 생성해준다. `mHandler`를 초기화 시켜줍니다.
176+
177+
### `getThreadId` 메서드
178+
179+
- 현재 스레드의 아이디를 리턴합니다.
180+
- `run()` 함수 호출전까진, 초기값인 -1이 리턴합니다.
181+
182+
## HandlerThread가 필요한 이유
183+
184+
- 내부적으로 *looper.prepare()**looper.loop()* 을 실행하는 Looper 스레드에게 Looper가 없는 상황을 방지해줍니다.
185+
186+
- 해당 루퍼와 연결될 핸들러를 생성할 때엔 항상 `Handler(Looper looper, Handler.Callback callback)` 생성자를 사용하여, 루퍼를 명시적으로 정해줍니다.
187+
188+
- 핸들러가 달려있지 않은 스레드에 대한 메모리릭을 방지해줍니다.
189+
190+
- 개발자가 신경써야하는 위와 같은 상황들을 FW단에서 처리할 수 있게 하여, 개발자가 메세지 (작업) 에 집중할 수 있게 해줍니다.

0 commit comments

Comments
 (0)