"Android.view.WindowManager $ BadTokenException: No se puede agregar una ventana" en buider.show ()

118

Desde mi main activity, necesito llamar a una clase interna y en un método dentro de la clase, necesito mostrar AlertDialog. Después de descartarlo, cuando se presione el botón OK, reenvíe a Google Play para la compra.

Las cosas funcionan perfectamente la mayoría de las veces, pero para algunos usuarios se bloquea builder.show()y puedo ver " "android.view.WindowManager$BadTokenException:No se puede agregar la ventana" desde el registro de fallas. Sugiera.

Mi código es bastante parecido a esto:

public class classname1 extends Activity{

  public void onCreate(Bundle savedInstanceState) {
    this.requestWindowFeature(Window.FEATURE_NO_TITLE);
    super.onCreate(savedInstanceState);
    setContentView(R.layout.<view>); 

    //call the <className1> class to execute
  }

  private class classNamename2 extends AsyncTask<String, Void, String>{

    protected String doInBackground(String... params) {}

    protected void onPostExecute(String result){
      if(page.contains("error")) 
      {
        AlertDialog.Builder builder = new AlertDialog.Builder(classname1.this);
        builder.setCancelable(true);
        builder.setMessage("");
        builder.setInverseBackgroundForced(true);
        builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
          public void onClick(DialogInterface dialog, int whichButton){
            dialog.dismiss();
            if(!<condition>)
            {
              try
              {
                String pl = ""; 

                mHelper.<flow>(<class>.this, SKU, RC_REQUEST, 
                  <listener>, pl);
              }

              catch(Exception e)
              {
                e.printStackTrace();
              }
            }  
          }
        });

        builder.show();
      }
    }
  }
}

También he visto el error en otra alerta en la que no estoy reenviando a ninguna otra activity. Es tan simple como esto:

AlertDialog.Builder builder = new AlertDialog.Builder(classname1.this);
    builder.setCancelable(true);

    //if successful
    builder.setMessage(" ");
    builder.setInverseBackgroundForced(true);
    builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int whichButton){
            // dialog.dismiss();
                   }
    });
    builder.show();
}
MSIslam
fuente
2
Si este es su código completo, ¿realmente necesita AsyncTask?
Shobhit Puri
Este no es el código completo, es un código bastante grande, por lo que solo agregué la parte aquí donde veo el problema del informe de
fallas
bien bien. Por lo general, puede publicar el nombre de la función y comentar que está haciendo un montón de cosas allí (como lo hizo ahora). Es más fácil de entender. :).
Shobhit Puri
¿Estás navegando hacia alguna otra actividad de esta actividad en algún lugar?
Shobhit Puri
1
Ha escrito sus comentarios //send to some other activity. Creo que si comentas la parte en la que vas a una nueva actividad, este error desaparecerá. El error parece suceder porque su cuadro de diálogo anterior se descarta por completo y comienza su nueva actividad. En onPostExecute(), tiene el diálogo de alerta y está dando el contexto de la loginActividad. Pero está navegando a la otra actividad, por lo que el contexto se vuelve incorrecto. ¡Por lo tanto, está recibiendo este error! Consulte stackoverflow.com/questions/15104677/… una pregunta similar.
Shobhit Puri

Respuestas:

264
android.view.WindowManager$BadTokenException: Unable to add window"

Problema:

Esta excepción se produce cuando la aplicación intenta notificar al usuario desde el hilo en segundo plano (AsyncTask) abriendo un cuadro de diálogo.

Si está intentando modificar la interfaz de usuario desde el hilo de fondo (generalmente desde onPostExecute () de AsyncTask) y si la actividad entra en la etapa de finalización, es decir, llamando explícitamente a finish (), el usuario presiona el botón de inicio o retroceso o la limpieza de la actividad realizada por Android, entonces usted obtener este error.

Razón:

El motivo de esta excepción es que, como dice el mensaje de excepción, la actividad ha finalizado pero está intentando mostrar un diálogo con un contexto de la actividad finalizada. Dado que no hay una ventana para que el cuadro de diálogo muestre, el tiempo de ejecución de Android arroja esta excepción.

