关于dialog引起的java.lang.IllegalArgumentException:View=com.android.internal.policy.impl.Ph

本文探讨了在Android应用中使用ProgressDialog与AsyncTask时可能遇到的问题。特别是在TabView页面间快速切换时,ProgressDialog可能出现未正确附加到WindowManager的情况。文章提供了一种通过检查Activity状态来避免此问题的有效解决方案。

转自:http://blog.youkuaiyun.com/yuxiaohui78/article/details/38076447


E/AndroidRuntime(9916): Java.lang.IllegalArgumentException: View=com.Android.internal.policy.impl.PhoneWindow$DecorView{43805410 V.E..... R.....ID 0,0-1026,288} not attached to window manager

E/AndroidRuntime(9916):  at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:370)
E/AndroidRuntime(9916):  at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:299)
E/AndroidRuntime(9916):  at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:84)
E/AndroidRuntime(9916):  at android.app.Dialog.dismissDialog(Dialog.java:329)

E/AndroidRuntime(9916): at android.app.Dialog.dismiss(Dialog.java:312)

在将progressDialog 用在ASyncTask中的时候,有时候会遇到上面的错误。

我们先看一下,最常用的AsyncTask中使用ProgressDialog的方法。下面是最常用的使用方法。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class HttpRequestTask extends AsyncTask <String, Void, String> {  
  2.   
  3.     Context ctx = null;  
  4.     private ProgressDialog dialog = null;  
  5.   
  6.     public interface HttpRequestTaskListener {  
  7.         void ServerResponse (String jsonStr);  
  8.     }  
  9.   
  10.     public HttpRequestTask (final Context c){  
  11.           
  12.         ctx = c;  
  13.         dialog = new ProgressDialog(c);  
  14.       
  15.     }  
  16.   
  17.     public void addListener (HttpRequestTaskListener l){  
  18.         listeners.add(0, l);  
  19.     }  
  20.   
  21.         protected void onPreExecute() {  
  22.             dialog.setMessage("Downloading data from the Server...");  
  23.             dialog.show();  
  24.     }  
  25.       
  26.     @Override  
  27.     protected String doInBackground(String... params) {  
  28.         String url = params[0];  
  29.   
  30.         return HttpRequest ( url, .......);  
  31.     }  
  32.   
  33.     @Override  
  34.     protected void onPostExecute(String result) {  
  35.   
  36.         if (dialog.isShowing()) {  
  37.             dialog.dismiss();  
  38.         }  
  39.       
  40.             if (listeners.size() > 0){  
  41.                 listeners.get(0).ServerResponse ( result );  
  42.             }  
  43.         }  
  44.     }  

一般情况下,上面的方法不会出现任何错误。 任务结束后,ProgressDialog会正常消失。

但是在某些情况下,上面的使用方法就非常不安全。

在项目开发中会遇到在TabView的各个tab page进行切换。同时每个tab page中都会调用

这个异步任务HttpRequestTask去请求网络数据。这时会出现一个问题。
当用户在各个tab page间快速切换的时候,ProgressDialog 使用的context就很不安全。
会遇到调用 dialog.dismiss 和 dialog.show(); 的时候无法attach到Window Manager.

原因是在切换的时候,dialog还没有完成所有的调用,所对应的context已经被destroy或正在destroy。

这时就会导致上面的错误。

尝试解决这个问题。最初想使用ApplicationContext,(context.getApplicationContext()),但是,这个context无法用于ProgressDialog和Toast。会直接导致crash。

目前的解决方法是,先检查context对应的Activity的状态,如果不可用就停止dialog操作:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class HttpRequestTask extends AsyncTask <String, Void, String> {  
  2.   
  3.     Context ctx = null;  
  4.     private ProgressDialog dialog = null;  
  5.   
  6.     public interface HttpRequestTaskListener {  
  7.         void ServerResponse (String jsonStr);  
  8.     }  
  9.   
  10.     public HttpRequestTask (final Context c){  
  11.           
  12.         ctx = c;  
  13.         dialog = new ProgressDialog(c);  
  14.       
  15.     }  
  16.   
  17.     public void addListener (HttpRequestTaskListener l){  
  18.         listeners.add(0, l);  
  19.     }  
  20.   
  21.   protected void onPreExecute() {  
  22.         if (isValidContext(ctx)){  
  23.             dialog.setMessage("Downloading data from the Server...");  
  24.             dialog.show();  
  25.         }  
  26.     }  
  27.       
  28.     @Override  
  29.     protected String doInBackground(String... params) {  
  30.         String url = params[0];  
  31.   
  32.         return HttpRequest ( url, .......);  
  33.     }  
  34.   
  35.     @Override  
  36.     protected void onPostExecute(String result) {  
  37.   
  38.         if (isValidContext(ctx) && dialog.isShowing()) {  
  39.             dialog.dismiss();  
  40.         }  
  41.       
  42.             if (listeners.size() > 0){  
  43.                 listeners.get(0).ServerResponse ( result );  
  44.             }  
  45.         }  
  46.     }  
  47.   
  48.     private boolean isValidContext (Context c){  
  49.           
  50.         Activity a = (Activity)c;  
  51.           
  52.         if (a.isDestroyed() || a.isFinishing()){  
  53.             Log.i("YXH""Activity is invalid." + " isDestoryed-->" + a.isDestroyed() + " isFinishing-->" + a.isFinishing());  
  54.             return false;  
  55.         }else{  
  56.             return true;  
  57.         }  
  58.     }  
<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]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值