"Android.view.WindowManager $ BadTokenException: impossibile aggiungere la finestra" su buider.show ()


119

Dal mio main activity, devo chiamare una classe interna e in un metodo all'interno della classe, devo mostrare AlertDialog. Dopo averlo chiuso, quando viene premuto il pulsante OK, inoltra a Google Play per l'acquisto.

Le cose funzionano perfettamente per la maggior parte delle volte, ma per pochi utenti si blocca builder.show()e posso vedere " "android.view.WindowManager$BadTokenException:Impossibile aggiungere la finestra" dal registro di arresto anomalo.

Il mio codice è più o meno così:

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

Ho anche visto l'errore in un altro avviso in cui non sto inoltrando a nessun altro activity. È semplice così:

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

2
Se questo è il tuo codice completo, hai davvero bisogno di AsyncTask?
Shobhit Puri

Questo non è il codice completo, questo è un codice abbastanza grande, quindi ho aggiunto solo la parte qui in cui vedo il problema dal rapporto di arresto anomalo
MSIslam

buono ok. Di solito puoi semplicemente pubblicare il nome della funzione e commentare che stai facendo un sacco di cose lì (come hai fatto ora). È più facile da capire. :).
Shobhit Puri

Stai navigando verso qualche altra attività da questa attività da qualche parte?
Shobhit Puri

1
Hai scritto è commenti //send to some other activity. Penso che se commenterai la parte in cui stai andando a una nuova attività, questo errore scomparirà. L'errore sembra accadere perché la tua finestra di dialogo prima viene completamente chiusa, la tua nuova attività inizia. In onPostExecute(), hai la finestra di dialogo di avviso e stai fornendo il contesto logindell'attività. Ma stai navigando verso l'altra attività, quindi il contesto diventa sbagliato. Quindi stai ricevendo questo errore! Vedi stackoverflow.com/questions/15104677/… domanda simile.
Shobhit Puri

Risposte:


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

Problema:

Questa eccezione si verifica quando l'app tenta di inviare una notifica all'utente dal thread in background (AsyncTask) aprendo una finestra di dialogo.

Se stai tentando di modificare l'interfaccia utente dal thread in background (di solito da onPostExecute () di AsyncTask) e se l'attività entra nella fase di completamento, ad esempio) chiamando esplicitamente finish (), l'utente che preme il pulsante home o indietro o la pulizia dell'attività eseguita da Android, allora tu ottenere questo errore.

Motivo :

Il motivo di questa eccezione è che, come dice il messaggio di eccezione, l'attività è terminata ma si sta tentando di visualizzare una finestra di dialogo con un contesto dell'attività finita. Poiché non esiste una finestra per la visualizzazione della finestra di dialogo, il runtime Android genera questa eccezione.

Soluzione:

Usa il isFinishing()metodo chiamato da Android per verificare se questa attività è in fase di completamento: che si tratti di una chiamata esplicita finish () o di una pulizia dell'attività eseguita da Android. Utilizzando questo metodo è molto facile evitare di aprire la finestra di dialogo dal thread in background al termine dell'attività.

Mantieni anche un weak referenceper l'attività (e non un riferimento forte in modo che l'attività possa essere distrutta una volta non necessaria) e controlla se l'attività non sta finendo prima di eseguire qualsiasi interfaccia utente utilizzando questo riferimento di attività (cioè mostrando una finestra di dialogo).

ad es .

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

Aggiornare :

Gettoni finestra:

Come suggerisce il nome, un token finestra è un tipo speciale di token Binder che il gestore delle finestre utilizza per identificare in modo univoco una finestra nel sistema. I token delle finestre sono importanti per la sicurezza perché impediscono alle applicazioni dannose di disegnare sopra le finestre di altre applicazioni. Il window manager protegge da ciò richiedendo alle applicazioni di passare il token della finestra della loro applicazione come parte di ogni richiesta di aggiungere o rimuovere una finestra. Se i token non corrispondono, il window manager rifiuta la richiesta e genera un'eccezione BadTokenException . Senza i token di finestra, questo passaggio di identificazione necessario non sarebbe possibile e il gestore di finestre non sarebbe in grado di proteggersi da applicazioni dannose.

 Uno scenario del mondo reale:

Quando un'applicazione viene avviata per la prima volta,  ActivityManagerService  crea un tipo speciale di token finestra denominato token finestra dell'applicazione, che identifica in modo univoco la finestra contenitore di primo livello dell'applicazione. Il gestore delle attività fornisce questo token sia all'applicazione che al gestore delle finestre e l'applicazione invia il token al gestore delle finestre ogni volta che desidera aggiungere una nuova finestra allo schermo. Ciò garantisce un'interazione sicura tra l'applicazione e il gestore delle finestre (rendendo impossibile aggiungere finestre sopra altre applicazioni) e rende anche facile per il gestore delle attività effettuare richieste dirette al gestore delle finestre.


Questo ha molto senso! Anche la tua soluzione suona alla grande per me. (y)
MSIslam

Messaggio "Il campo finale vuoto loginActivityWeakRef potrebbe non essere stato inizializzato" e provato in questo modo: private final WeakReference <login> loginActivityWeakRef = new WeakReference <login> (login.this); non sono sicuro che sia la cosa giusta da fare
MSIslam