Solución:

Use el isFinishing()método al que llama Android para verificar si esta actividad está en proceso de finalización: ya sea una llamada explícita a finish () o una limpieza de actividad realizada por Android. Al usar este método, es muy fácil evitar abrir un diálogo desde un hilo en segundo plano cuando la actividad está terminando.

También mantenga un weak referencepara la actividad (y no una referencia fuerte para que la actividad pueda ser destruida una vez que no sea necesaria) y verifique si la actividad no está terminando antes de realizar cualquier IU usando esta referencia de actividad (es decir, mostrando un diálogo).

ej .

private class chkSubscription extends AsyncTask<String, Void, String>{

  private final WeakReference<login> loginActivityWeakRef;

  public chkSubscription (login loginActivity) {
    super();
    this.loginActivityWeakRef= new WeakReference<login >(loginActivity)
  }

  protected String doInBackground(String... params) {
    //web service call
  }

  protected void onPostExecute(String result) {
    if(page.contains("error")) //when not subscribed
    {
      if (loginActivityWeakRef.get() != null && !loginActivityWeakRef.get().isFinishing()) {
        AlertDialog.Builder builder = new AlertDialog.Builder(login.this);
        builder.setCancelable(true);
        builder.setMessage(sucObject);
        builder.setInverseBackgroundForced(true);

        builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
          public void onClick(DialogInterface dialog, int whichButton){
            dialog.dismiss();
          }
        });

        builder.show();
      }
    }
  }
}

Actualización:

Fichas de ventana:

Como su nombre lo indica, un token de ventana es un tipo especial de token de Binder que el administrador de ventanas usa para identificar de forma única una ventana en el sistema. Los tokens de ventana son importantes para la seguridad porque hacen imposible que las aplicaciones maliciosas se dibujen sobre las ventanas de otras aplicaciones. El administrador de ventanas protege contra esto al requerir que las aplicaciones pasen el token de ventana de su aplicación como parte de cada solicitud para agregar o eliminar una ventana. Si los tokens no coinciden, el administrador de ventanas rechaza la solicitud y lanza una BadTokenException . Sin tokens de ventana, este paso de identificación necesario no sería posible y el administrador de ventanas no podría protegerse de aplicaciones maliciosas.

 Un escenario del mundo real:

Cuando una aplicación se inicia por primera vez,  ActivityManagerService  crea un tipo especial de token de ventana llamado token de ventana de aplicación, que identifica de forma única la ventana de contenedor de nivel superior de la aplicación. El administrador de actividades le da este token tanto a la aplicación como al administrador de ventanas, y la aplicación envía el token al administrador de ventanas cada vez que quiere agregar una nueva ventana a la pantalla. Esto garantiza una interacción segura entre la aplicación y el administrador de ventanas (al hacer imposible agregar ventanas sobre otras aplicaciones) y también facilita que el administrador de actividades realice solicitudes directas al administrador de ventanas.

Ritesh Gune
fuente
¡Esto tiene mucho sentido! Además, tu solución me suena genial. (y)
MSIslam
El mensaje 'El campo final en blanco loginActivityWeakRef puede no haber sido inicializado' y lo intentó de esta manera: private final WeakReference <login> loginActivityWeakRef = new WeakReference <login> (login.this); no estoy seguro de si es lo correcto
MSIslam
También eliminé el final antes de WeakReference <login> loginActivityWeakRef ya que mostraba un error en el constructor.
MSIslam
1
intente usar el nuevo chkCubscription (this) .execute (""); en lugar de nuevo chkCubscription.execute (""); como publicaste arriba.
Ritesh Gune
2
¡¡Terrible error !! Estoy siguiendo un tutorial y como @PhilRoggenbuck, mi problema fue causado por llamar a Toast..Show () justo antes de llamar a StartActivity (...). Para solucionarlo, moví el brindis en la actividad recién llamada.
Thierry
26

