본문 바로가기
안드로이드

[안드로이드] Dialog.dismiss()를 할 때, java.lang.IllegalArgumentException: View not attached to window manager 예외 회피하기

by 호군 2013. 3. 8.
반응형

안드로이드에서 Dialog를 사용할 때는 dissmiss() 메소드 호출을 안 할 수가 없습니다. 그런데 종종 이 dismiss() 메소드 호출을 하다보면 'java.lang.IllegalArgumentException: View not attached to window manager' 라는 예외가 발생 할 때가 있습니다.


저는 이 예외가 발생하는 상황을 찾기 위해서 몇 가지 상황에 대해서 테스트를 했습니다. 그리고 이 예외가 발생하는 한 가지 상황을 찾았습니다. 


Dialog가 보여지고 있는 상태에서 Activity.finish()를 한 뒤, Dialog.dismiss()를 호출하면 'java.lang.IllegalArgumentException: View not attached to window manager' 예외가 발생하게 됩니다.


그러나 이와 같은 상황이였어도 어디서 finish와 dismiss를 호출하는지에 따라서 발생하는 경우가 있고, 발생하지 않는 경우가 있었습니다. 이것은 약간의 타이밍 문제입니다.. 

(finish를 하게되면 ActivityManagerService와도 통신을 하고, ActivityThread.H.handleMessage()에서 뷰 제거 등을 처리하기 때문으로 보임)


이 예외가 발생하게 되는 상황을 인위적으로 만들어 보겠습니다. 이 코드는 어떤 상황에 발생하게 되는지 알기 위한 코드이기 때문에 비현실적인(?) 코드 입니다. 실제 이렇게 프로그래밍을 하지는 않는다는 말입니다. 오해하지마시길!


public class MyActivity extends Activity {

    private final String TAG = "MyActivity";

    

    private final int MSG_DISMISS_AFTER_FINISH = 0;

    private final int MSG_ONLY_DISMISS = 1;


    private Handler mHandler = new Handler() {

        public void handleMessage(Message msg) {

            switch (msg.what) {

            case MSG_DISMISS_AFTER_FINISH:

                Log.d(TAG, "to finish activity.");

                finish();

                sendEmptyMessageDelayed( MSG_ONLY_DISMISS, 2000);

                break;

            case MSG_ONLY_DISMISS:

                if (mDialog != null ) {

                    Log.d(TAG, "to dismiss dialog. is dialog showing? "+mDialog.isShowing());

                    mDialog.dismiss();

                }

                break;

            }

        }

    };


    private AlertDialog mDialog;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        mDialog = new AlertDialog.Builder(this)

                .setTitle("Title")

                .setMessage("This is for Dialog Test")

                .create();

        mDialog.show();


        mHandler.sendEmptyMessageDelayed(MSG_DISMISS_AFTER_FINISH, 5000);

    }

}



위의 코드를 실행하시면, 아래와 같은 logcat을 얻을 수 있습니다. 

D/DialogTestActivity(20064): called onCreate

D/DialogTestActivity(20064): called onStart

D/DialogTestActivity(20064): called onResume

D/DialogTestActivity(20064): to finish activity.

D/DialogTestActivity(20064): called onPause

D/DialogTestActivity(20064): called onStop

D/DialogTestActivity(20064): called onDestroy

E/WindowManager(17637): Activity com.dhna.example.DialogTestActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@44f63488 that was originally added here

E/WindowManager(17637): android.view.WindowLeaked: Activity com.dhna.example.DialogTestActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@44f63488 that was originally added here

E/WindowManager(17637): at android.view.ViewRoot.<init>(ViewRoot.java:247)

E/WindowManager(17637): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:148)

E/WindowManager(17637): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)

E/WindowManager(17637): at android.view.Window$LocalWindowManager.addView(Window.java:424)

E/WindowManager(17637): at android.app.Dialog.show(Dialog.java:241)

E/WindowManager(17637): at com.dhna.example.DialogTestActivity.onCreate(DialogTestActivity.java:49)

E/WindowManager(17637): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)

E/WindowManager(17637): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)

E/WindowManager(17637): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)

E/WindowManager(17637): at android.app.ActivityThread.access$2300(ActivityThread.java:125)

E/WindowManager(17637): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)

E/WindowManager(17637): at android.os.Handler.dispatchMessage(Handler.java:99)

E/WindowManager(17637): at android.os.Looper.loop(Looper.java:123)

E/WindowManager(17637): at android.app.ActivityThread.main(ActivityThread.java:4627)

E/WindowManager(17637): at java.lang.reflect.Method.invokeNative(Native Method)

E/WindowManager(17637): at java.lang.reflect.Method.invoke(Method.java:521)

E/WindowManager(17637): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)

E/WindowManager(17637): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)

E/WindowManager(17637): at dalvik.system.NativeStart.main(Native Method)

D/DialogTestActivity(20064): to dismiss dialog. is dialog showing? true

D/AndroidRuntime(18361): Shutting down VM

W/dalvikvm(18361): threadid=1: thread exiting with uncaught exception (group=0x4001d800)


E/AndroidRuntime(18361): FATAL EXCEPTION: main

E/AndroidRuntime(18361): java.lang.IllegalArgumentException: View not attached to window manager

