AsyncTask를 이야기하려다 보니 프로그래스바 하나 넣어보겠다고 3~4일을 고생해서 겨우 찾아냈던 기억이 새록새록하면서 애증이 담겨있는 class라는 생각이 듭니다. 하지만 AsyncTask는  thread+handler라는 나름 강력한 기능을 가지고 있어서 제대로만 알아두면 유용한 클래스가 되지 않을까 싶습니다.

혹시 책만 보고 도전했다가 저같이 고생하지 않았으면 하는 맘에 이 글을 적어봅니다.

왜 AsyncTask인가?

AsyncTask라는 클래스 이름은 Asynchronous Task의 줄임이며, UI스레드의 입장에서 볼 때 비동기적으로 작업이 수행되기 때문에 붙여진 이름이다.

sync 와 async의 용어는 네트워크이나 커널 프로세스에서 많이 나오는 용어인데요.저야 뭐 이론적으로는 공부한지 하도 오래되어서 그냥 프로그램 하면서 경험으로 이야기를 해보겠습니다.

보통 sync는 직렬회로라고 보시면 될듯합니다. 반면 async는 병렬회로라고 보시면 될 듯 합니다.

일이 순차적으로 진행되면서 하나가 해결되면 그다음 일이 진행되는 식으로 네트워크에서는 요청(request)를 보내면 항상 응답(response)을 받아야 진행하는 방식으로 구현하면 sync방식,아니고 계속 요청을 보내는 통로와 응답을 받는 통로를 따로 만들어두면 async방식이라고 불리죠.

AsyncTask는 UI 처리 및 Background 작업 등 을 하나의 클래스에서 작업 할 수 있게 지원해 줍니다. 쉽게말해 메인Thread와 일반Thread를 가지고 Handler를 사용하여 핸들링하지 않아도 AsyncTask 객체하나로 각각의 주기마다 CallBack 메서드가 편하게 UI를 수정 할 수 있고, Background 작업을 진행 할 수 있습니다.

AsyncTask사용법

Object로부터 상속하는 AsyncTask는 Generic Class이기 때문에 사용하고자 하는 type을 지정해야 합니다.
AsyncTask클래스는 기본적으로 3개의 제네릭 타입(Params,Progress,Result)을 제공하고 있는 추상 클래스다.Generic type은 실행시간에 데이터 타입을 정할 수 있는 특수한 타입이라고 이해하시면 될 듯 합니다.

Params: background작업 시 필요한 data의 type 지정
Progress: background 작업 중 진행상황을 표현하는데 사용되는 data를 위한 type 지정
Result: 작업의 결과로 리턴 할 data 의 type 지정

예)

AsyncTask<DataInfo[], Integer, Void> InsertTask = new InsertCallTask().execute(datainfo4);

미사용 타입에 대해서는 Void를 전달하면 된다.  

AsyncTask Callbak 함수

언제 호출되여 무슨 작업을 하는가와 함께 각 메서드가 어떤 스레드에서 실행되는가도 중요하다. doInBackground메서드 외에는 모두 UI스레드에서 실행되므로 메인스레드의 view들을 안전하게 참조할 수 있습니다.

주로 쓰게 되는 콜백메스드 4개에 대해 설명해보겠습니다.

● void onPreExecute()

작업이 시작되기 전에 호출되며 UI스레드에서 실행되는 메소드,계산을 위한 초기화나 프로그래스 대화상자를 준비하는 등의 작업을 수행합니다.

		// 이곳에 포함된 code는 AsyncTask가 execute 되자 마자 UI 스레드에서 실행됨.
		// 작업 시작을 UI에 표현하거나
		// background 작업을 위한 ProgressBar를 보여 주는 등의 코드를 작성.
		@Override
		protected void onPreExecute() {
	           // 작업을 시작하기 전 할일
	        dialog = new ProgressDialog(DetailActivity.con);
	        dialog.setTitle("이미지 다운로드중");
	        dialog.setMessage("잠시만 기다리세요...");
	        dialog.setIndeterminate(true);
	        dialog.setCancelable(true);
	        dialog.show();
	        			
			super.onPreExecute();
		}	

Result doInBackground(Params... params)

