Tistory View

Android Develop/helper

Android ASyncTask 사용

God Dangchy What should I do? 2019. 6. 26. 01:32

ASyncTask 개요

예를들어, 안드로이드에서 리스트뷰 등을 사용할 때, 리스트뷰의 각각의 아이템마다 이미지를 그릴 경우가 많다.  이 경우 이미지를 불러와서 그려야 하는 데, 문제는 미리 메모리로 올려져있는 이미지가 아닐 경우, UI Thread에서 이미지를 로드할 경우 일반적으로 데이터크기가 큰 이미지를 처리하면 스크롤시 화면이 뚝뚝 끊기게 된다. 이미지를 저장장치에서 읽고, 이 것은 대부분 압축이 되어 있는 상태이므로 압축을 풀고 화면에 그려야 하는 데, 읽고 압축을 푸는 두 과정은 시간이 많이 드는 작업이다. 프로세서가 빨라지는 속도만큼 이미지의 크기도 커지고 화면도 해상도가 올라가기 때문에 시간이 지나 프로세서가 빨라 진다고 해도 좀처럼 이 끊김이 없어지는 걸 기대 할 수는 없는 상황이다.

 

이 뚝뚝 끊기는 것을 없애기위해 개발자들은 주로 이 시간이 많이 걸리는 작업은 새로운 Thread를 만들어 작업을 하고 작업이 끝나면 화면에 그리는 것으로 처리한다. 그러면 이 뚝뚝 끊기는 문제를 해결할 수 있다.

 

안드로이드에서는 사용자와 상호작용하는 Thread를 UI Thread[Main Thread]라고 하는 데, 이 Thread에서는 긴 작업을 하면 사용자가 불편함을 느낀다. 너무가 긴 시간동안 작업을 하면 화면의 정보를 처리하지 못해서 멈춘 것처럼 보이기도 한다(ANR).

 

이런 상황에서 편하게 프로그래밍을 하기위해 Android에서는 ASyncTask라는 클래스를 제공해 주고 있다.

동작방식을 다음과 같다.

 

ASyncTask의 처리 방식은 위에서 보듯 UI Thread에서 작업되는 부분과 다른 Thread에서 작업되는 부분이 분리되어 있어, 각각에 맞는 코드만 작성하면 된다.

 

 

구현을 위한 기본 사항

AsyncTask는 Template형태로(Java에서도 Template이라고 표현하는 지 모르겠다.) 작성하는 법은 다음과 같다.

 

 

Template에 사용되는 파라미터들을 먼저 살펴 보도록 한다.

Template의 파라미터는 Primitive-Type을 쓸 수 없으니, Object-Type으로 써야한다. 즉 int를 사용할 수 없고 Integer를 사용해야 한다. 만약 필요없는 파라미터는 "Void"[V 대문자]를 사용하면 된다.

 

첫번째 파라미터 : execute의 파라미터형, doInBackground의 파라미터 형

두번째 파라미터 : onProgressUpdate의 파리미터 형

세번째 파라미터 : doInBackground의 리턴형, onPostExecute의 파라미터 형

 

왜 이렇게 되어있는 지는 다음을 보면 알 수 있다.

 

실행과 동작방식

 

execute함수로 실행을 하며, execute함수는 다음과 같이 선언되어있다.

 

public final AsyncTask<Params, Progress, Result> execute(Params... params);

 

여기서 주목할 것은 파라미터가 가변형태로 파라미터를 여러개 넘겨 줄 수 있으며, 파라미터 없이 실행을 할 수도 있다.

이 파라미터들은 doInBackground함수로 전달이 된다. doInBackground는 UI Thread가 아닌 다른 Thread[이하 작업Thread]에서 실행되면 나머지는 전부 UI Thread에서 실행된다.

 

 

doInBackground 함수

꼭 만들어야 하는 함수로, 실제 작업 Thread에서 처리하는 코드를 넣는 곳이다. 파라미터는 execute에서 넘어온 값을 받는다. 이 함수의 리턴값은 onPostExecute함수로 전달이 된다.

