태그 보관물: dialog

Android Dialog를 안전하고 간단하게 종료하기..

안드로이드 애플리케이션을 개발하다 보면, Dialog 부류의 위젯을 많이 사용하게 된다. 그리고 이 위젯을 화면에 보여주고 완료하면 dismiss()를 호출해서 종료를 시킨다. 이 과정에서 java.lang.NullPointerException이 발생할 수 있다. 그리고 Activty가 Dialog보다 먼저 종료(finish() 호출되는 등)가 되는 상황에서는 아래의 예외를 발생시킨다. java.lang.IllegalArgumentException: View not attached to window manager.

아래는 API 문서의 구조로, Dialog 부류의 최상위는 DialogInterface라는 것을 알 수 있다.

위 구조에서 dismiss()를 가지고 있는 클래스는 Dialog로, 이 클래스의 구조도는 다음과 같다.

ProgressDialog를 사용하면서 dismiss()를 하는 일반적이 코드는 다음과 같다.

if(progressdialog != null && progressdialog.isShowing())
{
   progressdialog.dismiss();
}

이 코드는 대체로 동작을 하지만, 위에서 언급한 예외가 발생할 수 있기에, 위의 코드를 try ~ catch로 감싸는 코드를 종종 보게 된다. 그래서 dismiss()를 안전하고 verbose하지 않게 처리하기 위해서 아래와 같은 간단한 유틸 클래스를 사용하면 좋다.

public class DialogDismisser {
	public static void dismiss(DialogInterface d) {
		if(d == null)
			return;
		
		try {
			if(d instanceof AlertDialog) {
				if(((AlertDialog) d).isShowing())
					((AlertDialog)d).dismiss();
				
				return;
			}
					
			if(d instanceof ProgressDialog) {
				if(((ProgressDialog) d).isShowing())
					((ProgressDialog)d).dismiss();
				
				return;
			}
			
			if(d instanceof Dialog) {
				if(((Dialog) d).isShowing())
					((Dialog)d).dismiss();
				
				return;
			}
		} catch(Exception e) {
			Log.e("dissmiss error", e);
		}
	}

	public static void dismiss(DialogInterface d1, DialogInterface d2) {
		dismiss(d1);
		dismiss(d2);
	}
}

이 클래스를 사용해서 각종 Dialog의 dismiss()를 안전하게 종료시킬 수 있다. 혹시 cancel()을 사용한다면 이 클래스와 동일한 형태로 작성해서 사용하면 된다.

안드로이드(Android) Dialog의 cancel()과 dismiss()의 차이

안드로이드(Android)에서 다이얼로그(Dialog)는 앱이 자주 사용하는 UI 컴포넌트이다. 다이얼로그를 보여주는 데는 show() 세드를 사용하고, 종료하는데는 cancel(), dismiss() 메서드를 사용할 수 있다. 종료하는 이 두 메서드에 대해서 API 문서에서는 아래와 같이 설명하고 있다. UI 경험이 별로 없다보니 잘 감이 안 온다. 화면에서 다이얼로그가 종료되는 동작은 같기에 차이가 없어 보인다.

종료하는데 사용하는 두 메서드의 API 문서를 살펴보면 다음과 같다.

void cancel()

Cancel the dialog.
void dismiss()

Dismiss this dialog, removing it from the screen.

이 두 메서드의 차이에 대해서 살펴보자. API 문서만으로 차이점을 알기가 쉽지 않으니, 다이얼로그 API 소스에서 차이를 확인해보자.

1. cancel() 메서드 소스

    /**
     * Cancel the dialog.  This is essentially the same as calling {@link #dismiss()}, but it will
     * also call your {@link DialogInterface.OnCancelListener} (if registered).
     */
    public void cancel() {
        if (!mCanceled && mCancelMessage != null) {
            mCanceled = true;
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mCancelMessage).sendToTarget();
        }
        dismiss();
    }

위 canel() 메서드는 다음의 단계를 거치게 된다. 그리고 이 메서드는 OnCancelListener로 등록한 객체가 있으면 호출해 준다.

  • mCancelMessage가 있으면 메시지를 전달한다.
  • dismiss()를 호출한다.

2. dismiss() 메서드

 
    /**
     * Dismiss this dialog, removing it from the screen. This method can be
     * invoked safely from any thread.  Note that you should not override this
     * method to do cleanup when the dialog is dismissed, instead implement
     * that in {@link #onStop}.
     */
    public void dismiss() {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(mDismissAction);
        } else {
            mHandler.removeCallbacks(mDismissAction);
            mDismissAction.run();
        }
    }

위 dismiss() 메서드는 화면에서 다이얼로그를 종료하는 역할을 하고 스레드 세이프 하다. 스레드 세이프 하다는 것으로 UI 스레드만이 다이얼로그를 종료시킨다는 것도 알 수 있다. 다이얼로그는 mDismissAction.run() 메서드가 종료시킨다. 이 메서드는 dismissDialog() 메서드를 호출하고, 이 메서드에서는 윈도 매니저를 사용해서 다이얼로그를 윈도에서 제거한다.

그리고 다이얼로그가 화면에 보이는 상황에서 백 키를 누르면 아래의 소스와 같이 동작한다. 즉 cancel() 메서드를 호출해서 다이얼로그를 종료시킨다. 따라서 백키의 이벤트로 다이얼로그가 종료하는 상황은 DialogInterface.OnCancelListener 인터페이스를 구현하면 되겠다.

    /**
     * Called when the dialog has detected the user's press of the back
     * key.  The default implementation simply cancels the dialog (only if
     * it is cancelable), but you can override this to do whatever you want.
     */
    public void onBackPressed() {
        if (mCancelable) {
            cancel();
        }
    }

위에서 살펴본 cancel()과 dismiss()와 같이 API 문서를 봐도 차이점이 잘 이해가 되지 않는 경우에는 API 소스를 보면서 확인하는게 매우 도움이 된다.