본문 바로가기
2023년 이전/Android

Android 백그라운드 - Thread, Handler ,Looper의 차이

by JeongUPark 2019. 11. 12.
반응형

누가 Thread, Handler, Looper의 차이를 물어봣는데, 알고 있는데 선듯 대답을 하지 못해서 다시 정확하게 공부하기 위해서 글을 씁니다.

 

Thread는 간단하게 말해서 하나의 독립적인 실행 흐름으로 생각할 수 있습니다. 그리고 하나의 프로그램이 실행 될때 최초로 실행되는 Thread를 mainThread라고 합니다. 프로세스의 시작과 동시에 무조건 실행되는, 프로세스의 가장 중요한 스레드이기 때문에 메인 스레드라고 부릅니다.

 

그리고 android에서 MainThread에서만 UI를 컨트롤 할 수 있습니다. 그 이유는 간단하게 생각해서 MainThread가 아닌 다른 Thread에서 동시에 UI를 컨트롤 하게 될경우 어느 thread의 동작을 수행해야할지 예측할 수 없고, 둘 중에 하나의 동작은 버려지기 때문입니다.

그래서 android에서는 MainThread에서만 UI 동작을 컨트롤하고 (그래서 UI Thread라고도 합니다.)  이런 원칙을 싱글 Thread 모델 이라 합니다. 이 싱글쓰레드를 원칙은 첫째, 메인 스레드(UI 스레드)를 블럭하지 말 것, 둘째, 안드로이드 UI 툴킷은 오직 UI 스레드에서만 접근할 수 있도록 할 것 입니다.

 

왜 MainThread에 대해서 설명을 했냐면 MainThread에는 내부적으로 Looper를 가지고 있고, 그 안에는 MessageQueue를 포함하고 있습니다. 그래서 Looper가 MessageQueue에 message나 Runnable 객체를 선입선출로 보관하였가다, 차례로 꺼내게 됩니다. 그리고 Handler는 Looper로 부터 전달 받은 message나 Runnable를 처리하거나 다른 Thread로 부터 받은 message를 MessageQueue에 넣습니다.

그럼 쫌 더 자세히 알아보겠습니다.

 

Handler

Handler는 위에서 말했다 시피 MessageQueue로 부터 받은 Message나 Runnable을 처리합니다. Handler는 하나의 Thread와 그 MessageQueue에 종속됩니다. 그래서 새로운 Handler를 만들 경우 그 Handler를 만든 Thread에 연결됩니다. (그래서 android에서 MainThread에서 handler를 만들어서 사용하면 UI를 컨트롤 할 수 있습니다.)

그리고 위 그림에서 처럼 다른 Thread에서 특정 handler를 통하여 Message를 전달할 경우 그 동작은 Message를 전달한 handler와 연결된 Thread의 MessageQueue에 들어가 동작하게 됩니다.

간단한 동작 code를 보면

Kotlin

var mHandler = Handler(Handler.Callback{
 msg->
 //doing
 false
})

mHandler.sendMessage(msg)

var mRunnable = Runnable{
 //doing        
}

var mHandler2 = Hander()
mHandler2.post(mRunnable)

JAVA

Handler mHandler = new Handler(new Handler.Callback(){
	@Override
    public boolean handleMessage(Message msg) {
        //doing
    	return false;
    }
 }
);
Message msg = new Message();
Handler.sendMessage(msg);


Runable runable = new Runnable(){
  @Override
  public void run(){
   // doing
  }
};

Handler mHandler2 = new Handler();
mHandler2.post(runnable);

같은 형태를 취합니다. 즉, Message의 경우 Handler에서 MessageQueue에 전달 후 전달한 Handler의 handleMessage에서 받아서 동작합니다. Runnable의 경우도 handler를 통해 MessqeQueue에 전달 후 차례가 되면 Runnable의 run()에서 동작합니다. 뿐만 아니라 deleay를 주면 그 동작을 늦출 수 있습니다. (간단하게 Handler.sendMessageDelayed(msg, 3000)를 하면 3초있다 massage를 전달합니다.) 

 

