java.lang.IllegalArgumentException: View not attached to window manager when dismissing dialog

本文探讨了在Android应用中遇到的View not attached to window manager异常问题,详细分析了异常产生的原因,并提供了有效的解决方案。建议将对话框关闭操作移至UI线程执行,同时推荐在Activity的onDestroy或onStop方法中关闭所有对话框。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原blog地址:http://blog.youkuaiyun.com/zirconsdu/article/details/8755533



Exception One,
java.lang.IllegalArgumentException: View not attached to window manager
at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:664)
at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:354)
at android.view.WindowManagerImpl$CompatModeWrapper.removeView(WindowManagerImpl.java:165)
at android.app.Dialog.dismissDialog(Dialog.java:319)
at android.app.Dialog.dismiss(Dialog.java:302)
at com.android.email.activity.setup.AccountSetupBasics$FindProviderTask.onPostExecute(AccountSetupBasics.java:914)
at com.android.email.activity.setup.AccountSetupBasics$FindProviderTask.onPostExecute(AccountSetupBasics.java:863)
at android.os.AsyncTask.finish(AsyncTask.java:631)
at android.os.AsyncTask.access$600(AsyncTask.java:177)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:644)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4780)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:808)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:575)
at dalvik.system.NativeStart.main(Native Method)

Exception Two,
STACK TRACE:
java.lang.IllegalArgumentException: View not attached to window manager
at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:355)
at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:200)
at android.view.Window$LocalWindowManager.removeView(Window.java:432)
at android.app.Dialog.dismissDialog(Dialog.java:280)
at android.app.Dialog.access$000(Dialog.java:73)
at android.app.Dialog$1.run(Dialog.java:109)
at android.app.Dialog.dismiss(Dialog.java:264)
at com.xxxxxxxx.suquashi.ActionDialog.access$001(ActionDialog.java:43)
at com.xxxxxxxx.suquashi.ActionDialog$2.onAnimationEnd(ActionDialog.java:299)
at android.view.animation.AnimationSet.getTransformation(AnimationSet.java:329)
at android.view.ViewGroup.drawChild(ViewGroup.java:1494)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1356)
at android.view.ViewGroup.drawChild(ViewGroup.java:1627)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1356)
at android.view.ViewGroup.drawChild(ViewGroup.java:1627)


[Bad Design]
The following code is a bad design to put dialog.dismiss() and activity.finish() in the asynctask.

[java]  view plain copy
  1. final AsyncTask<MyParams, Object, MyResult> task = new AsyncTask<MyParams, Object, MyResult>() {  
  2.   
  3.     @Override  
  4.     protected MyResult doInBackground(MyParams... params) {  
  5.         // Long operation goes here  
  6.     }  
  7.   
  8.     @Override  
  9.     protected void onPostExecute(MyResult result) {  
  10.         dialog.dismiss();  
  11.         onCompletion(result);  
  12.    
  13.  finish();  
  14.     }  
  15. };  


First every dialog or sth extends dialog would have a own window and décor view(itself), and except popup(dialog) there is a window and décor view. Second when the phone change orientation, the activity Thread would call HandleRelaunchActivity(this would call handledestroyactivity), the window manager would remove all the window and décor view and then create new window and décor, so the old décor has no use after this had been done.
In the code of action dialog there would be a animation make the super dismiss called later, but if the HandleRelaunchActivity happened, the actiondialog's décor would be useless, then the animation call back come, the super dismiss is called, it should not be in the mview[] in windowmanagerimpl. After that analyze, I think if this happened, we don't have very useful method to prevent, and it should be ok for a not in the array, but if lockview method throw a exception, I think we could just catch it. It won't affect much because the décor view(actiondialog) would be useless.

Change affects the way the ActionDialog behaves when it is dismissed. Most notable for an end user is that an ActionDialog that is closed when its Activity is being shut down will no longer animate.

Addition to the information below The reason for the Dialog being removed from WindowManager in the first place is typically due to a rotation of the screen, and if the ActionDialog is managed the Android system will take care of removing/dismissing it.

The problem seems to be because ActionDialog delays Dialog.dismiss() to play an animation. When the animation is finished ActionDialog calls Dialog.dismiss but at that time it has already been removed from the WindowManager so the WindowManager will throw an exception when Dialog requests to be removed from WindowManager.

This problem is thus related to ActionDialog and not HomeScreen which means that other applications using ActionDialog could also get this problem.