E/AndroidRuntime(18361): at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:355)

E/AndroidRuntime(18361): at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:200)

E/AndroidRuntime(18361): at android.view.Window$LocalWindowManager.removeView(Window.java:432)

E/AndroidRuntime(18361): at android.app.Dialog.dismissDialog(Dialog.java:278)

E/AndroidRuntime(18361): at android.app.Dialog.access$000(Dialog.java:71)

E/AndroidRuntime(18361): at android.app.Dialog$1.run(Dialog.java:111)

E/AndroidRuntime(18361): at android.os.Handler.handleCallback(Handler.java:587)

E/AndroidRuntime(18361): at android.os.Handler.dispatchMessage(Handler.java:92)

E/AndroidRuntime(18361): at android.os.Looper.loop(Looper.java:123)

E/AndroidRuntime(18361): at android.app.ActivityThread.main(ActivityThread.java:4627)

E/AndroidRuntime(18361): at java.lang.reflect.Method.invokeNative(Native Method)

E/AndroidRuntime(18361): at java.lang.reflect.Method.invoke(Method.java:521)

E/AndroidRuntime(18361): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)

E/AndroidRuntime(18361): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)

E/AndroidRuntime(18361): at dalvik.system.NativeStart.main(Native Method)



  로그를 보시면, 'Activity com.dhna.example.DialogTestActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@44f63488 that was originally added here'은 Activity.finish()를 호출하면서 출력된 로그 입니다.그런데 이 부분은 개발자에게 알려주는 정보로 오류 다이알로그를 보여주지 않고, 단순히 console에 로그만 출력합니다. 이 로그가 출력되는 이유는 아마도 다이알로그가 띄워진 상태에서 Activity.finish()를 하게 되면 출력되는 것으로 추측됩니다.


  그리고 다음 로그를 보시면 'java.lang.IllegalArgumentException: View not attached to window manager' 예외가 발생하는 것을 볼 수 있습니다. 실질적으로 이 부분에서 Exception을 발생하기 때문에 앱이 죽게됩니다. Activity.finish()를 하게 되면 View들을 정리하게 됩니다. 하지만 Dialog는 dismiss()를 하기 전까지 자신이 보여지고 있는 걸로 착각하고 있습니다. 그래서 이미 제거되어 있는 상태인데 Window에서 자신을 제거 해달라고 했기 때문에 이 예외가 발생하게 된 것 입니다. 


그래서 Dialog.isShowing()으로 이 예외를 피할 수는 없습니다. dismiss()는 내부적으로 자신이 보여지고 있는지 체크를 이미 하고 있기 때문이죠. 물론 Dialog.isShowing()이 의미 없는 코드는 아닙니다. 

Dialog.dismiss()를 호출하는 Thread가 Main쓰레드가 아니라면 실제 다이알로그를 종료하는 시간이 조금은 늦게 시작 할 수 있을 뿐입니다.


stackoverflow의 글을 보면, 해결책은 나와있습니다. finish()보다 dismiss()를 먼저 수행하면 됩니다. 만약 finish()보다 dismiss()를 먼저 호출하는 경우가 있다면, 코드를 변경해야겠죠.


저는 좀 더 간단한(?) 방법으로 Activity.onDestroy()에서 Dialog를 제거하는 방법을 생각해봤습니다. 위의 예제 코드에서 아래와 같이 코드를 추가하시면 오류가 발생하지 않는 것을 확인 할 수 있습니다.


public class MyActivity extends Activity {

    private final String TAG = "MyActivity";

    

    private final int MSG_DISMISS_AFTER_FINISH = 0;

    private final int MSG_ONLY_DISMISS = 1;


    private Handler mHandler = new Handler() {

        public void handleMessage(Message msg) {

            switch (msg.what) {

            case MSG_DISMISS_AFTER_FINISH:

                Log.d(TAG, "to finish activity.");

                finish();

                sendEmptyMessageDelayed( MSG_ONLY_DISMISS, 2000);

                break;

            case MSG_ONLY_DISMISS:

                if (mDialog != null ) {

                    Log.d(TAG, "to dismiss dialog. is dialog showing? "+mDialog.isShowing());

                    mDialog.dismiss();

                }

                break;

            }

        }

    };


    private AlertDialog mDialog;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        mDialog = new AlertDialog.Builder(this)

                .setTitle("Title")

                .setMessage("This is for Dialog Test")

                .create();

        mDialog.show();


        mHandler.sendEmptyMessageDelayed(MSG_DISMISS_AFTER_FINISH, 5000);

    }


    @Override

    protected void onDestroy() {

        Log.d(TAG, "called onDestroy");

        mDialog.dismiss();

        super.onDestroy();

    }

}


여기까지 읽은 신 분들 중에는 아마도 "너무나 당연한 이야기를 한다"라고 생각하신 분들도 계실 거라고 생각됩니다. 'java.lang.IllegalArgumentException: View not attached to window manager dialog dismiss' 라고 구글링을 해보시면, 생각보다 많은 분들이 이 오류를 직면한다는 것을 알 수 있습니다. 


해결책의 키워드는 '모든 Dialog는 Activity.onDestroy()에서 Dialog.dismiss() 한다.' 라는 것 입니다. 






반응형