java.lang.IllegalArgumentException: vista no adjunta al administrador de ventanas

148

Tengo una actividad que inicia AsyncTask y muestra el diálogo de progreso durante la duración de la operación. La actividad se declara como NO recreada mediante rotación o deslizamiento del teclado.

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

Una vez que se completa la tarea, descarto el diálogo, pero en algunos teléfonos (framework: 1.5, 1.6) se produce dicho error:

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)

Mi código es:

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();

Por lo que he leído ( http://bend-ing.blogspot.com/2008/11/properly-handle-progress-dialog-in.html ) y visto en las fuentes de Android, parece que la única situación posible para obtener eso La excepción es cuando la actividad fue destruida. Pero como he mencionado, prohíbo la recreación de actividades para eventos básicos.

Entonces cualquier sugerencia es muy apreciada.

alex2k8
fuente
1
Esta pregunta tiene muchas respuestas, si alguna de ellas le ayudó, selecciónela como la respuesta correcta.
Parag Kadam

Respuestas:

228

A veces también recibo este error cuando descarto el diálogo y finalizo la actividad del método onPostExecute. Supongo que a veces la actividad termina antes de que el diálogo se cierre correctamente.

Solución simple pero efectiva que funciona para mí.

@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;
    }  
}
Damjan
fuente
44
¿Solución simple? Si. ¿Eficaz? Quizás en este caso. ¿Lo recomendaría? ¡NO! ¡No te tragues TODAS las excepciones así! Ni siquiera captaría la IllegalArgumentException, sino que buscaría otra solución.
Simon Forsberg el
66
Porque, por lo general, las trampas vacías son una mala idea ... Aunque a veces puede ser lo correcto.
Thomas
3
@Damjan Por su respuesta, sugiere el tipo de captura Excepción. Bueno, esta es una mala práctica de Google. Puede leer sobre esto aquí: No atrape la excepción genérica .
Yaniv
17
Creo que esta es una solución efectiva. En casos generales, no deberíamos hacer esto, pero dado que el Marco de Android no proporciona ninguna verificación fácil para nosotros, tenemos que usar una forma inusual. Además, si la llamada isShowing () de un cuadro de diálogo funciona como esperamos, no necesitamos este tipo de pirateo.
SXC
1
solución rápida hasta que se encuentre algo mejor
Rohit Tigga
13

Aquí está mi solución "a prueba de balas", que es una compilación de todas las buenas respuestas que encontré sobre este tema (gracias a @Damjan y @Kachi). Aquí la excepción se traga solo si todas las otras formas de detección no tuvieron éxito. En mi caso, necesito cerrar el diálogo automáticamente y esta es la única forma de proteger la aplicación de un bloqueo. ¡Espero que te ayude! Por favor, vote y deje comentarios si tiene comentarios o una mejor solución. ¡Gracias!

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;
        }
    }
Ivo Stoyanov
fuente
1
Muy buena solución! La configuración dialog = nullno tiene ningún efecto. Y StatusEventDialogdebería leer solo Dialog.
hgoebl
1
StatusEventDialog debe cambiarse a diálogo.
Sreekanth Karumanaghat
Esta respuesta debe ser la aceptada, muy bien manejada
blueware
Entiendo que quieres ser "correcto" y solo usar try / catch en caso de isDestroyed()que no esté disponible, pero a efectos prácticos, ¿no sería lo mismo usar try / catch siempre?
zundi
11

Puedo tener una solución alternativa.

Estaba teniendo el mismo problema, donde estoy cargando muchos artículos (a través del sistema de archivos) en una ListViewvía AsyncTask. Tenía el onPreExecute()encendido a ProgressDialog, y luego ambos onPostExecute()y onCancelled()(llamado cuando la tarea se cancela explícitamente a través de AsyncTask.cancel()) cerrándolo a través de .cancel().

Obtuve el mismo error "java.lang.IllegalArgumentException: vista no adjunta al administrador de ventanas" cuando estaba eliminando el diálogo en el onCancelled()método de AsyncTask(había visto esto en la excelente aplicación de Shelves ).

La solución era crear un campo público en el AsyncTaskque contenga ProgressDialog:

public ProgressDialog mDialog;

Luego, onDestroy()cuando cancelo mi AsyncTask, también puedo eliminar el diálogo asociado a través de:

AsyncTask.mDialog.cancel();

Llamar a AsyncTask.cancel()DOES se activa onCancelled()en el AsyncTask, pero por alguna razón para el momento en que se llama al método, la Vista ya se ha destruido y, por lo tanto, la cancelación del diálogo está fallando.

Paul Mennega
fuente
La implementación de UserTask me parece excelente como mencionó @Paul. El código fuente está aquí: code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/…
Evi Song
Si bien el caso de uso se puede encontrar en el mismo proyecto: code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/…
Evi Song
9

Aquí está la solución correcta para resolver este problema:

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

En lugar de detectar ciegamente todas las excepciones, esta solución aborda la raíz del problema: tratar de atenuar un diálogo cuando la actividad utilizada para inicializar el diálogo ya ha finalizado. Trabajando en mi Nexus 4 con KitKat, pero debería funcionar para todas las versiones de Android.

