java.lang.IllegalArgumentException:视图未附加到窗口管理器


148

我有一个活动,它将启动AsyncTask并在操作期间显示进度对话框。声明该活动不能通过旋转或键盘滑动来重新创建。

    <activity android:name=".MyActivity" 
              android:label="@string/app_name"
              android:configChanges="keyboardHidden|orientation"
              >
        <intent-filter>
        </intent-filter>
    </activity>

任务完成后,我关闭对话框,但是在某些电话(框架:1.5、1.6)上会引发此类错误:

java.lang.IllegalArgumentException: View not attached to window manager
    at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:356)
    at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:201)
    at android.view.Window$LocalWindowManager.removeView(Window.java:400)
    at android.app.Dialog.dismissDialog(Dialog.java:268)
    at android.app.Dialog.access$000(Dialog.java:69)
    at android.app.Dialog$1.run(Dialog.java:103)
    at android.app.Dialog.dismiss(Dialog.java:252)
    at xxx.onPostExecute(xxx$1.java:xxx)

我的代码是:

final Dialog dialog = new AlertDialog.Builder(context)
    .setTitle("Processing...")
    .setCancelable(true)
    .create();

final AsyncTask<MyParams, Object, MyResult> task = new AsyncTask<MyParams, Object, MyResult>() {

    @Override
    protected MyResult doInBackground(MyParams... params) {
        // Long operation goes here
    }

    @Override
    protected void onPostExecute(MyResult result) {
        dialog.dismiss();
        onCompletion(result);
    }
};

task.execute(...);

dialog.setOnCancelListener(new OnCancelListener() {
    @Override
    public void onCancel(DialogInterface arg0) {
        task.cancel(false);
    }
});

dialog.show();

根据我已阅读(http://bend-ing.blogspot.com/2008/11/properly-handle-progress-dialog-in.html)并在Android资料中看到的内容,看来这是唯一可能的解决方法例外是活动被破坏时。但是正如我已经提到的,我禁止为基本活动进行活动娱乐。

因此,任何建议都非常感谢。


1
这个问题有很多答案,如果有任何帮助您,请选择它作为正确的答案。
2015年

Answers:


227

当我关闭对话框并完成onPostExecute方法的活动时,有时也会收到此错误。我猜有时候对话结束之前,活动已经完成。

简单但有效的解决方案对我有用

@Override
protected void onPostExecute(MyResult result) {
    try {
        if ((this.mDialog != null) && this.mDialog.isShowing()) {
            this.mDialog.dismiss();
        }
    } catch (final IllegalArgumentException e) {
        // Handle or log or ignore
    } catch (final Exception e) {
        // Handle or log or ignore
    } finally {
        this.mDialog = null;
    }  
}

44
简单的解决方案?是。有效?也许在这种情况下。我会推荐吗?没有!不要吞下所有这样的异常!我什至不会捕获IllegalArgumentException,但是正在寻找另一种解决方案。
西蒙·佛斯伯格

6
因为通常空的try-catching是个坏主意……尽管有时可能是正确的选择。
Thomas

3
@Damjan根据您的回复,建议捕获类型为Exception。好吧,这是Google的不良做法。您可以在此处阅读有关内容:不要捕获通用异常
亚尼夫2013年

17
我相信这是一个有效的解决方案。在一般情况下,我们不应该这样做,但是由于Android框架无法为我们提供任何简便的检查方法,因此我们必须使用不寻常的方式。另外,如果对话框的isShowing()调用按预期工作,则不需要这种hack。
SXC

1
快速修复,直到发现更好的地方
Rohit Tigga 2014年

13

这是我的“防弹”解决方案,该解决方案汇总了我在该主题上发现的所有好的答案(感谢@Damjan和@Kachi)。在此,只有在所有其他检测方式均未成功的情况下,该异常才会被吞没。就我而言,我需要自动关闭对话框,这是防止应用崩溃的唯一方法。希望对您有所帮助!如果您有任何意见或更好的解决方案,请投票并发表评论。谢谢!

public void dismissWithCheck(Dialog dialog) {
        if (dialog != null) {
            if (dialog.isShowing()) {

                //get the Context object that was used to great the dialog
                Context context = ((ContextWrapper) dialog.getContext()).getBaseContext();

                // if the Context used here was an activity AND it hasn't been finished or destroyed
                // then dismiss it
                if (context instanceof Activity) {

                    // Api >=17
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                        if (!((Activity) context).isFinishing() && !((Activity) context).isDestroyed()) {
                            dismissWithTryCatch(dialog);
                        }
                    } else {

                        // Api < 17. Unfortunately cannot check for isDestroyed()
                        if (!((Activity) context).isFinishing()) {
                            dismissWithTryCatch(dialog);
                        }
                    }
                } else
                    // if the Context used wasn't an Activity, then dismiss it too
                    dismissWithTryCatch(dialog);
            }
            dialog = null;
        }
    }

    public void dismissWithTryCatch(Dialog dialog) {
        try {
            dialog.dismiss();
        } catch (final IllegalArgumentException e) {
            // Do nothing.
        } catch (final Exception e) {
            // Do nothing.
        } finally {
            dialog = null;
        }
    }