It could be wrong here, but I suspect that multiple phones 'in the wild' have a bug that causes them to switch orientation on applications that are marked as statically oriented. This happens quite a bit on my personal phone, and on many of the test phones our group uses (including droid, n1, g1, hero). Typically an app marked as statically oriented (perhaps vertically) will lay itself out for a second or two using a horizontal orientation, and then immediately switch back. End result is that even though you don't want your app to switch orientation, you have to be prepared that it may. I don't know under what exact conditions this behavior can be reproduced, I don't know if it is specific to a version of Android. All I know is that I have seen it happen plenty of times :(

I would recommend using the solution provided in the link you posted that suggests overriding the Activity onCreateDialog method and letting the Android OS manage the lifecycle of your Dialogs. It looks to me like even though you don't want your activity to switch orientations, it is switching orientation somewhere. You can try to track down a method that will always prevent orientation switching, but I am trying to tell you that I personally don't believe there is a foolproof way that works on all current Android phones in the market.

[Dialog dismiss Framework Code]

[java]  view plain copy
  1. 301    public void dismiss() {  
  2. 302        if (Thread.currentThread() != mUiThread) {  
  3. 303            mHandler.post(mDismissAction);  
  4. 304        } else {  
  5. 305            mHandler.removeCallbacks(mDismissAction);  
  6. 306            mDismissAction.run();  
  7. 307        }  
  8. 308    }  

For Exception One, In email, IllegalArgumentException rises in onPostExec when the AsyncTsk is finishing.
When dismissing the dialog, WindowManager can't find the window and view in its Views.   

[java]  view plain copy
  1. 300    public void dismiss() {  
  2. 301        if (Looper.myLooper() == mHandler.getLooper()) {  
  3. 302            dismissDialog();  
  4. 303        } else {  
  5. 304            mHandler.post(mDismissAction);  
  6. 305        }  
  7. 306    }  
  8. 307  
  9. 308    void dismissDialog() {  
  10. 309        if (mDecor == null || !mShowing) {  
  11. 310            return;  
  12. 311        }  
  13. 312  
  14. 313        if (mWindow.isDestroyed()) {  
  15. 314            Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");  
  16. 315            return;  
  17. 316        }  
  18. 317  
  19. 318        try {  
  20. 319            mWindowManager.removeView(mDecor);  
  21. 320        } finally {  
  22. 321            if (mActionMode != null) {  
  23. 322                mActionMode.finish();  
  24. 323            }  
  25. 324            mDecor = null;  
  26. 325            mWindow.closeAllPanels();  
  27. 326            onStop();  
  28. 327            mShowing = false;  
  29. 328  
  30. 329            sendDismissMessage();  
  31. 330        }  
  32. 331    }  
  33.   
  34. 158        @Override  
  35. 159        public void removeView(View view) {  
  36. 160   if(null == view)  
  37. 161   {  
  38. 162             Log.d("WindowManager""uuNote:removeView:view=null###");  
  39. 163    throw new NullPointerException("view parameter is NULL into findViewLocked()");  
  40. 164   }  
  41. 165            mWindowManager.removeView(view);  
  42. 166        }  
  43.   
  44. 352    public void removeView(View view) {  
  45. 353        synchronized (this) {  
  46. 354            int index = findViewLocked(view, true);  
  47. 355            View curView = removeViewLocked(index);  
  48. 356            if (curView == view) {  
  49. 357                return;  
  50. 358            }  
  51. 359  
  52. 360            throw new IllegalStateException("Calling with view " + view  
  53. 361                    + " but the ViewAncestor is attached to " + curView);  
  54. 362        }  
  55. 363    }  
  56.   
  57. 649    private int findViewLocked(View view, boolean required) {  
  58. 650        synchronized (this) {  
  59. 651            final int count = mViews != null ? mViews.length : 0;  
  60. 652            for (int i=0; i<count; i++) {  
  61. 653                if (mViews[i] == view) {  
  62. 654                    return i;  
  63. 655                }  
  64. 656            }  
  65. 657            if(null==view)  
  66. 658            {  
  67. 659             Log.d("WindowManager""findViewLocked:view=null###");  
  68. 660             required = false;  
  69. 661  throw new NullPointerException("view parameter is NULL into findViewLocked()");  
  70. 662            }  
  71. 663            if (required) {  
  72. 664                throw new IllegalArgumentException(  
  73. 665                        "View not attached to window manager");  
  74. 666            }  
  75. 667            return -1;  
  76. 668        }  
  77. 669    }  
  78. 670}  


