Vista de Android no adjunta al administrador de ventanas

111

Tengo algunas de las siguientes excepciones:

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)

Lo busqué en Google y veo que tiene algo que ver con ventanas emergentes y girar la pantalla, pero no hay ninguna referencia a mi código.

Las preguntas son:

  1. ¿Hay alguna manera de saber exactamente cuándo ocurre este problema?
  2. además de girar la pantalla, ¿hay algún otro evento o acción que provoque este error?
  3. ¿Cómo evito que esto suceda?
Daniel Benedykt
fuente
1
Vea si puede explicar cómo se describe la actividad en el manifiesto y qué actividad hay en la pantalla cuando ocurre el error. Vea si puede eliminar su problema en un caso de prueba mínimo.
Josh Lee
¿Quizás está intentando modificar su vista antes de que View#onAttachedToWindow()se le haya llamado?
Alex Lockwood

Respuestas:

163

Tuve este problema en el que, en un cambio de orientación de la pantalla, la actividad finalizó antes de la AsyncTask con el cuadro de diálogo de progreso completado. Parecía resolver esto estableciendo el cuadro de diálogo en nulo onPause()y luego verificando esto en AsyncTask antes de descartar.

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

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

... en mi AsyncTask:

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

protected void onPostExecute(Object result) {
   if ((mDialog != null) && mDialog.isShowing()) { 
        mDialog.dismiss();
   }
}
johnnyblizzard
fuente
12
@YekhezkelYovel, AsyncTask se está ejecutando en un hilo que sobrevive al reinicio de la actividad y puede contener referencias a la Actividad ahora muerta y sus Vistas (esto es efectivamente una pérdida de memoria, aunque probablemente sea a corto plazo, depende de cuánto tiempo tarde en completarse la tarea ). La solución anterior funciona bien, si acepta una pérdida de memoria a corto plazo. El enfoque recomendado es cancelar () cualquier AsyncTask en ejecución cuando la actividad está en pausa.
Stevie
8

Después de una pelea con este problema, finalmente termino con esta solución:

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

A veces, un buen manejo de excepciones funciona bien si no hubiera una mejor solución para este problema.

blueware
fuente
5

Si tiene un Activityobjeto dando vueltas, puede usar el isDestroyed()método:

Activity activity;

// ...

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

Esto es bueno si tiene una AsyncTasksubclase no anónima que usa en varios lugares.

Shawkinaw
fuente
3

Estoy usando una clase estática personalizada que hace, muestra y oculta un diálogo. esta clase también está siendo utilizada por otras actividades, no solo una actividad. Ahora también se me apareció el problema que describiste y me he quedado toda la noche para encontrar una solución.

¡Finalmente les presento la solución!

Si desea mostrar o descartar un cuadro de diálogo y no sabe qué actividad inició el cuadro de diálogo para poder tocarlo, el siguiente código es para usted.

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


            }
        } 

 }
aimiliano
fuente
1

La solución anterior no funcionó para mí. Entonces, lo que hice fue tomar ProgressDialogcomo globalmente y luego agregar esto a mi actividad

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

de modo que, en caso de que se destruya la actividad, el ProgressDialog también se destruirá.

Kishan Solanki
fuente
0

Para la pregunta 1):

Teniendo en cuenta que el mensaje de error no parece decir qué línea de su código está causando el problema, puede rastrearlo utilizando puntos de interrupción. Los puntos de interrupción pausan la ejecución del programa cuando el programa llega a líneas específicas de código. Al agregar puntos de interrupción a ubicaciones críticas, puede determinar qué línea de código causa el bloqueo. Por ejemplo, si su programa se bloquea en una línea setContentView (), podría poner un punto de interrupción allí. Cuando el programa se ejecuta, se detendrá antes de ejecutar esa línea. Si luego reanudar hace que el programa se bloquee antes de alcanzar el siguiente punto de interrupción, entonces sabrá que la línea que mató al programa estaba entre los dos puntos de interrupción.

