【问题标题】:android View not attached to window managerandroid View未附加到窗口管理器
【发布时间】:2011-01-14 13:04:15
【问题描述】:

我遇到以下一些例外情况:

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. 如何防止这种情况发生?

【问题讨论】:

  • 看看你能不能解释一下这个activity在manifest中是如何描述的,以及当错误发生时屏幕上的activity是什么。看看你能不能把你的问题分解成一个最小的测试用例。
  • 也许您正试图在调用View#onAttachedToWindow() 之前修改您的视图?

标签: android exception


【解决方案1】:

我遇到了这个问题,在屏幕方向更改时,活动在 AsyncTask 之前完成,进度对话框完成。我似乎通过将对话框设置为 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();
   }
}

【讨论】:

  • @YekhezkelYovel AsyncTask 正在一个线程中运行,该线程在 Activity 重新启动后仍然存在,并且可能持有对现已死亡的 Activity 及其视图的引用(这实际上是内存泄漏,尽管可能是短期的 -取决于完成任务需要多长时间)。如果您愿意接受短期内存泄漏,上述解决方案可以正常工作。推荐的方法是在 Activity 暂停时取消()任何正在运行的 AsyncTask。
【解决方案2】:

在与这个问题斗争之后,我最终得到了这个解决方法:

/**
 * 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;
    }
}

有时,如果没有更好的解决方案来解决这个问题,良好的异常处理效果会很好。

【讨论】:

    【解决方案3】:

    如果你有一个Activity 对象,你可以使用isDestroyed() 方法:

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

    如果您有一个在不同地方使用的非匿名 AsyncTask 子类,那就太好了。

    【讨论】:

      【解决方案4】:

      我正在使用一个自定义静态类,它可以显示和隐藏一个对话框。 这个类也被其他活动使用,而不仅仅是一个活动。 现在你描述的问题也出现在我面前,我已经过夜寻找解决方案..

      最后我给你介绍一下解决方案!

      如果您想显示或关闭一个对话框并且您不知道是哪个活动启动了该对话框以便触摸它,那么以下代码适合您..

       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;
                          }
                      }
      
      
                  }
              } 
      
       }
      

      【讨论】:

        【解决方案5】:

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

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

        这样,如果活动被破坏,那么 ProgressDialog 也将被破坏。

        【讨论】:

          【解决方案6】:

          对于问题 1):

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

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

          您还可以使用 Log.d("My application", "Some information here that tell you the log line is") 填充您的应用程序,然后在 Eclipse 的 LogCat 窗口中发布消息。如果找不到该窗口,请使用 Window -> Show View -> Other... -> Android -> LogCat 打开它。

          希望有帮助!

          【讨论】:

          • 问题发生在客户端手机上,所以我没有调试选项。而且这个问题一直在发生,只是有时会发生,所以不知道如何跟踪它
          • 您仍然可以从手机中获取调试消息,如果您能拿到手机的话。在菜单 -> 设置 -> 应用程序 -> 开发中,有一个 USB 调试选项。如果启用,您可以插入电话,LogCat 会捕获所有正常的调试行。除此之外......嗯......间歇性可以根据程序的状态来解释。我想您必须使用该程序来查看是否可以重新创建问题。
          • 正如我之前所说,问题发生在客户端电话上,我无法访问它。客户可能在另一个大陆:)
          • 市场上有一些应用程序会通过电子邮件向您发送他们的 logcat。
          • 只是让您知道您可以通过按数字键盘键 9 来旋转模拟器
          【解决方案7】:

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

          android:configChanges="keyboardHidden|orientation|screenLayout"
          

          【讨论】:

          • 这不是解决方案:系统也可能由于其他原因破坏您的活动。
          • 你能解释一下你的答案吗?
          【解决方案8】:

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

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

          这是相关代码,这是问题的原因(从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;
                  }
              }
          

          【讨论】:

          • 我在这里写的代码是谷歌所做的。我写的是为了说明这个问题的原因。
          【解决方案9】:

          我的问题通过 uhlocking 我的 android 上的屏幕旋转解决了,导致我出现问题的应用程序现在完美运行

          【讨论】:

            【解决方案10】:

            另一种选择是在通过覆盖对话框上的 onAttachedToWindow() 将对话框附加到窗口之前不启动异步任务,这样它总是可以关闭的。

            【讨论】:

              【解决方案11】:

              或者你可以简单地添加

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

              这将使ProgressDialog 无法取消

              【讨论】:

                【解决方案12】:

                为什么不试试 catch,像这样:

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

                【讨论】:

                • IllegalStateOfException 应该以更好的方式处理,而不仅仅是行为异常。
                【解决方案13】:

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

                示例:

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

                【讨论】:

                • 只有当开发者不想通过android系统销毁和重新创建活动时才需要这个标签。
                猜你喜欢
                • 1970-01-01
                • 2012-06-09
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-03-23
                • 2014-05-20
                • 1970-01-01
                相关资源
                最近更新 更多