1
很好的解决方案!设置dialog = null没有任何效果。并且StatusEventDialog应该只读Dialog
hgoebl

1
StatusEventDialog应该更改为对话框。
Sreekanth Karumanaghat

这个答案应该是被接受的,处理得很好的答案
blueware '17

我了解您希望“正确”,并且仅在isDestroyed()无法使用时才使用try / catch ,但出于实际目的,总是使用try / catch会不会一样?
zundi '17

11

我可能有解决方法。

也有同样的问题,我在哪里装载大量物品(通过文件系统)为ListView通过AsyncTask。有onPreExecute()点火了ProgressDialog,然后这两个onPostExecute()onCancelled()(当任务是通过明确取消所谓AsyncTask.cancel()通过关闭它).cancel()

当我使用的onCancelled()方法杀死对话框时,出现了相同的“ java.lang.IllegalArgumentException:视图未附加到窗口管理器”错误AsyncTask(我在出色的Shelves应用程序中看到了这一点)。

解决方法是在中创建一个AsyncTask包含以下内容的公共字段ProgressDialog

public ProgressDialog mDialog;

然后,在onDestroy()取消我的时AsyncTask,我还可以通过以下方法终止相关的对话框:

AsyncTask.mDialog.cancel();

调用AsyncTask.cancel()DOES触发onCancelled()AsyncTask,但由于某些原因通过该方法被调用的时候,查看已经被销毁,因此撤销对话框失败。


我发现@Task提到的UserTask实现非常出色。源代码在这里: code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/…–
Evi Song

:虽然使用的情况下可以在同一个项目中找到 code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/...
Evi的宋

9

这是解决此问题的正确解决方案:

public void hideProgress() {
    if(mProgressDialog != null) {
        if(mProgressDialog.isShowing()) { //check if dialog is showing.

            //get the Context object that was used to great the dialog
            Context context = ((ContextWrapper)mProgressDialog.getContext()).getBaseContext();

            //if the Context used here was an activity AND it hasn't been finished or destroyed
            //then dismiss it
            if(context instanceof Activity) { 
                if(!((Activity)context).isFinishing() && !((Activity)context).isDestroyed()) 
                    mProgressDialog.dismiss();
            } else //if the Context used wasnt an Activity, then dismiss it too
                mProgressDialog.dismiss();
        }
        mProgressDialog = null;
    }
}

此解决方案不是盲目地捕获所有异常,而是解决了问题的根源:在用于初始化对话框的活动已经完成时,尝试取消对话框。可以在运行KitKat的Nexus 4上运行,但可以在所有版本的Android上运行。


3
isDestroyed需要API 17+
Androiderson

为什么需要将mProgressDialog设置为null?这与内存泄漏有关吗?你能解释一下吗?
Pawan

@Pawan,这是我的实现细节。这不是必需的,它只是此类中函数的工作方式。隐藏进度对话框后,将其设置为null。当用户想要显示另一个进度对话框时,将实例化一个新实例。
卡奇

绝对!((Activity)context).isFinishing()是必需的,谢谢!:)
Daniel Krzyczkowski

5

我同意“ Damjan”的观点。
如果使用许多对话框,则应关闭onDestroy()或onStop()中的所有对话框。
那么您就可以降低发生“ java.lang.IllegalArgumentException:视图未附加到窗口管理器”异常的频率。

@Override
protected void onDestroy() {
    Log.d(TAG, "called onDestroy");
    mDialog.dismiss();
    super.onDestroy();
}



但几乎没有...
为了使它更清晰,您可以防止在调用onDestroy之后显示任何对话框。
我不使用如下。但很明显

private boolean mIsDestroyed = false;

private void showDialog() {
    closeDialog();

    if (mIsDestroyed) {
        Log.d(TAG, "called onDestroy() already.");
        return;
    }

    mDialog = new AlertDialog(this)
        .setTitle("title")
        .setMessage("This is DialogTest")
        .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        })
        .create();
    mDialog.show();
}

private void closeDialog() {
    if (mDialog != null) {
        mDialog.dismiss();
    }
}

@Override
protected void onDestroy() {
    Log.d(TAG, "called onDestroy");
    mIsDestroyed = true;
    closeDialog();
    super.onDestroy();
}


祝好运!