Kachi
fuente
3
isDestroyedrequiere API 17+
Androiderson el
¿Por qué necesita establecer mProgressDialog en nulo? ¿Está relacionado con la pérdida de memoria? ¿Podría explicar por favor?
Pawan
@Pawan, es un detalle de implementación de mi parte. No es obligatorio, es solo la forma en que funciona la función en esta clase. Después de que se oculta un cuadro de diálogo de progreso, lo configuro en nulo. Cuando un usuario desea mostrar otro cuadro de diálogo de progreso, se crea una instancia nueva.
Kachi
Definitivamente! ((Activity) context) .isFinishing () es obligatorio, ¡gracias! :)
Daniel Krzyczkowski
5

Acepto una opinión de 'Damjan'.
si usa muchos cuadros de diálogo, debería cerrar todos los cuadros de diálogo en onDestroy () o onStop ().
entonces es posible que pueda reducir la frecuencia de la excepción 'java.lang.IllegalArgumentException: vista no adjunta al administrador de ventanas'.

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



pero poco excede ...
para que quede más claro, evita mostrar cualquier diálogo después de que onDestroy lo llame.
No uso como a continuación. Pero está claro.

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


¡buena suerte!

Hogun
fuente
Siempre preferiré evitar bloques vacíos. Vale la pena intentarlo, pero dado que este error es difícil de producir, solo el tiempo dirá si realmente está funcionando. gracias de cualquier manera.
Dror Fichman
¿Qué son los bloques vacíos? No uso try / catch. se supera una variable mIsDestroyed funcionando. pero si escribe en el código que muestra el cuadro de diálogo después de trabajar en otro hilo, es posible que necesite esta variable. cuando otro hilo está funcionando, si la actividad ha finalizado, puede ver esta excepción.
Hogun
Tuve el mismo problema y cuando agregué @Override public void onPause () {if (dialog! = Null) dialog.dismiss (); super.onPause (); } hasta el presente no tengo este error ... así que creo que es lo mismo que tu respuesta y es realmente útil
Chris Sim
@ChrisSim hola! onPuase () y onDestroy () es la diferencia. Cuando la actividad está activada, el diálogo se cierra. y cuando ejecuta la aplicación, no se muestra el diálogo. lo quieres
Hogun
@Hogun Sí, por supuesto, quiero decir la misma idea, estoy cerrando el diálogo en pausa en lugar de destruir porque lo necesito en pausa, no en destruir. En segundo lugar, lo cierro justo cuando no es nulo. Gracias por explicar esto a los demás.
Chris Sim
4

Utilizar este.

if(_dialog!=null && _dialog.isShowing())
_dialog.dismiss();
Pralabh Jain
fuente
2
Es casi la misma solución que propuso @Damjan.
Yury
28
Esto no es suficiente, la IllegalArgumentException todavía ocurre con esta verificación.
Murphy
Hice la misma solución, pero aún no sé si es efectiva. La única diferencia es que anidé dos if para asegurarme de que la segunda parte .isShowing () no se evaluará si es nula.
Nick
2
Esto no es suficiente.
trante
1
@Nick: no es necesario anidar múltiples ifen casos como este, el &&operador de Java tiene una evaluación diferida (también llamada cortocircuito), lo que significa que el segundo operando no se evalúa si el primero evalúa false(lo que significa el resultado de la &&siempre habrá falsede todos modos, por lo tanto, la evaluación "perezosa"). Del mismo modo ||, no evaluará su segundo operando si el primero se evalúa como true. Nota: los operadores &y |no tienen este comportamiento y, por lo tanto, siempre evalúan ambos operandos.
Matthias
3

Tuve el mismo problema, puedes resolverlo de la siguiente manera:

@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;
    }  
}
motociclista
fuente
2

Creo que su código es correcto a diferencia de la otra respuesta sugerida. onPostExecute se ejecutará en el hilo de la interfaz de usuario. Ese es el objetivo de AsyncTask: no tiene que preocuparse por llamar a runOnUiThread o tratar con controladores. Además, de acuerdo con los documentos, se puede invocar de forma segura desde cualquier subproceso (no estoy seguro de que hayan hecho de esto la excepción).

¿Quizás es un problema de tiempo donde se llama a dialog.dismiss () después de que la actividad ya no se muestra?

¿Qué pasa con la prueba de lo que sucede si comenta el setOnCancelListener y luego sale de la actividad mientras se ejecuta la tarea en segundo plano? Entonces su onPostExecute intentará descartar un diálogo ya descartado. Si la aplicación falla, probablemente pueda verificar si el cuadro de diálogo está abierto antes de cerrarlo.

Tengo exactamente el mismo problema, así que voy a probarlo en código.

