안드로이드 애플리케이션이 데이터를 로딩하는데 AsyncTask를 사용하고, 사용자에게 데이터 로딩을 알려주려고 ProgressDialog를 같이 사용하는 경우를 종종 볼 수 있다. 아래의 그림이 이 예제이다.
위 그림의 상황에서, 사용자가 다른 화면을 보길 원하거나 더는 로딩하기 원하지 않는 경우에도 ProgressDialog 클래스의 cancelable 변수를 false로 설정해서 백 키를 눌러도 데이터 로딩창을 종료하지 않고 계속 보이는 경우를 볼 수 있다. UX의 관점에서 보면, 이런 경우는 좋지 않다.
같은 상황에서 iOS 애플리케이션들의 UX를 보니, 많은 경우 화면을 전환하기 전에 데이터를 로딩하고 로딩을 완료하면 화면을 전환한다. 이 UX로 인해서 사용자는 로딩을 취소하고 쉽게 다른 화면으로 전환할 수 있다. 물론 꼭 그렇지는 않지만, 필자의 경우에는 비교적 iOS의 UX가 안드로이드보다 좋게 느껴진다. 그래서 안드로이드 애플리케이션도 ProgressDialog 창을 종료시키고, 데이터 로딩과 같은 비동기 요청(AsyncTask를 사용하는 경우)을 바로 취소하는 방법을 사용해서 비교적 UX를 개선할 수 있다. 사용자에게 조금 더 좋은 UX를 제공할 수 있는 이 방법을 살펴보자.
1. 백 키를 사용해서 종료하는 ProgressDialog 객체 생성
pd = new ProgressDialog(this); pd.setTitle(""); pd.setMessage("Loading..."); pd.setCancelable(true); pd.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { Log.d(TAG, "onDismissed() "); task.cancel(true); //task.cancel(false); } });
이 코드는 ProgressDialog 객체를 생성하면서 백 키로 창을 종료하면서 Dismiss 이벤트를 받아서 비동기 데이터를 로딩하는 태스크인 task를 취소한다.
2. 비동기로 데이터를 로딩하는 AsyncTask 클래스
static class LoadingTask extends AsyncTask<String, Integer, Boolean> { private ProgressDialog pd = null; public LoadingTask(ProgressDialog pd) { this.pd = pd; } @Override protected void onPreExecute() { super.onPreExecute(); if(pd != null) pd.show(); } @Override protected Boolean doInBackground(String... params) { try { Thread.sleep(1000 * 20); } catch(InterruptedException e) { Log.d(TAG, "Exception : " + e.getLocalizedMessage()); } return Boolean.TRUE; } @Override protected void onCancelled(Boolean result) { Log.d(TAG, "onCancelled : " + result); if(pd != null) pd.dismiss(); } @Override protected void onPostExecute(Boolean result) { Log.d(TAG, "onPostExecute : " + result); if(pd != null) pd.dismiss(); } }
이 클래스는 비동기로 데이터를 로딩하는 형태로 비동기의 태스크 로직 메서드에서 20초를 Sleep하고 있다. 그리고 이 태스크의 취소에 대한 콜백 메서드인 onCancelled(Boolean result) 메서드를 확인할 수 있다.
이제 위에서 살펴본 코드를 사용해서 사용자가 백 키를 누른 경우, ProgressDialog를 종료하고 LoadingTask도 종료시킬 수 있다. AsyncTask를 종료하는 방법으로 cancel(boolean mayInterruptIfRunning) 메서드를 호출한다. 이 메서드를 호출하면 내부에서 cancelled 변수를 true로 설정한다. 그리고 mayInterruptIfRunning를 true로 호출하면 바로 종료시키기 위한 인터럽트를 던지게 된다. 그래서 AsyncTask가 IO를 looping하면서 읽어들이지 않는 경우에는 cancel(true)로 바로 종료시킬 수 있다.
다음으로 ProgressDialog가 종료하면서 AsyncTask를 취소하는 메서드의 호출 결과를 살펴보자.
– 아래는 cancel(false)을 호출한 결과이다.
01-30 05:56:05.720: D/MainActivity(1416): onDismissed() 01-30 05:56:24.136: D/MainActivity(1416): onCancelled : true
이 결과로 cancel(false)을 호출하면, doInBackground()가 종료된 후에 onCancelled가 호출된 것을 알 수 있다.
– 아래는 cancel(true)을 호출한 결과이다.
01-30 05:58:33.038: D/MainActivity(1584): onDismissed() 01-30 05:58:33.038: D/MainActivity(1584): Exception : null 01-30 05:58:33.038: D/MainActivity(1584): onCancelled : true
cancel(true)을 호출 결과로 AsyncTask의 doInBackground() 메서드에서 InterruptedException이 발생하게 되고, 이것으로 바로 태스크 로직을 빠져나오게 한다.
이상 살펴본 형태로 AsyncTask와 ProgressDialog를 결합해서 사용하는 경우에 사용자의 반응에 즉시 반응할 수 있는 형태로 개발해서 UX를 개선할 수 있다.