“Android.view.WindowManager $ BadTokenException: Não é possível adicionar janela” em buider.show ()

118

Do meu principal activity, preciso chamar uma classe interna e, em um método dentro da classe, preciso mostrar AlertDialog. Depois de dispensá-lo, quando o botão OK for pressionado, encaminhe para o Google Play para compra.

As coisas funcionam perfeitamente na maioria das vezes, mas para poucos usuários está travando builder.show()e posso ver "android.view.WindowManager$BadTokenException:Não é possível adicionar janela "no log de travamento. Por favor, sugira.

Meu código é mais ou menos assim:

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

Também vi o erro em outro alerta onde não estou encaminhando para nenhum outro activity. É simples assim:

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
fonte
2
Se este for o seu código completo, você realmente precisa do AsyncTask?
Shobhit Puri
Este não é o código completo, é um código bem grande, então eu apenas adicionei a parte aqui onde estou vendo o problema do relatório de falha
MSIslam
ok bom. Normalmente, você pode simplesmente postar o nome da função e comentar que está fazendo um monte de coisas lá (como fez agora). É mais fácil de entender. :).
Shobhit Puri
Você está navegando para alguma outra atividade desta atividade em algum lugar?
Shobhit Puri
1
Você escreveu comentários //send to some other activity. Acho que se você comentar a parte em que está indo para uma nova Activity, esse erro vai embora. O erro parece ocorrer porque sua caixa de diálogo antes de ser completamente descartada, sua nova atividade será iniciada. No onPostExecute(), você tem a caixa de diálogo de alerta e está fornecendo o contexto da loginatividade. Mas você está navegando para outra atividade, então o contexto fica errado. Portanto, você está recebendo este erro! Consulte stackoverflow.com/questions/15104677/… pergunta semelhante.
Shobhit Puri

Respostas:

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

Problema:

Essa exceção ocorre quando o aplicativo está tentando notificar o usuário do thread de segundo plano (AsyncTask) abrindo uma caixa de diálogo.

Se você estiver tentando modificar a IU a partir do thread de segundo plano (geralmente de onPostExecute () de AsyncTask) e se a atividade entrar no estágio de finalização, ou seja, chamar explicitamente finish (), usuário pressionando o botão home ou voltar ou limpeza de atividade feita pelo Android, então você obter este erro.

Razão:

O motivo dessa exceção é que, como diz a mensagem de exceção, a atividade foi concluída, mas você está tentando exibir uma caixa de diálogo com um contexto da atividade concluída. Como não há janela para a caixa de diálogo exibir o tempo de execução do Android, essa exceção é lançada.

Solução:

Use o isFinishing()método que é chamado pelo Android para verificar se esta atividade está em processo de finalização: seja uma chamada final explícita () ou limpeza de atividade feita pelo Android. Usando este método, é muito fácil evitar a abertura da caixa de diálogo do thread de fundo quando a atividade está terminando.

Além disso, mantenha um weak referencepara a atividade (e não uma referência forte para que a atividade possa ser destruída quando não for necessária) e verifique se a atividade não está terminando antes de executar qualquer IU usando esta referência de atividade (ou seja, mostrando uma caixa de diálogo).

por exemplo .

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

Atualização:

Tokens de janela:

Como o próprio nome indica, um token de janela é um tipo especial de token Binder que o gerenciador de janelas usa para identificar exclusivamente uma janela no sistema. Os tokens de janela são importantes para a segurança porque tornam impossível que aplicativos maliciosos sejam colocados sobre as janelas de outros aplicativos. O gerenciador de janelas protege contra isso exigindo que os aplicativos passem o token de janela de seu aplicativo como parte de cada solicitação para adicionar ou remover uma janela. Se os tokens não corresponderem, o gerenciador de janelas rejeita a solicitação e lança uma BadTokenException . Sem os tokens de janela, essa etapa de identificação necessária não seria possível e o gerenciador de janelas não seria capaz de se proteger de aplicativos maliciosos.

 Um cenário do mundo real:

Quando um aplicativo é inicializado pela primeira vez, o  ActivityManagerService  cria um tipo especial de token de janela chamado token de janela de aplicativo, que identifica exclusivamente a janela de contêiner de nível superior do aplicativo. O gerenciador de atividades fornece esse token para o aplicativo e o gerenciador de janelas, e o aplicativo envia o token para o gerenciador de janelas cada vez que deseja adicionar uma nova janela à tela. Isso garante uma interação segura entre o aplicativo e o gerenciador de janelas (tornando impossível adicionar janelas em cima de outros aplicativos) e também torna mais fácil para o gerenciador de atividades fazer solicitações diretas ao gerenciador de janelas.

