현치비

Main Thread(메인 스레드)와 Worker Thread(작업 스레드) 본문

Android/Basic

Main Thread(메인 스레드)와 Worker Thread(작업 스레드)

현CHIBI 2017. 8. 23. 21:01

Main Thread(메인 스레드)와 Worker Thread(작업 스레드)


오늘은 안드로이드 메인 스레드에 대해서 알아보려고 한다.


앱은 일반적으로 프로세스 하나 위에 여러 멀티 스레드가 돌고 있는 형태이다. (물론, 멀티 프로세스도 가능하긴 하다.)


https://brunch.co.kr/@kd4/3 (프로세스와 스레드)


멀티 스레드를 사용하면 성능은 향상된다.


하지만, UI를 멀티 스레드로 변경해버린다면,,? 원하는 UI로 그려지지 않을 확률도 높고, 여러 이상현상이 발생할 것이다.


그래서 안드로이드는 UI를 그리는 스레드는 오직 Main(UI) 스레드에서만 가능하도록 설계되었다.


그래서 메인스레드 혹은 UI 스레드라고 부른다. (같은 말임)


메인 스레드가 아닌 작업(Worker) 스레드(or 백그라운드 스레드)에서 UI를 변경하려고 한다면 Exception이 발생한다.


우리가 일반적으로 onCreate()에 때려 박는 메소드들은 다 UI 스레드에서 동작한다.


새로운 작업 스레드를 만들어서 실행하고 싶다면?


new Thread().start(); 를 하면 된다. 간단함.


다음 예시는 작업 스레드를 만들어서 이미지 데이터를 받아와서 ImageView에 그리는 작업을 하는 코드이다.


public void onClick(View v) {
   
new Thread(new Runnable() {
       
public void run() {
           
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView
.setImageBitmap(b);
       
}
   
}).start();
}

데이터를 받아오는 것은 문제가 없다.


하지만, ImageView에 그리는 작업은 UI 작업이다.


즉, UI 스레드에서 동작해야 하는데, 작업 스레드 안에서 동작한다.


mImageView.setImageBitmap(b); 이부분에서 Exception이 발생한다.

그렇다면 어떻게 해야할까?


ImageView에 post라는 메서드가 있는데, UI 작업을 실행할 코드를 Runnable.run()에 감싸면 된다.


public void onClick(View v) {
   
new Thread(new Runnable() {
       
public void run() {
           
final Bitmap bitmap =
                    loadImageFromNetwork
("http://example.com/image.png");
            mImageView
.post(new Runnable() {
               
public void run() {
                    mImageView
.setImageBitmap(bitmap);
               
}
           
});
       
}
   
}).start();
}

이렇게 귀찮은 짓을 꼭 해야 하나? 라고 생각이 들 수 있겠지만!


인터넷에서 데이터를 받아오는 작업은 오래 걸릴 수 있다. (이미지나 동영상을 얻을 때 or 데이터 통신이 느린 장소에 있을 때)


메인 스레드에서 데이터를 받아오는 작업을 계속 기다리다가 사용자는 움직이지 않는 앱을 보고 속이 터져 죽을지도 모른다.


또한, 5초 이상 앱이 반응하지않으면 ANR이 발생한다. 


       <Application Not Responding>


그리고 API 11부터는 데이터 통신은 무조건 작업 스레드에서 해야한다. 안그러면 Exception이 발생한다.


즉, onCreate()에 모든 코드를 때려박았다가는 문제가 된다.


맨날 new Thread()를 해서 결과값을 받아오면 view.post()로 UI를 업데이트를 해줘야하는게 귀찮다면


AsyncTask를 쓰면 된다.


AsyncTask를 찾아보면 많은 블로그가 나오니 찾아보면 되고,


핵심만 요약하자면, 


doInBackground() 메서드는 작업 스레드에서 동작하는 녀석이고 (네트워크 통신을 하면 됨)


onPostExecute() 메서드는 메인 스레드에서 동작하는 녀석이다 (UI 업데이트를 하면 됨)


public void onClick(View v) {
   
new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
   
/** The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */

   
protected Bitmap doInBackground(String... urls) {
       
return loadImageFromNetwork(urls[0]);
   
}

   
/** The system calls this to perform work in the UI thread and delivers
      * the result from doInBackground() */

   
protected void onPostExecute(Bitmap result) {
        mImageView
.setImageBitmap(result);
   
}
}


그 외에도 Handler나 runOnUiThread() 메서드를 사용해서 업데이트를 해도 된다.


그래도 AsyncTask가 가장 많이 사용되고 쉬우니 많이 사용하는 것을 추천한다.


SQLite에서 데이터를 찾는 작업도 작업 스레드에서 동작하는 것을 추천한다.


오늘의 결론은


오래 걸리는 작업은 작업 스레드에서, UI 업데이트만 메인 스레드에서 작업하자.


이다.

Comments