파라미터가 가변형이므로 하나의 작업과 관련된 파라미터를 보내도 되고, 여러작업을 지속적으로 처리할 수 있어서 입맛대로 쓰면 된다.

 

onPreExecute함수

doInBackground함수 이전에 처리할 작업이다. 초기화 코드를 넣어두면 좋다. UI Thread에서 실행 되므로, UI Thread가 필요없는 부분은 doInBackground에서 처리하는 게 좋다.

 

onPostExecute 함수

doInBackground함수의 리턴값을 받아 처리하면 된다. 주로 이미지를 그린다던지하는 화면상의 변화하는 코드를 넣는다.

만약 UI Thread에서 처리할 작업이 없다면 구현하지 않아도 된다.

 

onProgressUpdate함수

onProgressUpdate함수는 현재 처리 작업에 얼마나 되었는지를 구현할 때 사용되며, 필요없다면 빼버려도 된다.

 

 

Progress처리

사용자에게 현재 얼마나 처리되었는 지 보여주고 싶은 경우나 너무 오랜 시간이 걸려 사용자에게 보여 줘야 하는 경우[파일을 다운로드받는 경우 등]는  onProgressUpdate를 구현해야 한다. doInBackground함수내에서 publishProgress함수를 중간중간 필요한 시점마다 호출하면 된다. onProgressUpdate는 UI Thread에서 실행되기 때문에 화면에 그리기 동작이 가능하다. publishProgress함수도 가변형의 파라미터로 onProgressUpdate도 가변형 파라미터 형태를 가진다.

 

작업취소

cancel 함수를 이용하여 취소할 수 있다. 이미 실행중이 Thread를 멈추는 것이 아니다. doInBackground함수내에서 isCancelled함수로 중간중간 체크를 해야 되고, onPostExecute에서도 필요하다면 체크를 해야한다. (주1)

cancel함수에 따른 동작방식은 구글의 API를 꼭 읽어보고 처리해야 한다. 좀 복잡하다. https://developer.android.com/reference/android/os/AsyncTask.html#cancel(boolean)

 

주의사항 - 중요함!

1. execute함수는 UI Thread에서만 호출해야한다. "Looper를 가진 Thread면 되지 않을까?"라는 생각을 해보았는 데 @MainThread annotation이 붙은 걸로 봐서 그냥 UI Thread에서만 된다고 봐야 할 듯하다.

2. 100개를 실행시켜도 100개가 동시에 실행되지 않는다. 실제 1개만 실행되고 이 작업이 끝나면 다음 작업이 실행되는 방식이다(주2). 필자의 경우 이 걸 몰라서 처리가 안되는 부분이 발생한 적이 있다.

3. 재사용할 수 없다. 지속적으로 새 Object를 만들어서 실행시켜야 한다.(꾸짐)

4. API16[젤리빈]미만에서는 객체생성을 꼭 UI Thread에서해야 한다.

 

 

 

다음 포스트에서는 실제 List에 이미지를 로드하는 코드를 작성해 보겠다.

 


주1) 실제 필자의 경우 onPostExecute에 isCancelled를 안 쓴 적이 별로 없다.. 대부분의 상황이 그랬다..

주2) 이게 안드로이드 버전별로 다르다. API 3(CupCake)부터 추가가 되었는 데, 이 때는 1개씩 처리가 되었다. API4(Donut)가 되면서 동시에 처리가 되었는데(여기서도 최대 실행갯수의 제한이 있다.), 이러다 보니 문제가 생겨 API 11(HoneyComb)부터 다시 1개로 변경되었다. 굳이 동시처리를 쓰고 싶다면 executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)를 사용하면 된다.

게다가 언제부터인지는 모르겠지만 AsyncTask.THREAD_POOL_EXECUTOR를 사용하지 않아도 갯수의 제한은 있지만 병렬로 처리되고 있다.

 

TAG
Replies
Reply Write