[Reference Solution]
Move the dialog.dismiss() and activity.finish to UiThread.

[java]  view plain copy
  1. new Thread() {  2946   
  2.  @Override  2947   
  3.  public void run() {  2948   
  4.   Uri uri = ContentUris.withAppendedId(SemcThreads.CONTENT_URI,  2949   
  5.      threadId);  2950   
  6.   getContentResolver().delete(uri, nullnull);  2951   
  7.   runOnUiThread(new Runnable() {  2952   
  8.    public void run() {  2953   
  9.     pd.dismiss();  2954   
  10.           2955   
  11.     // prevent saving of draft  2956   
  12.     mComposer = null;  2957   
  13.     mConversation = null;  2958   
  14.           2959   
  15.     Notifications.updateAll(ConversationActivity.this);  2960   
  16.     ConversationCache.clear();  2961   
  17.     finish();  2962   
  18.    }  2963   
  19.   });  2964   
  20.  }  2965   
  21. }.start();    

if you use many dialogs, should close all dialog in activity's overridenonDestroy() or onStop().
then you may be able to reduce the frequency 'java.lang.IllegalArgumentException: View not attached to window manager' exception occurs.

[java]  view plain copy
  1. @Override  
  2. protected void onDestroy() {  
  3.     Log.d(TAG, "called onDestroy");  
  4.     mDialog.dismiss();  
  5.     super.onDestroy();  
  6. }  


but little exceed...
to make it more clear, you prevent to show any dialog after onDestroy called.
i don't use as below. but it's clear.

[java]  view plain copy
  1. private boolean mIsDestroyed = false;  
  2.   
  3. private void showDialog() {  
  4.     closeDialog();  
  5.   
  6.     if (mIsDestroyed) {  
  7.         Log.d(TAG, "called onDestroy() already.");  
  8.         return;  
  9.     }  
  10.   
  11.     mDialog = new AlertDialog(this)  
  12.         .setTitle("title")  
  13.         .setMessage("This is DialogTest")  
  14.         .setNegativeButton("Cancel"new DialogInterface.OnClickListener() {  
  15.             public void onClick(DialogInterface dialog, int which) {  
  16.                 dialog.dismiss();  
  17.             }  
  18.         })  
  19.         .create();  
  20.     mDialog.show();  
  21. }  
  22.   
  23. private void closeDialog() {  
  24.     if (mDialog != null && mDialog.isShowing()) {  
  25.         mDialog.dismiss();  
  26.     }  
  27. }  
  28.   
  29. @Override  
  30. protected void onDestroy() {  
  31.     Log.d(TAG, "called onDestroy");  
  32.     mIsDestroyed = true;  
  33.     closeDialog();  
  34.     super.onDestroy();  
  35. }  

And at last, is it better to use dialog.cancel() rather than dialog.dismiss()?


