android视图未附加到窗口管理器


111

我有以下一些例外情况:

java.lang.IllegalArgumentException: View not attached to window manager
at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:355)
at android.view.WindowManagerImpl.updateViewLayout(WindowManagerImpl.java:191)
at android.view.Window$LocalWindowManager.updateViewLayout(Window.java:428)
at android.app.Dialog.onWindowAttributesChanged(Dialog.java:596)
at android.view.Window.setDefaultWindowFormat(Window.java:1013)
at com.android.internal.policy.impl.PhoneWindow.access$700(PhoneWindow.java:86)
at com.android.internal.policy.impl.PhoneWindow$DecorView.drawableChanged(PhoneWindow.java:1951)
at com.android.internal.policy.impl.PhoneWindow$DecorView.fitSystemWindows(PhoneWindow.java:1889)
at android.view.ViewRoot.performTraversals(ViewRoot.java:727)
at android.view.ViewRoot.handleMessage(ViewRoot.java:1633)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4338)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
at dalvik.system.NativeStart.main(Native Method)

我已经用谷歌搜索了一下,发现它与弹出窗口和打开屏幕有关,但是没有引用我的代码。

问题是:

  1. 有没有一种方法可以确切地找出何时发生此问题?
  2. 除了转动屏幕之外,是否还有其他事件或动作触发此错误?
  3. 如何防止这种情况发生?

1
看看您是否可以解释在清单中如何描述活动以及发生错误时屏幕上显示的活动。看看是否可以将您的问题解决成一个最小的测试用例。
Josh Lee 2010年

也许您正在尝试View#onAttachedToWindow()在调用视图之前修改视图?
Alex Lockwood

Answers:


163

我遇到了这样的问题:在屏幕方向更改上,活动在A进度任务完成且进度对话框完成之前完成了。我似乎可以通过将对话框设置为null来解决此问题onPause(),然后在关闭前在AsyncTask中检查它。

@Override
public void onPause() {
    super.onPause();

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

...在我的AsyncTask中:

protected void onPreExecute() {
    mDialog = ProgressDialog.show(mContext, "", "Saving changes...",
            true);
}

protected void onPostExecute(Object result) {
   if ((mDialog != null) && mDialog.isShowing()) { 
        mDialog.dismiss();
   }
}

12
@YekhezkelYovel AsyncTask在一个线程中运行,该线程在活动重新启动后仍然存在,并且可能保存对已死的Activity及其视图的引用(这实际上是内存泄漏,尽管可能是短期的-取决于任务完成所需的时间)。如果您乐意接受短期内存泄漏,则上述解决方案效果很好。推荐的方法是在Activity暂停时取消()任何正在运行的AsyncTask。
Stevie

8

经过与这个问题的斗争,我最终得到了以下解决方法:

/**
 * Dismiss {@link ProgressDialog} with check for nullability and SDK version
 *
 * @param dialog instance of {@link ProgressDialog} to dismiss
 */
public void dismissProgressDialog(ProgressDialog dialog) {
    if (dialog != null && 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 (!((Activity) context).isFinishing() {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                        if (!((Activity) context).isDestroyed()) {
                            dismissWithExceptionHandling(dialog);
                        }
                    } else { 
                        // Api < 17. Unfortunately cannot check for isDestroyed()
                        dismissWithExceptionHandling(dialog);
                    }
                }
            } else
                // if the Context used wasn't an Activity, then dismiss it too
                dismissWithExceptionHandling(dialog);
        }
        dialog = null;
    }
}

/**
 * Dismiss {@link ProgressDialog} with try catch
 *
 * @param dialog instance of {@link ProgressDialog} to dismiss
 */
public void dismissWithExceptionHandling(ProgressDialog dialog) {
    try {
        dialog.dismiss();
    } catch (final IllegalArgumentException e) {
        // Do nothing.
    } catch (final Exception e) {
        // Do nothing.
    } finally {
        dialog = null;
    }
}

有时,如果没有更好的解决方案来解决此问题,则良好的异常处理会很好地工作。


5

如果Activity周围有对象,可以使用以下isDestroyed()方法:

Activity activity;

// ...

if (!activity.isDestroyed()) {
    // ...
}

如果您有一个AsyncTask在各个地方使用的非匿名子类,那么这很好。


3

我正在使用一个自定义的静态类来显示和隐藏对话框。该类也被其他活动使用,而不仅仅是一个活动。现在,您描述的问题也出现在我的身上,我整夜都在寻找解决方案。

最后,我向您介绍解决方案!

如果要显示或关闭对话框,并且不知道是哪个活动启动了该对话框以进行触摸,则以下代码适合您。

 static class CustomDialog{

     public static void initDialog(){
         ...
         //init code
         ...
     }

      public static void showDialog(){
         ...
         //init code for show dialog
         ...
     }

     /****This is your Dismiss dialog code :D*******/
     public static void dismissProgressDialog(Context context) {                
            //Can't touch other View of other Activiy..
            //http://stackoverflow.com/questions/23458162/dismiss-progress-dialog-in-another-activity-android
            if ( (progressdialog != null) && progressdialog.isShowing()) {

                //is it the same context from the caller ?
                Log.w("ProgressDIalog dismiss", "the dialog is from"+progressdialog.getContext());

                Class caller_context= context.getClass();
                Activity call_Act = (Activity)context;
                Class progress_context= progressdialog.getContext().getClass();

                Boolean is_act= ( (progressdialog.getContext()) instanceof  Activity )?true:false;
                Boolean is_ctw= ( (progressdialog.getContext()) instanceof  ContextThemeWrapper )?true:false;

                if (is_ctw) {
                    ContextThemeWrapper cthw=(ContextThemeWrapper) progressdialog.getContext();
                    Boolean is_same_acivity_with_Caller= ((Activity)(cthw).getBaseContext() ==  call_Act )?true:false;

                    if (is_same_acivity_with_Caller){
                        progressdialog.dismiss();
                        progressdialog = null;
                    }
                    else {
                        Log.e("ProgressDIalog dismiss", "the dialog is NOT from the same context! Can't touch.."+((Activity)(cthw).getBaseContext()).getClass());
                        progressdialog = null;
                    }
                }


            }
        } 

 }