Inoltre ho rimosso la finale prima di WeakReference <login> loginActivityWeakRef poiché mostrava un errore nel costruttore.
MSIslam

1
prova a usare new chkCubscription (this) .execute (""); invece di new chkCubscription.execute (""); come hai postato sopra.
Ritesh Gune

2
Terribile errore !! Sto seguendo un tutorial e come @PhilRoggenbuck, il mio problema è stato causato dalla chiamata a Toast..Show () appena prima di chiamare StartActivity (...). Per risolverlo, ho spostato invece il toast nell'attività appena chiamata!
Thierry

26

Avevo una finestra di dialogo che mostrava la funzione:

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

Ho ricevuto questo errore e ho dovuto controllare isFinishing()prima di chiamare questa funzione di visualizzazione della finestra di dialogo.

if(!isFinishing())
    showDialog();

1
non dovremmo scrivere if(!MyActivity.this.isFinishing())? Se non è corretto in MyActivity
Bibaswann Bandyopadhyay

2
Perché Android dovrebbe eseguire qualsiasi codice se sta già terminando? Se seguiamo questa soluzione, immagina quanto tempo dovremmo davvero usare isFinishing per evitare problemi simili.
David

@David Penso che manchino alcuni dettagli, come la finestra di dialogo che viene richiamata in un thread in background, ma sono completamente d'accordo con il tuo punto come è ora.
Carrello abbandonato

Ottimo punto, perché diamine dovrei controllare per isFinishing!
Chibueze Opata il

9

Il motivo possibile è il contesto della finestra di dialogo di avviso. Potresti aver terminato l'attività, quindi sta cercando di aprirsi in quel contesto ma che è già chiuso. Prova a cambiare il contesto di quella finestra di dialogo con la tua prima attività perché non sarà finita fino alla fine.

per esempio

piuttosto che questo.

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

prova ad usare

AlertDialog alertDialog = new AlertDialog.Builder(FirstActivity.getInstance()).create();

3
  • per prima cosa non puoi estendere AsyncTask senza sovrascrivere doInBackground
  • secondo prova a creare AlterDailog dal builder, quindi chiama 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();
    }

1
Grazie per la risposta. In realtà ho usato il metodo doInBackground, ma non l'ho menzionato qui perché non è correlato all'avviso. Per quanto riguarda l'aggiunta di builder.create (), sembra funzionare bene, ma non so se funzionerà bene per tutti. Come ho detto prima, anche il mio codice attuale funziona bene, ma solo poche volte per pochi utenti mostra l'impossibilità di aggiungere problemi di finestra. Potresti suggerirmi quale potrebbe essere il problema reale nella mia codifica che cosa potrebbe causare questo?
MSIslam

in questo caso l'utente esce dalla tua attività prima che onPostExecute venga chiamato, quindi non c'è una finestra per contenere la finestra di dialogo, e questo causa il crash dell'applicazione. aggiungi flag a onStop per sapere se la tua attività non è più visibile, quindi non mostrare la finestra di dialogo.
moh.sukhni

onPostExecute viene effettivamente chiamato, poiché builder.show () è sottoposto a una condizione quando controllo se l'utente non è iscritto in base al risultato della chiamata al servizio web da doInBackground (). Quindi, se onPostExecute non fosse stato chiamato, non sarebbe arrivato alla riga builder.show ().
MSIslam

onPostExecute viene chiamato per impostazione predefinita dopo doInBackground, non puoi chiamarlo e qualsiasi cosa verrà eseguita.
moh.sukhni

1
la tua attività asincrona continuerà a funzionare dopo che l'utente è uscito dalla tua attività e questo farà sì che builder.show () craching la tua applicazione perché non ci sono attività per gestire l'interfaccia utente per te. quindi la tua app sta estraendo dati dal web ma la tua attività è stata distrutta prima che tu ottenga i dati.
moh.sukhni

1

Sto creando Dialog in onCreatee lo utilizzo con showe hide. Per me la causa principale non era il licenziamento onBackPressed, ovvero il completamento Homedell'attività.

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

Stavo finendo l'attività Home onBackPressedsenza chiudere / chiudere le mie finestre di dialogo.

Quando ho chiuso i miei dialoghi, il crash è scomparso.

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

0

Provo a risolverlo.

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

-1

Prova questo :

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

}
}

-4

con questa idea delle variabili globali, ho salvato l'istanza MainActivity in onCreate (); Variabile globale Android

public class ApplicationController extends Application {

    public static MainActivity this_MainActivity;
}

e Apri finestra di dialogo in questo modo. ha funzionato.

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

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

e in un thread, apro una finestra di dialogo come questa.

AlertDialog.Builder alert = new AlertDialog.Builder(globals.this_MainActivity);
  1. Apri MainActivity
  2. Inizia un thread.
  3. Apri finestra di dialogo dal thread -> lavoro.
  4. Fai clic sul pulsante "Indietro" (onCreate verrà chiamato e rimuoverà il primo MainActivity)
  5. Inizierà la nuova MainActivity. (e salva la sua istanza nelle globali)
  6. Apri la finestra di dialogo dal primo thread -> si aprirà e funzionerà.

:)


4
Non mantenere mai un riferimento statico a un'attività. Causerà una perdita di memoria
Causerà una Leandroid
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.