Thread with Handler & AsyncTask



reference: 

https://developer.android.com/reference/android/os/Handler?hl=ko

https://developer.android.com/reference/android/os/Looper?hl=ko

https://academy.realm.io/kr/posts/android-thread-looper-handler/

https://developer.android.com/reference/android/os/AsyncTask?hl=ko

https://stackoverflow.com/questions/12797550/android-asynctask-for-long-running-operations?noredirect=1&lq=1





Android 앱에서의 GUI 작업은 메인스레드(UI스레드)에서만 가능하도록 제한되어있습니다. 그 이유는 메인스레드에서는 GUI에 관련한 작업을 하도록 강제하여 오래 걸리는 작업은 다른 스레드를 이용하도록 하기 위함입니다. 만약 다른 스레드에서도 허용한다면 같은 View 요소에 대한 동기화 이슈가 생길 것입니다. 이를 해결하기 위해 Android에서는 스레드에 Handler & Looper 클래스를 도입하였습니다.


 Looper & Handler

Android의 메인스레드는 내부적으로 message queue와 Looper를 가집니다. Message queue의 역할은 이름 그대로 message를 전달받아 선입선출 방식으로 하나씩 해결하기 위한 자료구조입니다. Message는 다른 스레드나 현재 스레드로부터 받을 수 있습니다. Looper 클래스는 그 message queue로부터 message를 하나씩 꺼내어 Handler에게 처리하도록 전달하는 역할을 합니다. 그럼 Handler는 전달 받은 message를 처리하고, 반대로 message를 받아 message queue에 집어 넣는 역할도 합니다.


Handler

모든 Handler 객체는 하나의 스레드와 그 스레드의 message queue에 연결됩니다. Handler 객체를 하나 생성하면, 생성하고 있는 스레드에 바인드됩니다. 그 순간부터 Handler는 message나 Runnable 객체를 받아 처리하기 시작합니다.

Handler는 2가지 목적으로 사용됩니다.

1. 미래 언젠가에 처리될 message와 runnable을 스케쥴

2. 자기 자신이 아닌 다른 스레드에게 처리하도록 전달

1번의 경우 post...() 및 sendMessage...() 메소드로 이루어집니다. post가 붙은 메소드들은 runnable 객체를 message queue에 전달하고, send message류의 메소드들은 handleMessage() 메소드에 의해 처리될 message들을 전달합니다.

Handler에 post나 send를 할 땐, 준비가 됐을 때 바로 처리하도록 하게 하거나, 지연되어 처리하도록 할 수 있습니다. 지연된 처리의 경우 timeout, tick과 같은 timing 관련 처리도 지원합니다.


Looper

스레드의 message 관련 loop을 돌게 해주는 클래스입니다. 기본적인 스레드는 message loop 관련 처리를 하지 않습니다. 따라서 스레드 안에서 message loop을 돌게 하기 위해서는, 스레드 안에서 Looper의 prepare() 정적 메소드를 호출한 뒤 loop() 정적 메소드를 호출해야 합니다. 메인스레드는 기본적으로 Looper를 갖지만, 사용자가 정의한 스레드에서는 갖지 않기 때문에 message 처리를 하려고 한다면 필수적으로 이 과정이 수행되어야 합니다. Looper는 무한히 loop을 돌며 message queue의 message나 runnable을 차례로 꺼내 처리할 Handler에게 전달합니다.


다음은 전형적인 Looper와 Handler를 갖는 스레드의 예시입니다.

mHandler에 원하는 message나 runnable을 전달할 수 있습니다.

class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } }

이런 이유로 편의를 위해 만들어진 클래스가 HandlerThread 클래스입니다. Java의 기본 스레드는 Android의 Looper를 갖지 않기 때문에 이것을 미리 가지도록 처리한 것이 HandlerThread입니다.

아래 그림은 위의 설명을 이해하는데 도움이 됩니다.

android looper message에 대한 이미지 검색결과


 AsyncTask

AsyncTask 클래스는 메인스레드를 적절하고 쉽게 사용할수 있도록 만들어진 클래스입니다. 백그라운드 작업을 가능하게 해주고, Handler나 Looper가 없이도 그 결과에 대한 UI 작업을 메인스레드에게 수행하도록 하게합니다.

AsyncTask는 최대 수초 정도의 비교적 작은 작업을 하는 것이 이상적입니다. 만약 긴 작업을 해야한다면, Executor, ThreadPoolExecutor나 FutureTask 등의 java.util.concurrent 패키지를 사용하는 것이 권장됩니다.

여기에서 비동기 작업이란, 백그라운드 스레드에서 작업을 한 뒤 그 결과를 메인스레드에 전달하는 것을 의미합니다. AsyncTask는 Params, Progress, Result의 세가지 generic type을 갖습니다. 또한 4개onPreExecute() -> doInBackground -> onProgressUpdate -> onPostExecute 의 순서로 전개됩니다. 아래는 전형적인 AsyncTask의 사용 예입니다.

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     
protected Long doInBackground(URL... urls) {
         
int count = urls.length;
         
long totalSize = 0;
         
for (int i = 0; i < count; i++) {
             totalSize
+= Downloader.downloadFile(urls[i]);
             publishProgress
((int) ((i / (float) count) * 100));
             
// Escape early if cancel() is called
             
if (isCancelled()) break;
         
}
         
return totalSize;
     
}

     
protected void onProgressUpdate(Integer... progress) {
         setProgressPercent
(progress[0]);
     
}

     
protected void onPostExecute(Long result) {
         showDialog
("Downloaded " + result + " bytes");
     
}
 
}
 

URL을 Param으로 가지고, 정수를 Progress로, 그 결과 값을 Long으로 택했습니다. doInBackground() 메소드에서 URL로부터 파일을 다운 받으면서, 중간에 publicProgress()를 호출해서 그 progress를 UI에 업데이트 하도록 하고있습니다. onProgressUpdate()에서 그 것을 수행합니다. 또한 작업이 완료 된 후 onPostExecute()가 호출되어 다운로드한 최종 사이즈를 표시합니다.

이 모든 과정에서 Handler에 message를 전달하거나 처리하는 과정이 없이, 자연스럽게 GUI 작업을 한 것을 확인할 수 있습니다.


Conclusion

Android의 메인스레드에서는 GUI 작업만하고, 그외의 작업은 GUI가 블락되는 것을 막기 위해 다른 스레드에서 작업하도록 권장됩니다. 이를 수행하기 위해 Looper와 Handler 클래스가 제공됩니다.

스레드에서 Looper를 동작 시키면 내부의 message queue를 돌면서 하나씩 Handler에게 전달합니다. Handler는 전달받은 message나 runnable을 처리하거나, 반대로 message나 runnable을 전달 받아 message queue에 넣습니다.

AsyncTask는 위의 Handler나 Looper의 직접 구현 없이도 간단하게 사용되도록 고안된 클래스입니다. 다만 수초 정도의 비교적 작은 작업을 수행하면서 GUI 업데이트를 하는 데에 이상적이기 때문에, 더 오래걸리는 작업은 다른 방법을 생각해야 합니다.

결론적으로, 두가지 방법 모두 익힌 상태에서 상황에 따라 Handler와 Looper 혹은 AsyncTask를 선택할 수 있어야 겠습니다.

'programming > android' 카테고리의 다른 글

View - RecyclerView  (0) 2018.08.26
ViewGroup  (0) 2018.08.24
View - AppWidget  (0) 2018.08.23
View - Widget  (0) 2018.08.23
Companion Objects in Kotlin  (0) 2018.04.08

+ Recent posts