Agregar puntos de interrupción es fácil si usa Eclipse. Haga clic derecho en el margen a la izquierda de su código y seleccione "Alternar punto de interrupción". Luego, debe ejecutar su aplicación en modo de depuración, el botón que parece un insecto verde junto al botón de ejecución normal. Cuando el programa llega a un punto de interrupción, Eclipse cambiará a la perspectiva de depuración y le mostrará la línea en la que está esperando. Para que el programa vuelva a ejecutarse, busque el botón 'Reanudar', que parece un 'Reproducir' normal pero con una barra vertical a la izquierda del triángulo.

También puede completar su solicitud con Log.d ("Mi solicitud", "Alguna información aquí que le indica dónde está la línea de registro"), que luego publica mensajes en la ventana LogCat de Eclipse. Si no puede encontrar esa ventana, ábrala con Ventana -> Mostrar vista -> Otro ... -> Android -> LogCat.

¡Espero que ayude!

Steve Haley
fuente
7
El problema está sucediendo en los teléfonos de los clientes, por lo que no tengo una opción para depurar. Además, el problema está sucediendo todo el tiempo, solo sucede a veces, así que no sé cómo rastrearlo
Daniel Benedykt
Aún puede recibir mensajes de depuración desde un teléfono, si puede conseguir uno. En el menú -> configuración -> aplicaciones -> desarrollo, hay una opción para depuración USB. Si está habilitado, puede conectar el teléfono y LogCat captura todas las líneas de depuración normales. Aparte de eso ... bueno ... la intermitencia podría explicarse por ello dependiendo del estado del programa. Supongo que tendrías que perder el tiempo usando el programa para ver si puedes recrear el problema.
Steve Haley
4
Como dije antes, el problema ocurre en el teléfono de un cliente y no tengo acceso a él. El cliente puede estar en otro continente :)
Daniel Benedykt
Hay aplicaciones en el mercado que le enviarán su logcat por correo electrónico.
Tim Green
1
Solo para que sepas que puedes rotar el emulador presionando la tecla del teclado numérico 9
Rob
0

Agregué lo siguiente al manifiesto para esa actividad

android:configChanges="keyboardHidden|orientation|screenLayout"
Rickey
fuente
19
Esta no es una solución: el sistema también podría destruir su actividad por otras razones.
Roel
1
¿Podría explicar su respuesta por favor?
Leebeedev
0

según el código del windowManager (enlace aquí ), esto ocurre cuando la vista que está intentando actualizar (que probablemente pertenece a un diálogo, pero no es necesario) ya no está adjunta a la raíz real de las ventanas.

como han sugerido otros, debe verificar el estado de la actividad antes de realizar operaciones especiales en sus cuadros de diálogo.

aquí está el código relacionado, que es la causa del problema (copiado del código fuente de 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;
        }
    }
desarrollador de Android
fuente
el código que he escrito aquí es lo que hace Google. lo que he escrito es para mostrar la causa de este problema.
desarrollador de Android
0

Mi problema se resolvió bloqueando la rotación de la pantalla en mi Android, la aplicación que me estaba causando un problema ahora funciona perfectamente

Roger
fuente
0

Otra opción es no iniciar la tarea asíncrona hasta que el cuadro de diálogo se adjunte a la ventana anulando onAttachedToWindow () en el cuadro de diálogo, de esa manera siempre se puede descartar.

Nick Palmer
fuente
0

O simplemente puedes agregar

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

que hará que ProgressDialogno se pueda cancelar

Sarith Vasu
fuente
-2

Por qué no intentar atrapar, así:

protected void onPostExecute(Object result) {
        try {
            if ((mDialog != null) && mDialog.isShowing()) {
                mDialog.dismiss();
            }
        } catch (Exception ex) {
            Log.e(TAG, ex.getMessage(), ex);
        }
    }
Heasen
fuente
IllegalStateOfException Debería tratarse de una mejor manera, en lugar de comportarse de manera anormal.
Lavakush
-3

cuando declaras actividad en el manifiesto necesitas android: configChanges = "Orientación"

ejemplo:

<activity android:theme="@android:style/Theme.Light.NoTitleBar" android:configChanges="orientation"  android:label="traducción" android:name=".PantallaTraductorAppActivity"></activity>
Cristian
fuente
1
Esta etiqueta es necesaria solo si el desarrollador no desea destruir y recrear la actividad mediante el sistema Android.
Ankit