Tenía un diálogo que muestra la función:

void showDialog(){
    new AlertDialog.Builder(MyActivity.this)
    ...
    .show();
}

Recibía este error y solo tenía que verificar isFinishing()antes de llamar a este cuadro de diálogo que muestra la función.

if(!isFinishing())
    showDialog();
Jemshit Iskenderov
fuente
1
¿No deberíamos escribir if(!MyActivity.this.isFinishing())? Si no está bien en MyActivity
Bibaswann Bandyopadhyay
2
¿Por qué Android ejecutaría código si ya está terminando? Si seguimos esta solución, imagínese cuánto tiempo deberíamos utilizar en isFinishing para evitar problemas similares.
David
@David Creo que a esto le faltan algunos detalles, como el cuadro de diálogo que se llama en un hilo de fondo, pero estoy completamente de acuerdo con su punto tal como está ahora.
Carrito abandonado
Gran punto, ¿por qué diablos tendría que verificar si está terminando?
Chibueze Opata
9

La posible razón es el contexto del diálogo de alerta. Es posible que haya terminado esa actividad, por lo que está tratando de abrirse en ese contexto pero que ya está cerrado. Intente cambiar el contexto de ese diálogo a su primera actividad porque no estará terminado hasta el final.

p.ej

En vez de esto.

AlertDialog alertDialog = new AlertDialog.Builder(this).create();

tratar de usar

AlertDialog alertDialog = new AlertDialog.Builder(FirstActivity.getInstance()).create();
Raghavendra
fuente
3
  • primero no puede extender AsyncTask sin anular doInBackground
  • segundo intente crear AlterDailog desde el constructor y luego llame a show ().

    private boolean visible = false;
    class chkSubscription extends AsyncTask<String, Void, String>
    {
    
        protected void onPostExecute(String result)
        {
            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
            builder.setCancelable(true);
            builder.setMessage(sucObject);
            builder.setInverseBackgroundForced(true);
            builder.setNeutralButton("Ok", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton)
                {
                    dialog.dismiss();
                }
            });
    
            AlertDialog myAlertDialog = builder.create();
            if(visible) myAlertDialog.show();
        }
    
        @Override
        protected String doInBackground(String... arg0)
        {
            // TODO Auto-generated method stub
            return null;
        }
    }
    
    
    @Override
    protected void onResume()
    {
        // TODO Auto-generated method stub
        super.onResume();
        visible = true;
    }
    
    @Override
    protected void onStop()
    {
        visible = false; 
        super.onStop();
    }
    
moh.sukhni
fuente
1
Gracias por la respuesta. De hecho, utilicé el método doInBackground, pero no lo mencioné aquí porque no está relacionado con la alerta. Con respecto a la adición de builder.create (), parece que funciona bien, pero no sé si funcionará bien para todos. Como dije anteriormente, mi código actual también funciona bien, pero solo algunas veces para algunos usuarios muestra el problema de no poder agregar la ventana. ¿Podría sugerirme cuál podría ser un problema real en mi codificación que podría causar esto?
MSIslam
en este caso, el usuario sale de su actividad antes de que se llame a onPostExecute, por lo que no hay una ventana para mantener el cuadro de diálogo, y esto hace que su aplicación se bloquee. agregue una bandera a onStop para saber si su actividad ya no es visible, entonces no muestre el diálogo.
moh.sukhni
onPostExecute realmente se llama, ya que builder.show () está bajo una condición cuando estoy verificando si el usuario no está suscrito en función del resultado de la llamada al servicio web de doInBackground (). Entonces, si no se hubiera llamado onPostExecute, no habría llegado a la línea builder.show ().
MSIslam
onPostExecute se llama de forma predeterminada después de doInBackground, no puede llamarlo y se ejecutará lo que sea.
Moh.sukhni
1
su tarea asíncrona continuará funcionando después de que el usuario navegue desde su actividad y esto hará que builder.show () rastree su aplicación porque no hay actividad para manejar la IU por usted. por lo que su aplicación está extrayendo datos de la web, pero su actividad se ha destruido antes de obtener los datos.
moh.sukhni
1