백그라운드 스레드로 동작해야 하는 작업을 실행한다. execute메서드로 전달한 data tye이 params 인수로 전달되는데 여러개의 인수를 전달할 수 있으므로 배열 타입으로 되어 있습니다. 그래서 하나의 인수만 필요하다면 params[0]만 사용하면 됩니다. 작업 중에 publishProgress 메소드를 호출하여 작업 경과를 UI스레드로 display할 수 있으며 작업결과는 Result타입으로 리턴됩니다.

		@Override
		protected Bitmap doInBackground(String... urls) {
			/* http://snowbora.com/417 참고 
			 * http://android-developers.blogspot.kr/2010/07/multithreading-for-performance.html
			 * */
			final HttpClient client			= AndroidHttpClient.newInstance("Android");
	        final HttpGet getRequest 		= new HttpGet(urls[0]);
	        final int IMAGE_MAX_SIZE 		= 1280;
	        
	        try 
	        {
	            HttpResponse response = client.execute(getRequest);
	            final int statusCode = response.getStatusLine().getStatusCode();
	            
	            if (statusCode != HttpStatus.SC_OK) 
	            {
	                return null;
	            }

	            final HttpEntity entity = response.getEntity();
	            
	            if (entity != null) 
	            {
	                InputStream inputStream = null;
	                
	                try 
	                {
	                	inputStream = entity.getContent();
	                	
	                	BitmapFactory.Options bfo 	= new BitmapFactory.Options();
	                	bfo.inJustDecodeBounds 		= true;

	                    BitmapFactory.decodeStream(new FlushedInputStream(inputStream), null, bfo);
	                    
	                    if(bfo.outHeight * bfo.outWidth >= IMAGE_MAX_SIZE * IMAGE_MAX_SIZE)
	                    {
	                    	bfo.inSampleSize = (int)Math.pow(2, (int)Math.round(Math.log(IMAGE_MAX_SIZE / (double) Math.max(bfo.outHeight, bfo.outWidth)) / Math.log(0.5)));
	                    }
	                    bfo.inJustDecodeBounds = false;
	                    
	                    response = client.execute(getRequest);
	                    final int nRetryStatusCode = response.getStatusLine().getStatusCode();
	                    
	                    if (nRetryStatusCode != HttpStatus.SC_OK) 
	                    {
	                        return null;
	                    }
	                    
	                    final HttpEntity reEntity = response.getEntity();
	                    
	                    if (reEntity != null)
	                    {
	                    	InputStream reInputStream = null;
	                    	
	                    	try
	                    	{
	                    		reInputStream = reEntity.getContent();
	                    		final Bitmap imgBitmap = BitmapFactory.decodeStream(new FlushedInputStream(reInputStream), null, bfo);
	                            
	                            return imgBitmap;
	                    	}
	                    	finally 
	                        {
	                    		 if (reInputStream != null) 
	                             {
	                    			 reInputStream.close();
	                             }
	                    		 
	                    		 reEntity.consumeContent();
	                        }
	                    }
	                } 
	                finally 
	                {
	                    if (inputStream != null) 
	                    {
	                        inputStream.close();
	                    }
	                    
	                    entity.consumeContent();
	                }
	            }
	        } 
	        catch (IOException e) 
	        {
	            getRequest.abort();
	        } 
	        catch (IllegalStateException e) 
	        {
	            getRequest.abort();
	        } 
	        catch (Exception e) 
	        {
	            getRequest.abort();
	        } 
	        finally 
	        {
	            if ((client instanceof AndroidHttpClient)) 
	            {
	                ((AndroidHttpClient)client).close();
	            }
	        }
	        
	        return null;
	        
	    }

void onProgressUpdate(Progress... values)

doInBackground에서드에서 publishProgress(Progress...) 메소드를 호출할 때 호출되며 작업의 진행사항을 표시하기 위해 호출됩니다. UI스레드에서 프로그래스바에 진행 상태 표시하는 역할 수행합니다.

		// onInBackground(...)에서 publishProgress(...)를 사용하면
		protected void onProgressUpdate(Integer... progress) {
			Log.d("LOST","onProgressUpdate:["+progress[0]+"]");
			dialog.setProgress(progress[0]);
		}

 

void onPostExecute(Result result)

doInBackground에서드의 작업 결과를 UI반영하는 역할을 담당하는 메소드입니다.

		// onInBackground(...)가 완료되면 자동으로 실행되는 callback
		// 이곳에서 onInBackground가 리턴한 정보를 UI위젯에 표시 하는 등의 작업을 수행함.
		// (예제에서는 작업에 걸린 총 시간을 UI위젯 중 TextView에 표시함)
		@Override
		protected void onPostExecute(Bitmap imgBitmap) {
			if (imgBitmap != null)
			{
				imgView.setImageBitmap(imgBitmap);
				imgView.setVisibility(ImageView.VISIBLE);
				textview.setVisibility(TextView.GONE);
			}else{
				//textview = (TextView)findViewById(R.id.textView1);
				textview.setText("등록된 사진이 없습니다.");
				textview.setTextSize(20);
				textview.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL);
				textview.setVisibility(TextView.VISIBLE);
				imgView.setVisibility(ImageView.GONE);
			}
		
			dialog.dismiss();
		}
		

일반적으로 AsyncTask를 구현할 때 별도 스레드로 동작해야 할 코드는 doInBackground()메소드에 작성하고, 이 메소드가 실행되기 전에 먼저 처리할 코드가 있다면 onPreExecute()메소드에 작성하면 됩니다. 그리고 doInBackground()메소드가 완료된 후에 처리해야할 코드는 onPostExecute()메소드에 작성하고, doInBackground()메소드가 동작하는 동안 주기적으로 반영해야 할 작업이 있다면 onProgressUpdate()메소드에 작성하면 됩니다.

이 때 주의사항은 3개의 제네릭 타입과 메소드 타입을 잘 맞춰어야 원하는 결과가 나온다는 겁니다.이게 잘 안 맞아서 저는 코딩하면서 구현이 제대로 안되고 한참 고생했던 기억이 있습니다. 꼭 doInBackground, onPreExecute, onPostExecute의 리턴값과 파라미터를 잘 매칭해야 합니다.

 

참고사이트

+ Recent posts