Ritesh Gune
fonte
Isso faz muito sentido! Além disso, sua solução parece ótima para mim. (y)
MSIslam
'O campo final em branco loginActivityWeakRef pode não ter sido inicializado' mensagem e tentada desta forma: private final WeakReference <login> loginActivityWeakRef = new WeakReference <login> (login.this); não tenho certeza se é a coisa certa a fazer
MSIslam
Também removi o final antes de WeakReference <login> loginActivityWeakRef, pois estava mostrando um erro no construtor.
MSIslam
1
tente usar novo chkCubscription (this) .execute (""); em vez de novo chkCubscription.execute (""); como você postou acima.
Ritesh Gune
2
Erro terrível !! Estou seguindo um tutorial e como @PhilRoggenbuck, meu problema foi causado ao chamar um Toast..Show () antes de chamar StartActivity (...). Para consertar, mudei a torrada para a atividade recém-chamada!
Thierry
26

Tive uma caixa de diálogo mostrando a função:

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

Eu estava recebendo este erro e só tive que verificar isFinishing()antes de chamar esta caixa de diálogo que mostra a função.

if(!isFinishing())
    showDialog();
Jemshit Iskenderov
fonte
1
não deveríamos escrever if(!MyActivity.this.isFinishing())? Se não estiver certo em MyActivity
Bibaswann Bandyopadhyay
2
Por que o Android executaria qualquer código se já está sendo concluído? Se seguirmos essa solução, imagine quanto tempo deveríamos realmente usar isFinishing para evitar problemas semelhantes.
David,
@David Acho que faltam alguns detalhes, como a caixa de diálogo sendo chamada em um thread de segundo plano, mas concordo totalmente com seu ponto como está agora.
Carrinho abandonado
Ótimo ponto, por que diabos eu precisaria verificar se há isFinishing!
Chibueze Opata
9

O possível motivo é o contexto da caixa de diálogo de alerta. Você pode ter terminado aquela atividade então ela está tentando abrir naquele contexto, mas que já está fechado. Tente mudar o contexto desse diálogo para a sua primeira atividade, porque ele não será concluído até o final.

por exemplo

em vez disso.

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

tente usar

AlertDialog alertDialog = new AlertDialog.Builder(FirstActivity.getInstance()).create();
Raghavendra
fonte
3
  • primeiro você não pode estender AsyncTask sem substituir doInBackground
  • em seguida, tente criar AlterDailog a partir do construtor e, em seguida, chame 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
fonte
1
Obrigado pela resposta. Na verdade, usei o método doInBackground, mas não o mencionei aqui, pois não está relacionado ao alerta. Em relação a adicionar o builder.create (), parece funcionar bem, mas não sei se funcionará bem para todos. Como eu disse antes, meu código atual também funciona bem, mas apenas algumas vezes para poucos usuários mostra o problema de não ser possível adicionar janela. Você poderia me sugerir qual poderia ser o problema real na minha codificação, o que pode causar isso?
MSIslam
neste caso, o usuário sai de sua atividade antes que onPostExecute seja chamado, portanto, não há janela para manter a caixa de diálogo e isso faz com que seu aplicativo trave. adicione sinalizador a onStop para saber se sua atividade não está mais visível e não mostre a caixa de diálogo.
moh.sukhni
onPostExecute na verdade é chamado, pois builder.show () está sob uma condição quando estou verificando se o usuário não está inscrito com base no resultado da chamada de serviço da web de doInBackground (). Portanto, se onPostExecute não fosse chamado, ele não teria chegado à linha builder.show ().
MSIslam
onPostExecute é chamado por padrão após doInBackground, você não pode chamá-lo e o que quer que seja será executado.
moh.sukhni
1
sua tarefa assíncrona continuará funcionando depois que o usuário sair de sua atividade e isso fará com que builder.show () destrua seu aplicativo porque não há atividade para manipular a IU para você. então seu aplicativo está extraindo dados da web, mas sua atividade foi destruída antes de você obter os dados.
moh.sukhni
1

Estou criando Dialog em onCreatee usando-o com showe hide. Para mim a causa raiz não foi demitir onBackPressed, que foi encerrar a Homeatividade.

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

Eu estava terminando a Atividade doméstica onBackPressedsem fechar / dispensar meus diálogos.

Quando descartei minhas caixas de diálogo, a falha desapareceu.

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
fonte
0

Eu tento resolver isso.

 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
fonte
-1

Experimente isto:

    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
fonte
-4

com essa ideia de variáveis ​​globais, salvei a instância MainActivity em onCreate (); Variável global Android

public class ApplicationController extends Application {

    public static MainActivity this_MainActivity;
}

e abrir diálogo como este. funcionou.

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

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

e em um tópico, eu abro um diálogo como este.

AlertDialog.Builder alert = new AlertDialog.Builder(globals.this_MainActivity);
  1. Abrir MainActivity
  2. Comece um tópico.
  3. Abra a caixa de diálogo do tópico -> trabalho.
  4. Clique no "botão Voltar" (onCreate será chamado e removerá primeiro MainActivity)
  5. Nova MainActivity será iniciada. (e salve sua instância para globais)
  6. Abra a caixa de diálogo do primeiro tópico -> ela abrirá e funcionará.

:)

Kazuhiko Nakayama
fonte
4
Nunca mantenha uma referência estática para uma atividade. Isso causará vazamento de memória
Leandroid,