Brandon O'Rourke
fuente
También busqué en el código dimiss (), y de hecho, se puede llamar de forma segura desde cualquier hilo. Por cierto, tengo un problema con las pruebas, ya que este problema ocurre en los teléfonos de los usuarios, y nunca pude reproducirlo yo solo :-( Así que tratando de resolverlo analizando el código ... De acuerdo con el tiempo. Estaba pensando en esto, pero no puedo imaginar una situación en la que la Actividad se pueda cerrar antes del Diálogo. Si se presiona ATRÁS, entonces se cancelará el Diálogo primero. Y la recreación automática de la actividad está prohibida por el archivo de manifiesto, pero ¿puede ser que todavía se pueda recrear de alguna manera? ¡sé si encuentras algo!
alex2k8
2

alex,

Podría estar equivocado aquí, pero sospecho que varios teléfonos 'en estado salvaje' tienen un error que hace que cambien de orientación en aplicaciones que están marcadas como orientadas estáticamente. Esto sucede bastante en mi teléfono personal y en muchos de los teléfonos de prueba que usa nuestro grupo (incluidos droide, n1, g1, héroe). Por lo general, una aplicación marcada como estáticamente orientada (tal vez verticalmente) se presentará por un segundo o dos utilizando una orientación horizontal, y luego cambiará de inmediato. El resultado final es que, aunque no desea que su aplicación cambie de orientación, debe estar preparado para que así sea. No sé en qué condiciones exactas se puede reproducir este comportamiento, no sé si es específico de una versión de Android. Todo lo que sé es que lo he visto suceder muchas veces :(

Recomendaría usar la solución provista en el enlace que publicó que sugiere anular el método Activity onCreateDialog y dejar que el sistema operativo Android administre el ciclo de vida de sus cuadros de diálogo. Me parece que a pesar de que no desea que su actividad cambie de orientación, está cambiando de orientación en alguna parte. Puede intentar localizar un método que siempre evitará el cambio de orientación, pero estoy tratando de decirle que personalmente no creo que haya una manera infalible que funcione en todos los teléfonos Android actuales en el mercado.

Hamy
fuente
1
Puede evitar que su dispositivo cambie de orientación, pero hay muchos otros cambios de configuración que destruyen / recrean su Actividad: una común es deslizar un teclado hacia adentro o hacia afuera.
MaximumGoat
2

Lo que funcionó para mí la mayor parte del tiempo es verificar si la Actividad no está terminando.

if (!mActivity.isFinishing()) {
    dialog.dismiss();
}
Herve Thu
fuente
2

La actividad se declara como NO recreada mediante rotación o deslizamiento del teclado.

Acabo de tener el mismo problema. Arreglo para API nivel 13 o superior.
Desde documentos de Android:

Nota: Si su aplicación se dirige al nivel 13 o superior de la API (según lo declarado por los atributos minSdkVersion y targetSdkVersion), también debe declarar la configuración "screenSize", porque también cambia cuando un dispositivo cambia entre las orientaciones vertical y horizontal.

Así que he cambiado mi manifiesto a esto:

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

Y ahora funciona bien. La actividad no se recrea cuando giro el teléfono, el diálogo de progreso y la vista permanecen igual. No hay error para mi.

Derivación
fuente
Esta no es una solución. por ejemplo, tengo una actividad de Adview de admob y debería recrear la actividad para los cambios de tamaño.
batmaci
2

En primer lugar, maneje los errores siempre que intente descartar el diálogo.

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

Si eso no se soluciona, descartarlo en el método onStop () de la actividad.

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

Tuve el mismo problema al usar un botón para sincronizar una lista del servidor: 1) Hago clic en el botón 2) Aparece un cuadro de diálogo de progreso al descargar la lista del servidor 3) Giro el dispositivo a otra orientación 4) java.lang .IllegalArgumentException: vista no adjunta al administrador de ventanas en postExecute () de AsyncTask durante progress.dismiss ().

Cuando probé la solución, pensé que incluso si el problema no se producía, mi lista no mostraba todos los elementos.

Pensé que lo que quería era que AsyncTask terminara (y descartara el diálogo) antes de que se destruyera la actividad, así que hice del objeto asynctask un atributo y anulé el método onDestroy ().

Si el asinctask toma mucho tiempo, el usuario tal vez sienta que el dispositivo es lento, pero creo que ese es el precio que paga por tratar de cambiar la orientación del dispositivo mientras se muestra el diálogo de progreso. E incluso si lleva algún tiempo, la aplicación no se bloquea.

private AsyncTask<Boolean, Void, Boolean> atask;

@Override
protected void onDestroy() {
    if (atask!=null)
        try {
            atask.get();
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    super.onDestroy();
}
shadowglas
fuente
1
@Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            if (progressDialog != null && progressDialog.isShowing()) {
                Log.i(TAG, "onPostexucte");
                progressDialog.dismiss();
}
}
vikseln
fuente
3
Si bien este fragmento de código puede responder la pregunta y proporcionar una explicación de cómo resuelve el problema, ayudará a los futuros visitantes del sitio a comprender su respuesta
RobV
0

Más abajo el código funciona para ti, funciona perfectamente para mí:

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

No defina su vista globalmente si la llama desde el servicio en segundo plano.

Shashwat Gupta
fuente