일반적으로 Network 관련 operation은 특정 서버에 대한 request와 해당 서버로 부터의 response로 이루어진다. 

예를 들어 브라우저에 http://technote.kr 을 입력하게 되면 해당 주소에 대한 resource를 요청(request)하게 되는 것이고, 이에 대한 결과로 html data들이 전달(response)되게 된다. 

이와 같은 network 를 통한 Resource request 이후 response가 오기 까지 다소간 지연이 발생하게 되는데, 간단한 샘플 코드나 테스트 코드 작성시에는 아무런 고려없이 구현해도 상관없겠지만 실제 UI와 연동된 코드 작성시에는 이를 고려하여 코드를 작성해야 한다. 

사용자에게 직접적으로 노출되어 있어 약간의 delay 만 발생해도 사용자가 쉽게 알아차릴 수 있는 UI 코드 내에 Network 동작이 추가될 경우 network request와 response가 발생할 때마다 지연이 발생해 사용자의 입장에서 버벅임을 느낄 수 있다.

따라서 이와 같은 경우를 고려해 Network 관련 코드는 UI 관련 코드에서 분리된 별도의 thread로 구분하여 처리하여야 한다. 안드로이드 내에서 이와 같은 목적으로 별도의 thread를 구현하기 위해서 AsyncTask class를 제공하고 있고, 이를 이용해 network 관련 코드들이 UI 에 영향을 미치지 않도록 구현할 수 있다. 

본 글에서는 AsyncTask에 대해서 간략하게 알아보고 해당 AsyncTask를 통해 image (Youtube thumbnail)를 다운로드 받아 화면에 표시(List)할 경우 발생하는 문제점에 대해서 알아보고자 한다. 

[Youtube thumbnail 위치 및 확인]

AsyncTask


AsyncTask를 이용하기 위해서는 3가지 Type 정의를하고 이를 통해 4가지의 절차들을 수행하도록 되어 있다. 


- 3가지 Type

Params, 4가지 절차 중 doInBackground() 수행시 넘길 parameter type

Progress, doInBackground() 수행 중 publishProgress() 를 호출하면 parameter를 onProgressUpdate()에서 받게 되는데 이 때의 parameter type

Result, doInBackground() 에서 모든 수행을 마치고 onPostExecute() 에 parameter를 넘기게 되는데 이 때의 parameter type


- 4가지 절차

onPreExecute()

실제 작업이 doInBackground()에서 진행되기 전에 UI 쓰레드 상에서 수행된다.

doInBackground(Params...)

onPreExecute() 종료 직후 별도의 쓰레드 상에서 처리되는 부분이다. UI쓰레드와 별도로 백그라운드에서 처리된다. 루틴 수행 도중 publishProgress(Progress...) 를 호출하여 현재 진행 상황을 UI 쓰레드에 전달할 수 있다. 

onProgressUpdate(Progress...)

doInBackground()내에서 onProgressUpdate(Progress...)를 호출할 경우 UI 쓰레드 상에서 수행되는 부분이다. AsyncTask를 상속 받은 class 내에 정의되어 있지만 UI 쓰레드 상에서 callback으로 수행된다.

onPostExecute(Result)

doInBackground() 가 종료되면 UI 쓰레드 내에서 수행되는 부분이다. doInBackground() 수행 결과가 parameter로 전달된다.


위의 구조를 예를 들면 다음과 같다. 

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     @Override
     protected void onPreExecute() {
         super.onPreExecute();
     }

     @Override
     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;
     }

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

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


실제 KidsTube에서 사용한 AsyncTask는 다음과 같다. 

Thumbnail을 다운로드 받는 용도의 class를 작성하였기 때문에 입력값은 URL 주소로 String을 넘겨주었고, 결과는 이미지로 Bitmap 형태로 return 하였다. 

추가로 별도의 progressbar가 필요없었기 때문에 onProgressUpdate는 구현하지 않았다. 

public class DownloadThumbnail extends AsyncTask<String, Void, Bitmap> { private final WeakReference<ImageView> imageViewReference; Bitmap bitmap; public DownloadThumbnail(ImageView imageView) { imageViewReference = new WeakReference<ImageView>(imageView); } @Override protected Bitmap doInBackground(String... params) { String urlparam = params[0]; try { URL url = new URL(urlparam); bitmap = BitmapFactory.decodeStream(url.openStream()); return bitmap; } catch (Exception e){ return null; } } @Override protected void onPostExecute(Bitmap result) { if (isCancelled()) { result = null; } if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); if (imageView != null) { if (result != null) imageView.setImageBitmap(result); else imageView.setImageResource(R.drawable.ic_launcher); } } } }


AsyncTask 의 문제점


AsyncTask는 android sdk에 기본으로 들어 있는 class로 UI와 Network 작업을 분리시켜 Network 수행에 있어 발생하는 지연등이 UI 표현에 영향을 미치지 않는다는 점에서 유용하다. 

하지만 해당 방식을 통해 Network 작업을 하게 될 경우 기존에 요청하여 응답을 받은 리소스라고 할지라도 반복적으로 서버에 요청하여 신규로 받게되는 문제점이 있다. 즉, 별도의 cache 기능이 없어 그 수행 자체가 효율적이지 않다.

AsyncTask는 별도의 cache가 없기 때문에 List 상에서 순차적으로 Youtube thumbnail을 읽어오게 되면 아래와 같이 이미지가 표시되는 것을 확인할 수 있다. 

문제는 받아온 이미지를 List에 표시할 경우 한번 화면에 뿌리고 나면, 별도로 저장하는 코드가 없기 때문에 리스트의 앞쪽으로 다시 돌아가 보면 다시 해당 화면에 표시될 Thumbnail을 다시 다운로드 받는 상황이 벌어지게 된다. 

  

결국은 다운로드 받은 thumbnail을 저장할 cache를 별도로 구현해야 하는 어려움이 있다. 

사실 AsyncTask 로 구현을 다하고 나서 확인한 문제로 이에 대한 문제점을 해결한 Library들이 존재하는 것을 확인할 수 있었다. 이를 해결하기 위한 Network 관련 library로는 volley, okhttp 등이 있고, image 관련 library는 glide 등이 추천되고 있다. 

각 library 들은 다양한 조합으로 사용이 가능한데, 현재 발생하고 있는 AsyncTask 의 문제점을 해결하기 위해서 추가로 Volley 자체만으로 구현, okhttp+Glide 조합으로 구현을 해보고자 한다.