<안드로이드/Android> 안드로이드에서 스레드란?

Posted by 앱해피
2015. 6. 20. 21:51 안드로이드 이론

 

안드로이드(Android)에서 메인 스레드(Main Thread)란 무엇인가?

 

어플리케이션이 실행됐을 때, 시스템에서는 "Main"이라 불리는 어플리케이션을 실행하는 스레드를 생성한다.


"Main" 스레드는 안드로이드 이벤트 생성 및 처리를 담당할 뿐만 아니라 안드로이드에서 발생되는 여러 이벤트를 그와 관련된 위젯으로 연동시키는 역할을 하기 때문에 매우 중요하다.

 

또한, 안드로이드에서 제공하는 다양한 뷰와 위젯을 표현하는 역할과 사용자로 하여금 그것들을 사용할 수 있게 해준다. 그와 같은 이유로, 메인 스레드를 UI(User Inferface) 스레드 라고도 부른다.

 

안드로이드 시스템은 각각의 컴포넌트를 위해서 별도의 스레드를 생성하지 않는다. 동일한 프로세스에서 동작하는 모든 안드로이드 컴포넌트는 UI 스레드에서 처리된다. 그리고 각각의 컴포넌트에 대한 시스템 호출은 UI 스레드로 전달 된다.

 

결과적으로, 시스템 콜백에 반응하는 여러 메소드들(ex. onKeyDown() 또는 LifeCycle과 관련된 각각의 메소드)은 프로세스의 UI 스레드에서 동작하게 된다.

 

예를 들어, 사용자가 스크린 상에 버튼을 클릭했을 때, 어플리케이션에서 사용되는 UI 스레드는 발생된 터치 이벤트를 위젯에 전달하게 된다. 그 순간 버튼은 눌린 상태(pressed stated)가 되며, 이벤트 큐에 invalidate 요청을 보내게 된다. UI 스레드에서는 전달된 요청을 수집해서, 이벤트가 발생된 뷰로 하여금 뷰를 다시 그리도록 한다.

 

어플리케이션이 유저의 요청에 반응해서 아주 복잡한 연산을 처리해야 할 때, 어플리케이션이 완벽하게 구현되어 있지 않을 경우 단일 스레드 모델에서는 형편없는 성능을 낳게 된다. UI 스레드에서 모든 작업을 처리하는 모델의 단점은 네트워크 접속 또는 데이터베이스 쿼리와 같이 오래 걸리는 작업을 하는 동안 UI 관련된 작업은 처리되지 못한다.

 

이것이 문제가 되는 이유는, 오랜 시간 동안에 UI 관련 작업이 처리되지 못하면 ANR(applicatio not responding)이라는 에러가 발생하게 된다. 그 결과 어플리케이션은 정지 된다.

 

추가적으로, 안드로이드 UI 관련 함수는 스레드에 안전하지 못하다. 그렇기 때문에 UI관련 작업을 Worker thread에서 처리하면 안된다. 반드시 UI 관련 함수는 UI 스레드에서 처리하도록 하자.

 

작업 스레드(Worker Thread)

 

위에서 언급했던 단일 스레드 모델 때문에, UI 스레드를 블록하지 않음으로써, 어플리케이션의 UI의 반응성을 최상으로 유지해야 한다. 만약에 부가적으로 처리해야 할 작업이 있는 경우에는, 그와 관련된 작업을 별도의 스레드에서 처리하도록 해야 한다.("background" or "worker" 스레드)

예를 들어, 아래의 코드는 별도의 스레드에서 이미지를 다운로드 받는 클릭 리스터와 그리고 그것을 이미지 뷰를 통해서 보여주는 것이다.

 

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 스레드에서 처리해야 하기 때문이다. 이 같은 코드는 디버깅하기도 어려운 에러를 발생시키기 때문에 해결하는데 시간이 오래 걸린다.

 

이와 관련된 문제를 해결하기 위해, 안드로이드에서는 다른 스레드에서 UI 스레드로 작업을 전달해줄 수 있는 여러가지 방법을 제공한다.

 

* Activity.runOnUiThread(Runnable)


* View.post(Runnable)


* View.postDelayed(Runnable, long)

 

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();
}

 

 

이제부터 스레드에 안전한 방식으로 구현하는 방법 : 네트워크와 관련된 기능은 별도의 스레드에서 처리하면서 ImageView를 처리하는 작업은 UI 스레드에서 처리하기.

 

하지만, 이와 같은 방식으로 코드를 작성하는 것은 코드를 복잡하게 하고 유지보수가 힘들게 만든다. worker 스레드를 사용해서 복잡한 연산을 처리하기 위해서는 worker 스레드에서 UI 스레드로 메시지를 전달하는 핸들러를 사용하는 것을 고려해야 한다.

 

아마 이것이 최고의 방법이겠지만, AsyncTask 클래스를 상속받아서 더 간단하게 처리할 수 있는 방법도 존재한다.

 

AsyncTask 사용하기

 

 

AsyncTask를 사용하면 사용자 인터페이스에 비동기 작업을 수행 할 수 있다. AsyncTask를 사용하면 Worker 스레드에서 blocking 기능을 수행할 수 있게 해주며, UI 스레드에 그 결과를 전달할 수 있게 해준다.

 

이와 같은 방법을 사용하기 위해서는, AsyncTask의 서브클래스를 생성해서 doInBackground() 콜백 함수를 구현해야 한다. doInBackground()는 백그라운드 스레드를 동작시키는 역할을 한다. UI를 업데이트하기 위해서는 onPostexecute()라는 메소드를 구현해야 하는데, 이 메소드를 이용해서 UI 스레드로 결과를 전달할 수 있다.

 

이 방식을 이용하면 UI를 메인스레드를 이용해서 안전하게 업데이트 할 수 있다. UI 스레드에서 execute()를 호출함으로써 AsyncTask를 동작시킬 수 있다.

 

예를 들어, 아래에 나와있는 방법과 같이 AsyncTask를 이용할 수 있다.

 

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);
    }
}

 

이제 UI와 관련된 작업을 안전하게 처리할 수 있고, 코드도 간단해진다. Work 스레드가 담당하는 영역과 UI 스레드가 담당하는 역할이 명백하게 구분되어 있기 때문이다.

 

* doInBackground() 함수에 정의된 내용은 Worker 스레드에서 실행된다.

 

* onPreExecute(), onPostExecute(), and onProgressUpdate()와 관련된 작업은

UI 스레드에서 담당한다.

 

* doInbackground()에서 반환되는 값은 onPostExecute()로 반환된다.

 

* doInBackground() 함수에서 언제든지 publishProgress() 메소드를 호출할 수 있는데,

이 함수를 호출하면 UI 스레드에서 onProgressUpdate()를 실행하게 된다.

 

* 언제든지 AsyncTask에서 처리하는 작업을 끝낼 수 있다.

 

감사합니다 ^_^