1

上述解决方案对我不起作用。所以我所做的就是将其ProgressDialog视为全局,然后将其添加到我的活动中

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

因此,如果活动被破坏,那么ProgressDialog也将被破坏。


0

对于问题1):

考虑到错误消息似乎并没有说明导致问题的代码的哪一行,因此可以使用断点进行跟踪。当程序到达特定的代码行时,断点会暂停程序的执行。通过在关键位置添加断点,可以确定导致崩溃的代码行。例如,如果您的程序在setContentView()行崩溃,则可以在此处放置一个断点。程序运行时,它将在运行该行之前暂停。如果然后继续执行,导致程序在到达下一个断点之前崩溃,那么您就知道杀死程序的行在两个断点之间。

如果使用Eclipse,则添加断点很容易。右键单击代码左侧的空白处,然后选择“切换断点”。然后,您需要在调试模式下运行应用程序,该按钮在正常运行按钮旁边看起来像绿色的昆虫。当程序到达断点时,Eclipse将切换到调试透视图,并向您显示其等待的行。要重新开始运行程序,请查找“恢复”按钮,该按钮看起来像正常的“播放”,但在三角形的左侧带有垂直条。

您还可以用Log.d(“我的应用程序”,“这里的一些信息告诉您日志行的位置”)填充应用程序,然后将其发布到Eclipse的LogCat窗口中。如果找不到该窗口,请使用窗口->显示视图->其他...-> Android-> LogCat将其打开。

希望有帮助!


7
问题发生在客户端电话上,因此我没有调试选项。这个问题一直在发生,有时只是在发生,所以不知道如何追踪
Daniel Benedykt 2010年

如果可以动手操作,您仍然可以从电话中获取调试消息。在菜单->设置->应用程序->开发中,有一个USB调试选项。如果启用,则可以插入电话,LogCat会捕获所有正常的调试行。除此之外...嗯...可以根据程序的状态来解释间歇性。我想您将不得不使用该程序来弄乱是否可以重现该问题。
史蒂夫·海利

4
正如我之前所说,该问题正在客户端电话上发生,并且我无权访问。客户可能在另一个大陆上:)
Daniel Benedykt 2010年

市场上有一些应用程序会通过电子邮件向您发送日志。
Tim Green 2010年

1
就是这样,您知道您可以通过按数字键盘上的数字键9
Rob

0

我在该活动的清单中添加了以下内容

android:configChanges="keyboardHidden|orientation|screenLayout"

19
这不是解决方案:系统也会由于其他原因破坏您的活动。
Roel

1
你能解释一下你的答案吗?
Leebeedev

0

根据windowManager的代码(在此处链接),当您尝试更新的视图(可能属于对话框,但不是必需的)不再附加到窗口的真实根目录时,就会发生这种情况。

正如其他人所建议的那样,您应该在对对话框执行特殊操作之前检查活动的状态。

这是相关的代码,这是导致问题的原因(从Android源代码复制):

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    final WindowManager.LayoutParams wparams
            = (WindowManager.LayoutParams)params;

    view.setLayoutParams(wparams);

    synchronized (this) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots[index];
        mParams[index] = wparams;
        root.setLayoutParams(wparams, false);
    }
}

private int findViewLocked(View view, boolean required) {
        synchronized (this) {
            final int count = mViews != null ? mViews.length : 0;
            for (int i=0; i<count; i++) {
                if (mViews[i] == view) {
                    return i;
                }
            }
            if (required) {
                throw new IllegalArgumentException(
                        "View not attached to window manager");
            }
            return -1;
        }
    }

我在这里写的代码就是Google所做的。我写的是为了显示此问题的原因。
android开发人员

0

通过在我的android应用上锁定屏幕旋转来解决了我的问题,该问题导致我现在出现问题了


0

另一个选择是不通过覆盖对话框上的onAttachedToWindow()将对话框附加到窗口上来启动异步任务,这样始终可以将其关闭。


0

或者简单地,您可以添加

protected void onPreExecute() {
    mDialog = ProgressDialog.show(mContext, "", "Saving changes...", true, false);
}

这将使ProgressDialog不可取消


-2

为什么不尝试捕获,像这样:

protected void onPostExecute(Object result) {
        try {
            if ((mDialog != null) && mDialog.isShowing()) {
                mDialog.dismiss();
            }
        } catch (Exception ex) {
            Log.e(TAG, ex.getMessage(), ex);
        }
    }

应该以更好的方式处理IllegalStateOfException,而不仅仅是表现异常。
Lavakush

-3

当您在清单中声明活动时,您需要android:configChanges =“ orientation”

例:

<activity android:theme="@android:style/Theme.Light.NoTitleBar" android:configChanges="orientation"  android:label="traducción" android:name=".PantallaTraductorAppActivity"></activity>

1
仅当开发人员不想通过android系统销毁并重新创建活动时,才需要此标签。
Ankit
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.