我总是喜欢避免空的捕获块。值得一试,但是由于此错误很难产生-只有时间才能证明它是否确实有效。不管怎么说,还是要谢谢你。
Dror Fichman

什么是空捕获块?我不使用try / catch。超出了mIsDestroyed变量的范围。但是,如果您编写该对话框在其他线程中工作后显示的代码,则可能需要此变量。当其他线程正在工作时,如果活动完成,则可以查看此异常。
Hogun 2013年

我有相同的概率,当我添加@Override public void onPause(){if(dialog!= null)dialog.dismiss();时,super.onPause(); }到现在为止,我还没有这个错误...所以我认为它和您的回答一样,并且非常有用
Chris Sim 2014年

@ChrisSim你好!onPuase()和onDestroy()是不同的。当Activity为onPuase时,对话框关闭。当您执行应用程序时,不会显示对话框。你想要吗?
Hogun

@Hogun是的,当然,我的意思是相同的,我要在暂停而不是销毁时关闭对话框,因为我需要在暂停而不是销毁中插入它。其次,仅在不为null时关闭它。感谢您为其他人解释。
克里斯·辛

4

用这个。

if(_dialog!=null && _dialog.isShowing())
_dialog.dismiss();

2
这几乎与@Damjan提出的解决方案相同。
尤里(Yury)2012年

28
这还不够,此检查仍会发生IllegalArgumentException。
墨菲2012年

我做了相同的解决方案,但仍然不知道它是否有效。唯一的区别是,我嵌套两个if以确保如果为null则不会评估第二部分.isShowing()。
尼克,

2
这还不够。
trante

1
@Nick:在if这种情况下,无需嵌套多个',Java的&&运算符具有惰性求值(也称为短路),这意味着如果第一个操作数求值到第二个操作数就不求值false(这意味着将&&永远是false,无论如何,因此“偷懒”的评价)。同样,||如果第一个操作数求值为,则不会计算第二个操作数true。注意:&|运算符没有这种行为,因此总是对两个操作数求值。
马提亚斯(Matthias)2015年

3

我有同样的问题,可以通过以下方法解决:

@Override
protected void onPostExecute(MyResult result) {
    try {
        if ((this.mDialog != null) && this.mDialog.isShowing()) {
            this.mDialog.dismiss();
        }
    } catch (final IllegalArgumentException e) {
        // Handle or log or ignore
    } catch (final Exception e) {
        // Handle or log or ignore
    } finally {
        this.mDialog = null;
    }  
}

2

我认为您的代码正确,与建议的其他答案不同。onPostExecute将在UI线程上运行。这就是AsyncTask的全部要点-您不必担心调用runOnUiThread或处理处理程序。此外,根据文档,可以从任何线程安全地调用dismiss()(不确定它们是否将此异常)。

在不再显示活动之后调用dialog.dismiss()可能是一个计时问题?

如果您注释掉setOnCancelListener,然后在后台任务运行时退出活动,那么测试会怎么样呢?然后,您的onPostExecute将尝试关闭一个已经关闭的对话框。如果应用程序崩溃,则可以在关闭对话框之前检查对话框是否打开。

我遇到了完全相同的问题,因此我将在代码中进行尝试。