<think>我们讨论的问题是在Android开发中,当尝试关闭Dialog时出现的`IllegalArgumentException:Viewnotattachedtowindowmanager`异常。这个异常通常发生在Dialog尝试被关闭(dismiss)时,但是Dialog所依附的窗口已经被移除或者Activity已经被销毁的情况。根据用户提供的引用,我们可以总结出以下几个关键点:1.异常发生的原因:当调用`dismiss()`方法时,如果DialogView已经与窗口管理器分离(例如,Activity已经销毁或者窗口已经被移除),就会抛出这个异常。2.常见的触发场景:-在Activity正在被销毁(如旋转屏幕、按返回键)时,尝试关闭Dialog。-在后台线程中关闭Dialog,而在这期间Activity已经被销毁。3.解决方案的核心思想:在关闭Dialog之前,需要检查Activity是否还处于活动状态(没有被销毁),以及Dialog是否还处于显示状态。根据引用[5]中的代码示例,我们可以采取如下步骤来避免这个异常:-在调用`dismiss()`之前,检查Dialog是否不为null,是否正在显示,并且其关联的Activity没有被销毁。但是需要注意的是,从Android官方文档来看,Dialog的dismiss方法本身是线程安全的,可以在任何线程中调用,但是我们必须确保在调用时,Dialog仍然依附于一个未被销毁的Activity。因此,解决方案可以归纳为:1.在关闭Dialog前,检查Activity是否已经销毁或者正在销毁中。2.确保在UI线程中执行关闭操作(尽管dismiss方法内部会切换到UI线程,但为了代码清晰,建议在UI线程中操作)。以下是解决方案的具体步骤:###解决方案####1.安全关闭Dialog的工具方法我们可以创建一个工具方法来安全地关闭Dialog,避免重复代码。方法如下:```javapublicstaticvoidsafeDismissDialog(Dialogdialog){if(dialog!=null&&dialog.isShowing()){//获取Dialog所依附的ContextContextcontext=dialog.getContext();if(contextinstanceofActivity){Activityactivity=(Activity)context;//检查Activity是否正在销毁或已经销毁if(activity.isFinishing()||activity.isDestroyed()){return;}}//在UI线程中关闭Dialogif(Looper.myLooper()==Looper.getMainLooper()){dialog.dismiss();}else{//如果不在主线程,则切换到主线程Handlerhandler=newHandler(Looper.getMainLooper());handler.post(dialog::dismiss);}}}```####2.在Activity中使用时的注意事项在Activity中,当需要关闭Dialog时,不要直接调用Dialog的dismiss()方法,而是调用上面的安全方法。另外,在Activity的生命周期方法(如onDestroy)中,需要确保安全地关闭Dialog。例如,在Activity的onDestroy方法中:```java@OverrideprotectedvoidonDestroy(){super.onDestroy();safeDismissDialog(myDialog);}```####3.在后台线程中关闭Dialog时的处理如果是在后台线程中需要关闭Dialog,也应该使用上述安全方法,它会自动切换到主线程并安全关闭。####4.使用DialogFragment替代DialogAndroid官方推荐使用DialogFragment来管理对话框,因为DialogFragment会跟随Fragment的生命周期自动管理Dialog的关闭,从而避免此类问题。使用DialogFragment的示例:```javapublicclassMyDialogFragmentextendsDialogFragment{@NonNull@OverridepublicDialogonCreateDialog(BundlesavedInstanceState){//创建并返回一个DialogAlertDialog.Builderbuilder=newAlertDialog.Builder(getActivity());builder.setTitle("提示");builder.setMessage("这是一个对话框");builder.setPositiveButton("确定",null);returnbuilder.create();}}//显示对话框MyDialogFragmentdialogFragment=newMyDialogFragment();dialogFragment.show(getSupportFragmentManager(),"dialog_tag");//关闭对话框:可以通过DialogFragment的dismiss方法,它会自动处理生命周期dialogFragment.dismiss();```使用DialogFragment的好处是,当Activity被销毁时,DialogFragment会与Activity的生命周期同步,避免了在Activity销毁后尝试关闭Dialog的问题。###最佳实践1.**尽量使用DialogFragment**:DialogFragment可以更好地处理生命周期,避免很多问题。2.**在关闭Dialog前进行状态检查**:如果直接使用Dialog,务必检查Activity和Dialog的状态。3.**确保在主线程操作UI**:尽管dismiss方法可以在非UI线程调用,但在UI线程中调用可以避免一些并发问题。###相关问题1.如何避免在Activity销毁时出现WindowManager$BadTokenException异常?2.Android中,如何正确管理和显示Dialog以避免内存泄漏?3.DialogFragment相比普通的Dialog有什么优势?4.如何在后台线程中更新UI组件(包括关闭Dialog)?###引用说明根据用户提供的引用,我们了解到这个异常是因为视图没有附加到窗口管理器造成的[^1][^2][^3][^4],而解决方案需要在关闭对话框时检查Activity是否已经销毁[^5]。</think> 针对您遇到的`java.lang.IllegalArgumentException: View not attached to window manager`异常,这是Android开发中关闭Dialog时的常见问题,尤其在Activity/Fragment销毁时发生。以下是完整的解决方案: ### 问题原因分析 1. **时机不当**:Activity已销毁时仍尝试关闭Dialog(如后台线程延后关闭)[^1][^2] 2. **生命周期异步**:屏幕旋转等配置变更导致Context失效,但线程仍执行`dismiss()`[^3][^4] 3. **窗口管理失效**:DecorView已被WindowManager移除[^5] 4. **线程同步问题**:非UI线程操作UI组件(如线程休眠后关闭Dialog)[^4] ```mermaid graph LR A[尝试关闭Dialog] --> B{Activity可用?} B -->|已销毁| C[抛异常] B -->|未销毁| D{在主线程?} D -->|是| E[安全关闭] D -->|否| F[切换到主线程] C --> G[异常处理] ``` ### 完整解决方案 #### 1. 基础安全关闭方法(推荐) ```kotlin fun safeDismissDialog(dialog: Dialog?) { dialog?.let { // 检查Activity状态 val context = it.context if (context is Activity) { if (!context.isFinishing && !context.isDestroyed) { // 确保在主线程执行 runOnUiThreadIfNeeded(context) { if (it.isShowing) it.dismiss() } } } else { // 非Activity Context的情况 runOnUiThreadIfNeeded(context) { if (it.isShowing) it.dismiss() } } } } // 主线程安全执行工具 private fun runOnUiThreadIfNeeded(context: Context, action: () -> Unit) { if (Looper.myLooper() == Looper.getMainLooper()) { action() } else { Handler(Looper.getMainLooper()).post(action) } } ``` #### 2. 在Activity销毁时的处理 ```kotlin override fun onDestroy() { safeDismissDialog(yourDialog) // 退出时安全关闭 yourDialog = null // 释放引用避免内存泄漏 super.onDestroy() } ``` #### 3. 使用DialogFragment的推荐方案 Google官方推荐使用`DialogFragment`管理对话框生命周期: ```kotlin class SafeDialogFragment : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return AlertDialog.Builder(requireContext()) .setTitle("安全对话框") .setPositiveButton("确定", null) .create() } // 系统自动处理生命周期同步 } // 打开对话框 SafeDialogFragment().show(parentFragmentManager, "dialog_tag") ``` #### 4. 处理后台线程的关闭请求 当线程需关闭对话框时,增加生命周期检查: ```kotlin private class BackgroundTask(private val activityRef: WeakReference<Activity>) : Runnable { override fun run() { // 模拟耗时操作 Thread.sleep(5000) activityRef.get()?.runOnUiThread { // 双重检查 if (!activityRef.get().isFinishing && !activityRef.get().isDestroyed) { safeDismissDialog(yourDialog) } } } } ``` #### 5. 针对配置变更(如屏幕旋转) 在Manifest中配置`configChanges`: ```xml <activity android:name=".MainActivity" android:configChanges="orientation|screenSize|keyboardHidden"> </activity> ``` ### 最佳实践原则 1. **生命周期绑定**:Dialog引用应与Activity/Fragment生命周期同步 2. **弱引用机制**:后台线程持有Activity需用`WeakReference` 3. **三阶检查**: ```kotlin if (dialog != null && // 对象存在 dialog!!.isShowing && // 正在显示 !context.isFinishing && // Activity未结束 !context.isDestroyed) // Activity未销毁(API 17+) ``` 4. **统一管理**:通过BaseActivity统一处理Dialog[^5] ### 常见错误案例修正 ```kotlin // 错误示例:线程中直接关闭(引用[4]问题) private class DialogThread(private val dialog: Dialog) : Thread() { override fun run() { SystemClock.sleep(5000) // 错误! 可能触发异常 dialog.dismiss() } } // 修正后 private class SafeDialogThread( private val activityRef: WeakReference<Activity>, private val dialogRef: WeakReference<Dialog> ) : Thread() { override fun run() { SystemClock.sleep(5000) activityRef.get()?.let { it.runOnUiThread { safeDismissDialog(dialogRef.get()) } } } } ``` ### 高级防护方案 ```kotlin // 扩展函数实现全局安全关闭 fun Dialog.safeDismiss() { val context = this.context if (context is Activity) { if (context.isFinishing || context.isDestroyed) return } try { if (isShowing) dismiss() } catch (e: IllegalArgumentException) { Log.w("Dialog", "安全捕获异常: ${e.message}") } catch (e: WindowManager.BadTokenException) { Log.w("Dialog", "窗口令牌无效: ${e.message}") } } ``` ### 相关问题 1. 如何避免Activity销毁时的`WindowManager$BadTokenException`? **提示**:使用弱引用检查Activity状态[^5] 2. DialogFragment与普通Dialog在生命周期处理上有何区别? **提示**:FragmentManager自动同步生命周期 3. 如何在ViewModel中安全控制Dialog显示? **提示**:结合LiveData和LifecycleObserver 4. 配置变更时保持Dialog显示的官方推荐方案是什么? **提示**:使用`setRetainInstance(true)`(已废弃,建议用ViewModel保存状态) > 通过上述方案,可彻底解决`View not attached to window manager`异常。关键点在于:**在正确时机使用主线程关闭 + 三重状态检查(Dialog状态/Activity状态/Context类型)** [^1][^2][^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值