해당 포스트는 "안드로이드 비동기 프로그래밍" 책의 내용을 요약한 것이다.
※ AsyncTask
: AsyncTask는 추상클래스로써 상속 받은 하위 클래스는 다음의 메서드를 재정의 할 수 있다.
- protected void onPreExecute() : AsyncTask.execute()를 호출하면 바로 실행되는 메서드다. 내부 작업을 완료하면 doInBackground가 호출된다.
- protected Result doInBackground(Params... params) : 백그라운드 작업을 한다.
- protected void onProgressUpdate(Progress... values) : doInBackground 메서드에서 publishProgress() 메서드를 호출하면 onProgressUpdate가 호출되 UI를 주기적으로 바꿀 수 있다.
- protected void onPostExecute(Result result) : doInBackground에서 백그라운드 작업이 완료되면 호출되고 백그라운드 처리 결과가 result 값으로 전달되고 UI를 결과값으로 바꿀 수 있다.
- protected void onCancelled(Result result) : doInBackground가 끝나기 전에 AsyncTask의 cancel 메서드를 호출하면 호출되는 메서드로 이 때는 onPostExecute가 호출되지 않는다.
여기서 doInBackground와 publishProgress 는 백그라운드에서 실행되고 나머지 재정의 메서드들은 메인 스레드에서 실행된다. 따라서 onPreExecute(), onProgressUpdate(), onPostExecute()에서 UI를 변경할 수 있다.
- AsyncTask 타입 선언
abstract class AsyncTask<Params, Progress, Result>
: 위 파라미터는 doInBackground에 "Params" 파라미터를 전달하고 진행 관련 메서드에 "Progress" 타입을 전달하고 결과 값으로 "Result" 타입을 반환한다는 걸 의미한다. 참고로 Void는 void를 나타내는 인스턴스화할 수 없는 클래스이다.
ex) 해당 예는 안드로이드 비동기 프로그래밍 책에 있다.
public class Example4Activity extends Activity { private PrimesTask task; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.ch2_example_layout); ((TextView)findViewById(R.id.title)).setText(R.string.ch2_ex4); ((TextView)findViewById(R.id.description)).setText(R.string.ch2_ex4_desc); Button goButton = (Button) findViewById(R.id.go); final TextView resultView = (TextView) findViewById(R.id.result); goButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { task = new PrimesTask(Example4Activity.this, resultView); task.execute(500); } }); } @Override protected void onPause() { super.onPause(); if (task != null) task.cancel(false); } }
PrimesTask task = new PrimesTask(Example4Activity.this, resultView).execute(500);
public class PrimesTask extends AsyncTask<Integer, Integer, BigInteger> { private Context ctx; private ProgressDialog progress; private TextView resultView; public PrimesTask(Context ctx, TextView resultView) { this.ctx = ctx; this.resultView = resultView; } //백그라운드가 실행되기 전에 사용자와 상호작용할 수 있도록 다이얼로그를 보여준다. @Override protected void onPreExecute() { progress = new ProgressDialog(ctx); progress.setTitle(R.string.calculating); progress.setCancelable(true); progress.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { PrimesTask.this.cancel(false); } }); progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progress.setProgress(0); progress.setMax(100); progress.show(); } @Override protected BigInteger doInBackground(Integer... params) { int primeToFind = params[0]; BigInteger prime = new BigInteger("2"); for (int i=0; i<primeToFind; i++) { prime = prime.nextProbablePrime(); int percentComplete = (int)((i * 100f)/primeToFind); publishProgress(percentComplete); //다이얼로그 값을 갱신하기 위해 호출한다. if (isCancelled()) break; } return prime; } //다이얼로그 값을 실제로 갱신한다. @Override protected void onProgressUpdate(Integer... values) { progress.setProgress(values[0]); } //백그라운드가 끝난 후 결과값을 UI에 보여주고 사용자와 상호작용했던 다이얼로그를 종료한다. @Override protected void onPostExecute(BigInteger result) { resultView.setText(result.toString()); progress.dismiss(); } @Override protected void onCancelled(BigInteger result) { resultView.setText("cancelled at " + result.toString()); progress.dismiss(); } }
public abstract class SafeAsyncTask<R> extends AsyncTask<Void, Void, SafeAsyncTask.Result<R>> { static class Result<T> { private T result; private Exception exc; } protected abstract R doSafelyInBackground(Void... params) throws Exception; @Override protected final Result<R> doInBackground(Void... params) { Result<R> result = new Result<R>(); try { result.result = doSafelyInBackground(params); } catch (Exception exc) { result.exc = exc; } return result; } @Override protected final void onPostExecute(Result<R> result) { if (result.exc != null) { onCompletedWithException(result.exc); } else { onCompletedWithResult(result.result); } } @Override protected final void onCancelled(Result<R> result) { onCancelledWithResult(result.result); } // override me if you want to handle exceptions protected void onCompletedWithException(Exception exc) { Log.e(LaunchActivity.TAG, exc.getMessage(), exc); } // override me if you want to handle the result protected void onCompletedWithResult(R result) { } // override me if you to handle the partial result // but _don't_ call super if you override protected void onCancelledWithResult(R result) { onCancelled(); } }
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params)
exec에는 AsyncTask.SERIAL_EXECUTOR, AsyncTask.THREAD_POOL_EXECUTOR, Executor 객체가 들어갈 수 있다. SERIAL_EXECUTOR는 작업을 큐에 담고 순차적으로 실행되는 단일 백그라운드 스레드를 사용한다. THREAD_POOL은 스레드 풀을 사용해 작업한다. 최소 5개의 스레드를 유지하고 128개까지 확장할 수 있다. 또한 다음과 같이 Executor 객체를 인자로 줄 수 있다.
private static final Queue<Runnable> QUEUE = new LinkedBlockingQueue<Runnable>(); public static final Executor MY_EXECUTOR = new ThreadPoolExecutor(4,8,1,TimeUnit.MINUTES,QUEUE); task.executeOnExecutor(MY_EXECUTOR, params);
protected void onPause(){ super.onPause(); if((task != null) && (isFinishing())) task.cancel(false); }
위와 같이 하면 다른 액티비티로 전환되어 해당 액티비티가 스택에 있을 때 백그라운드 작업을 완료할 수도 있다.
2. retained headless fragment의 사용
: 만약 구성 변경 때문에 액티비티가 종료돼도 백그라운드 작업을 계속 하고 재시작한 액티비티에서 결과를 출력하려면 retained headless fragment를 사용하면 된다. retained는 액티비티가 재시작해도 프래그먼트가 그대로 유지된다는 뜻이다. 프래그먼트에서 setRetainInstance(true)를 호출하면 된다. 그러면 프래그먼트 생명주기에서 onDestroy()와 onCreate() 메서드가 호출 안 되 프래그먼트가 유지된다. 이를 통해 백그라운드 작업을 계속 유지할 수 있다. headless는 프래그먼트가 액티비티의 뷰를 관리하지 않는다는 뜻이다. 즉, 액티비티 뷰를 참조하지 않는다. 따라서 AsyncTask는 프래그먼트에서 분리되어야 하고 AsyncTask는 사용자 인터페이스와 직접 상호작용하지 않아 뷰 계층 객체에 대한 참조에 대해 누수할 가능성이 적다. 다음은 retained headless fragment 예제이다.
public interface AsyncListener<Progress, Result> { void onPreExecute(); void onProgressUpdate(Progress... progress); void onPostExecute(Result result); void onCancelled(Result result); }
액티비티에서 위 인터페이스를 구현해야 한다. 그래야 fragment에서 분리된다.
public class Example5Activity extends FragmentActivity implements AsyncListener<Integer, BigInteger> { public static final String PRIMES = "primes"; private ProgressDialog dialog; private TextView resultView; private Button goButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.ch2_example_layout); ((TextView)findViewById(R.id.title)).setText(R.string.ch2_ex5); ((TextView)findViewById(R.id.description)).setText(R.string.ch2_ex5_desc); resultView = (TextView)findViewById(R.id.result); goButton = (Button)findViewById(R.id.go); goButton.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { PrimesFragment primes = (PrimesFragment) getSupportFragmentManager().findFragmentByTag(PRIMES); //프래그먼트가 있는지 확인해야 한다.(retained fragment이기 때문) if (primes == null) { primes = new PrimesFragment(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.add(primes, PRIMES); transaction.commit(); } } }); } public void onPreExecute() { onProgressUpdate(0); } public void onProgressUpdate(Integer... progress) { if (dialog == null) { prepareProgressDialog(); } dialog.setProgress(progress[0]); } public void onPostExecute(BigInteger result) { resultView.setText(result.toString()); cleanUp(); } public void onCancelled(BigInteger result) { resultView.setText("cancelled at " + result); cleanUp(); } private void prepareProgressDialog() { dialog = new ProgressDialog(this); dialog.setTitle(R.string.calculating); dialog.setCancelable(true); dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { PrimesFragment primes = (PrimesFragment) getSupportFragmentManager().findFragmentByTag(PRIMES); primes.cancel(); } }); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); dialog.setProgress(0); dialog.setMax(100); dialog.show(); } private void cleanUp() { dialog.dismiss(); dialog = null; FragmentManager fm = getSupportFragmentManager(); PrimesFragment primes = (PrimesFragment) fm.findFragmentByTag(PRIMES); fm.beginTransaction().remove(primes).commit(); } }
package com.packt.asyncandroid.chapter2.example5; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.Fragment; import java.math.BigInteger; public class PrimesFragment extends Fragment { private AsyncListener<Integer,BigInteger> listener; private PrimesTask task; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); task = new PrimesTask(); task.execute(2000); } @Override public void onAttach(Activity activity) { super.onAttach(activity); listener = (AsyncListener<Integer,BigInteger>)activity; } @Override public void onDetach() { super.onDetach(); listener = null; } public void cancel() { task.cancel(false); } class PrimesTask extends AsyncTask<Integer, Integer, BigInteger> { @Override protected void onPreExecute() { if (listener != null) listener.onPreExecute(); } @Override protected BigInteger doInBackground(Integer... params) { int primeToFind = params[0]; BigInteger prime = new BigInteger("2"); for (int i=0; i<primeToFind; i++) { prime = prime.nextProbablePrime(); int percentComplete = (int)((i * 100f)/primeToFind); publishProgress(percentComplete); if (isCancelled()) break; } return prime; } @Override protected void onProgressUpdate(Integer... values) { if (listener != null) listener.onProgressUpdate(values); } @Override protected void onPostExecute(BigInteger result) { if (listener != null) listener.onPostExecute(result); } @Override protected void onCancelled(BigInteger result) { if (listener != null) listener.onCancelled(result); } } }
'안드로이드 > 비동기, 멀티스레드' 카테고리의 다른 글
6. 동시성 Service, 메신저, 로컬 서비스(Bind), 로컬 브로드캐스트 (1) | 2017.07.18 |
---|---|
5. IntentService, PendingService, 통지 (0) | 2017.07.17 |
4. Loader 기본/응용(AsyncTaskLoader, CursorLoader) (0) | 2017.07.16 |
3. Handler와 HandlerThread 기본/응용 (0) | 2017.07.15 |
1. 달빅 가상 머신, 스레드 (0) | 2017.07.13 |