LooperMessageQueue

Looper는 Thread에서 무한히 돌면서 자신이  속한 Thread의 MessageQueue에 message나 runnable이 들어오면 차례대로(선입선출)로 Handler에 전달하는 역할을 합니다. MainThread의 경우 자동으로 Looper가 생성 되지만, 새로 생성한 Thread의 경우 Looper가 없고 run만 존재해, run의 동작이 끝날경우 Thread는 종료가 됩니다. 그래서 새로 생성한 Thread에서는 Looper.prepare();를 통하여 Looper를 생성하고 Looper.loop()를 통하여 무한히 돌면서 MessageQueue에 Message Runnable을 차례대로 handler에 전달 하도록 해야합니다. 그리고 Looper의 종료는 quit()나 quitSafely()를 통하여 종료할 수 있습니다. quit()는 바로 종료, quitSafely()는 MessageQueue에 쌓인 Message 혹은 Runnable을 다 꺼내어 전달 후 종료합니다.

간단한 code를 보면

Thread thread = new Thread(){
 @Override
 public void run(){
  super.run();
  Looper.prepare();
  if(종료를 원할 경우){
   Looper.myLooper().quit();
  }
  Looper.loop();
 }
};

와 같이 사용합니다. 위의 code에서 myLooper는 현재 Thread에서 사용중이 Looper를 반환합니다.

 

 

MessageRunnable

그럼 지금까지 계속 언급한 Message와 Runnable은 무엇일까요? Message는 Thread간 통신을 할 내용을 담는 객체이자 MessageQueue에 들어갈 일감 단위 입니다. Runnable은 Thread를 생성할 때 생기는 run() 메서드를 따로 분리 시킨 형태라 생각하면 좋습니다. 그래서 Runnable은 run() 추상 메서드를 반드시 구현해야 합니다. 이 Runnable의 run에 실행동작이 들어 갑니다.

 

HandlerThread

위에서 설명했듯이 MainThread가 아닌 새로 생성한 Thread들은 Looper가 없습니다. 하지만 HandlerThread로 생성을 하면 자동으로 Looper가 생성 되고, MessageQueue도 생성 됩니다.

 

runOnUiThread

mainThread에 Runnable을 전달하는 기능을 합니다. 현재 쓰레드가 UI Thread라면 바로 실행이 되고, UI Thread가 아니라면 UI Thread의 자원을 가져와서 실행합니다. (UI Thread는 계속 언급했지만 MainThread를 말합니다.)

사용 방법은

Activity.runOnUiThread(new Runnable(){
  @Override
  public void run(){
  //doing
  }
});

이고 runOnUiThread의 상세 code를 보면

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

으로 handler를 통해 runable을 전달하는 것을 볼 수 있습니다.

 

AsynckTask

AsynckTask는 android에서 제공하는 class로 위에서 설명한 handler thread looper등을 몰라고 UI 컨트롤이나 background작업을 할 수 있도록 하는 class 입니다. 

구현 code는

 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
	@Override protected void onPreExecute() {
    	super.onPreExecute(); 
    } 
    @Override protected Long doInBackground(URL... urls) {
    	// 전달된 URL 사용 작업 
    	return total; 
    }
    @Override protected void onProgressUpdate(Integer... progress) {
    // 파일 다운로드 퍼센티지 표시 작업 
    } 
    @Override protected void onPostExecute(Long result) {
    // doInBackground 에서 받아온 total 값 사용 장소 
    }

 }
 

 

1.먼저 onPreExecute에서 background 에서 동작 수행전 동작을 수행합니다.

2.그리고 doInBackground에서 계속 동작을 하며

2-1.doInBakground에서 publishProgress를 호출 할때마다 onProgressupdate이 호출 되어 UI를 업데이트 할 수 있습니다.

3.그리고 doInBackground에서 모든 동작을 마치고 return한 값으로 onPostExecute에서 최종 동작을 마무리 합니다.

 

실행 방법은 DownloadFilesTask.excute()로 실행합니다.

 

참고 :

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

https://developer.android.com/

https://recipes4dev.tistory.com/143

반응형