我还研究了dimiss()代码,实际上,可以从任何线程安全地调用它。顺便说一句,我在测试上遇到了问题,因为这个问题发生在用户的手机上,所以我从来没有自己一个人复制过:-(所以试图通过分析代码来找出……根据时间。我正在思考,但是无法想象在Dialog之前如何关闭Activity的情况,如果按BACK,则它将首先取消Dialog,并且清单文件禁止该活动自动重新创建,但也许它仍然可以通过一些方式重新创建?我知道您是否发现了某些东西!
alex2k8

2

亚历克斯

我在这里可能是错的,但我怀疑“狂野”的多部手机存在一个错误,导致它们在标记为静态的应用程序上切换方向。这在我的个人电话以及我们小组使用的许多测试电话(包括droid,n1,g1,hero)上都发生了很多。通常,标记为静态(也许是垂直)的应用会使用水平方向放置一两秒钟,然后立即切换回去。最终结果是,即使您不希望您的应用切换方向,也必须做好准备。我不知道在什么确切条件下可以重现此行为,也不知道它是否特定于Android版本。我所知道的是,我已经看到它发生很多次了:(

我建议使用提供的解决方案 发布的链接中,建议覆盖Activity onCreateDialog方法并让Android OS管理Dialogs的生命周期。在我看来,即使您不希望您的活动改变方向,也要在某处切换方向。您可以尝试找到一种始终可以防止方向转换的方法,但我想告诉您的是,我个人并不认为有一种万无一失的方法可以在市场上所有当前的Android手机上使用。


1
您可以防止设备切换方向,但是还有许多其他配置更改会破坏/重新创建您的Activity-一种常见的更改是将键盘滑入或滑出。
MaximumGoat 2012年

2

在大多数情况下,对我有用的是验证活动是否未完成。

if (!mActivity.isFinishing()) {
    dialog.dismiss();
}

2

声明该活动不能通过旋转或键盘滑动来重新创建。

刚遇到同样的问题。修复了API级别13或更高版本。
从Android文档:

注意:如果您的应用程序以API级别13或更高级别(由minSdkVersion和targetSdkVersion属性声明)为目标,则还应声明“ screenSize”配置,因为当设备在纵向和横向之间切换时,它也会更改。

所以我将清单更改为:

<activity
        android:name="MyActivity"
        android:configChanges="orientation|screenSize"
        android:label="MyActivityName" >
</activity>

现在工作正常。当我旋转手机时,不会重新创建活动,进度对话框和视图保持不变。我没有错。


这不是解决方案。例如,我有一个来自admob的和Adview活动,它应该针对大小更改重新创建该活动。
batmaci

2

首先,在您试图关闭对话框的任何地方进行错误处理。

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

如果不能解决问题,请在活动的onStop()方法中将其关闭。

 @Override
    protected void onStop() {
        super.onStop();
        if ((progressDialog != null) && progressDialog.isShowing()) {
            progressDialog.dismiss();
            progressDialog = null;
        }
    }

1

使用按钮从服务器同步列表时遇到相同的问题:1)单击按钮2)从服务器下载列表时出现进度对话框3)将设备转到另一个方向4)java.lang .IllegalArgumentException:在progress.dismiss()期间,视图未附加到AsyncTask的postExecute()上的窗口管理器。

当我尝试修复时,我发现即使没有出现问题,我的列表也没有显示所有项目。

我认为我想要的是在活动开始之前销毁AsyncTask(并关闭对话框),因此我将asynctask对象设置为属性,并覆盖了onDestroy()方法。

如果asynctask花费大量时间,用户可能会觉得设备很慢,但是我认为这是他在进度对话框显示时尝试更改设备方向的代价。即使花费一些时间,应用程序也不会崩溃。

private AsyncTask<Boolean, Void, Boolean> atask;

@Override
protected void onDestroy() {
    if (atask!=null)
        try {
            atask.get();
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    super.onDestroy();
}

1
@Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            if (progressDialog != null && progressDialog.isShowing()) {
                Log.i(TAG, "onPostexucte");
                progressDialog.dismiss();
}
}

3
虽然此代码段可以回答问题,但提供了一些有关如何解决问题的解释,这将有助于未来的站点访问者理解您的答案
RobV 2014年

0

下面的代码对您有用,对我来说也很好:

private void viewDialog() {
    try {
        Intent vpnIntent = new Intent(context, UtilityVpnService.class);
        context.startService(vpnIntent);
        final View Dialogview = View.inflate(getBaseContext(), R.layout.alert_open_internet, null);
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_DIM_BEHIND,
                PixelFormat.TRANSLUCENT);
        params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL;
        windowManager.addView(Dialogview, params);

        Button btn_cancel = (Button) Dialogview.findViewById(R.id.btn_canceldialog_internetblocked);
        Button btn_okay = (Button) Dialogview.findViewById(R.id.btn_openmainactivity);
        RelativeLayout relativeLayout = (RelativeLayout) Dialogview.findViewById(R.id.rellayout_dialog);

            btn_cancel.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Handler handler = new Handler(Looper.getMainLooper());
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                if (Dialogview != null) {
//                                ( (WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
                                    windowManager.removeView(Dialogview);
                                }
                            } catch (final IllegalArgumentException e) {
                                e.printStackTrace();
                                // Handle or log or ignore
                            } catch (final Exception e) {
                                e.printStackTrace();
                                // Handle or log or ignore
                            } finally {
                                try {
                                    if (windowManager != null && Dialogview != null) {
//                                    ((WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
                                        windowManager.removeView(Dialogview);
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                            //    ((WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
//                        windowManager.removeView(Dialogview);


                        }
                    });
                }
            });
            btn_okay.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Handler handler = new Handler(Looper.getMainLooper());
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            //        ((WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
                            try {
                                if (windowManager != null && Dialogview != null)
                                    windowManager.removeView(Dialogview);
                                Intent intent = new Intent(getBaseContext(), SplashActivity.class);
                                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//                        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
//                        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);


                                context.startActivity(intent);
                            } catch (Exception e) {
                                windowManager.removeView(Dialogview);
                                e.printStackTrace();
                            }
                        }
                    });
                }
            });
        } catch (Exception e) {
            //` windowManager.removeView(Dialogview);
            e.printStackTrace();
        }
    }

如果您从后台服务调用视图,请不要全局定义视图。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.