Estoy creando Diálogo onCreatey utilizándolo con showy hide. Para mí la causa raíz no fue despedir onBackPressed, que fue terminar la Homeactividad.

@Override
public void onBackPressed() {
new AlertDialog.Builder(this)
                .setTitle("Really Exit?")
                .setMessage("Are you sure you want to exit?")
                .setNegativeButton(android.R.string.no, null)
                .setPositiveButton(android.R.string.yes,
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog,
                                    int which) {
                                Home.this.finish();
                                return;
                            }
                        }).create().show();

Estaba terminando la actividad de inicio onBackPressedsin cerrar / descartar mis diálogos.

Cuando descarté mis diálogos, el bloqueo desapareció.

new AlertDialog.Builder(this)
                .setTitle("Really Exit?")
                .setMessage("Are you sure you want to exit?")
                .setNegativeButton(android.R.string.no, null)
                .setPositiveButton(android.R.string.yes,
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog,
                                    int which) {
                                networkErrorDialog.dismiss() ;
                                homeLocationErrorDialog.dismiss() ;
                                currentLocationErrorDialog.dismiss() ;
                                Home.this.finish();
                                return;
                            }
                        }).create().show();
Siddharth
fuente
0

Intento esto resuelto.

 AlertDialog.Builder builder = new AlertDialog.Builder(
                   this);
            builder.setCancelable(true);
            builder.setTitle("Opss!!");

            builder.setMessage("You Don't have anough coins to withdraw. ");
            builder.setMessage("Please read the Withdraw rules.");
            builder.setInverseBackgroundForced(true);
            builder.setPositiveButton("OK",
                    (dialog, which) -> dialog.dismiss());
            builder.create().show();
himanshu kumar
fuente
-1

Prueba esto :

    public class <class> extends Activity{

    private AlertDialog.Builder builder;

    public void onCreate(Bundle savedInstanceState) {
                    this.requestWindowFeature(Window.FEATURE_NO_TITLE);
                    super.onCreate(savedInstanceState);

                setContentView(R.layout.<view>); 

                builder = new AlertDialog.Builder(<class>.this);
                builder.setCancelable(true);
                builder.setMessage(<message>);
                builder.setInverseBackgroundForced(true);

        //call the <className> class to execute
}

    private class <className> extends AsyncTask<String, Void, String>{

    protected String doInBackground(String... params) {

    }
    protected void onPostExecute(String result){
        if(page.contains("error")) //when not subscribed
        {   
           if(builder!=null){
                builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton){
                    dialog.dismiss();
                        if(!<condition>)
                        {
                        try
                        {
                        String pl = ""; 

                        mHelper.<flow>(<class>.this, SKU, RC_REQUEST, 
                        <listener>, pl);
                        }

                        catch(Exception e)
                        {
                        e.printStackTrace();
                        }
                    }  
                }
            });

            builder.show();
        }
    }

}
}
pathe.kiran
fuente
-4

con esta idea de variables globales, guardé la instancia de MainActivity en onCreate (); Variable global de Android

public class ApplicationController extends Application {

    public static MainActivity this_MainActivity;
}

y abrir un diálogo como este. funcionó.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Global Var
    globals = (ApplicationController) this.getApplication();
    globals.this_MainActivity = this;
}

y en un hilo, abro un diálogo como este.

AlertDialog.Builder alert = new AlertDialog.Builder(globals.this_MainActivity);
  1. Abrir MainActivity
  2. Inicie un hilo.
  3. Abrir diálogo desde hilo -> trabajar.
  4. Haga clic en "Botón Atrás" (se llamará a onCreate y se eliminará la primera actividad principal)
  5. Se iniciará una nueva MainActivity. (y guarde su instancia en globales)
  6. Abrir diálogo desde el primer hilo -> se abrirá y funcionará.

:)

Kazuhiko Nakayama
fuente
4
Nunca guarde una referencia estática a una actividad.
Causará