„Android.view.WindowManager $ BadTokenException: nie można dodać okna” w buider.show ()

118

Z mojego głównego activity, muszę wywołać klasę wewnętrzną, aw metodzie wewnątrz klasy muszę pokazać AlertDialog. Po zamknięciu, po naciśnięciu przycisku OK, przejdź do Google Play w celu zakupu.

Przez większość czasu wszystko działa idealnie, ale w przypadku kilku użytkowników dochodzi do awarii builder.show()i widzę "android.view.WindowManager$BadTokenException:Nie można dodać okna z dziennika awarii. Zasugeruj.

Mój kod jest mniej więcej taki:

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

Widziałem również błąd w innym alercie, w którym nie przekazuję wiadomości do żadnego innego activity. To jest takie proste:

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
źródło
2
Jeśli to jest twój pełny kod, czy naprawdę potrzebujesz AsyncTask?
Shobhit Puri,
To nie jest cały kod, to jest dość duży kod, więc dodałem tylko część tutaj, w której widzę problem z raportu o awarii
MSIslam
dobrze. Zwykle możesz po prostu opublikować nazwę funkcji i skomentować, że robisz tam wiele rzeczy (tak jak teraz). Łatwiej to zrozumieć. :).
Shobhit Puri,
Czy przechodzisz gdzieś do innego działania z tego działania?
Shobhit Puri,
1
Napisałeś to komentarze //send to some other activity. Myślę, że jeśli skomentujesz część, w której idziesz do nowego działania, ten błąd zniknie. Wydaje się, że błąd występuje, ponieważ okno dialogowe sprzed zostało całkowicie zamknięte i rozpoczyna się nowa aktywność. W onPostExecute()oknie masz okno dialogowe z ostrzeżeniem i podajesz kontekst logindziałania. Ale przechodzisz do innej czynności, więc kontekst staje się zły. Dlatego otrzymujesz ten błąd! Zobacz stackoverflow.com/questions/15104677/ ... podobne pytanie.
Shobhit Puri,

Odpowiedzi:

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

Problem:

Ten wyjątek występuje, gdy aplikacja próbuje powiadomić użytkownika z wątku w tle (AsyncTask), otwierając okno dialogowe.

Jeśli próbujesz zmodyfikować interfejs użytkownika z wątku w tle (zwykle z onPostExecute () AsyncTask) i jeśli czynność przechodzi do etapu kończenia, tj. Jawnie wywołuje funkcję finish (), naciśnięcie przez użytkownika przycisku home lub wstecz lub czyszczenie działania wykonane przez Androida, uzyskać ten błąd.

Powód:

Powodem tego wyjątku jest to, że zgodnie z komunikatem o wyjątku działanie zostało zakończone, ale próbujesz wyświetlić okno dialogowe z kontekstem zakończonego działania. Ponieważ nie ma okna dialogowego wyświetlającego środowisko wykonawcze systemu Android, zgłasza ten wyjątek.

Rozwiązanie:

Użyj isFinishing()metody, która jest wywoływana przez Androida, aby sprawdzić, czy ta aktywność jest w trakcie kończenia: czy to jawne wywołanie finish (), czy też czyszczenie aktywności wykonane przez Androida. Używając tej metody, bardzo łatwo jest uniknąć otwierania okna dialogowego z wątku w tle po zakończeniu czynności.

Zachowaj również weak referencedla działania (a nie silne odniesienie, aby działanie można było zniszczyć, gdy nie będzie potrzebne) i sprawdź, czy działanie nie kończy się przed wykonaniem jakiegokolwiek interfejsu użytkownika przy użyciu tego odniesienia do działania (tj. Pokazując okno dialogowe).

np .

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

Aktualizacja :

Żetony okien:

Jak sama nazwa wskazuje, token okna jest specjalnym typem tokenu Binder, którego menedżer okien używa do jednoznacznej identyfikacji okna w systemie. Tokeny okien są ważne dla bezpieczeństwa, ponieważ uniemożliwiają złośliwym aplikacjom korzystanie z okien innych aplikacji. Menedżer okien chroni przed tym, wymagając od aplikacji, aby przekazywały token okna swojej aplikacji w ramach każdego żądania dodania lub usunięcia okna. Jeśli tokeny nie pasują, menedżer okien odrzuca żądanie i zgłasza BadTokenException . Bez tokenów okien ten niezbędny krok identyfikacji nie byłby możliwy, a menedżer okien nie byłby w stanie chronić się przed złośliwymi aplikacjami.

 Scenariusz ze świata rzeczywistego:

Gdy aplikacja jest uruchamiana po raz pierwszy, usługa  ActivityManagerService  tworzy specjalny rodzaj tokenu okna nazywanego tokenem okna aplikacji, który jednoznacznie identyfikuje okno kontenera najwyższego poziomu aplikacji. Menedżer aktywności przekazuje ten token zarówno aplikacji, jak i menedżerowi okien, a aplikacja wysyła token do menedżera okien za każdym razem, gdy chce dodać nowe okno na ekran. Zapewnia to bezpieczną interakcję między aplikacją a menedżerem okien (uniemożliwiając dodawanie okien na innych aplikacjach), a także ułatwia menedżerowi aktywności bezpośrednie wysyłanie żądań do menedżera okien.

Ritesh Gune
źródło
To ma sens! Również twoje rozwiązanie brzmi dla mnie świetnie. (y)
MSIslam
Komunikat „Puste końcowe pole loginActivityWeakRef mogło nie zostać zainicjowane” i wypróbowano w następujący sposób: prywatna końcowa WeakReference <login> loginActivityWeakRef = new WeakReference <login> (login.this); nie jestem pewien, czy jest to słuszne
MSIslam
Usunąłem również ostatnią wersję przed WeakReference <login> loginActivityWeakRef, ponieważ pokazywała błąd w konstruktorze.
MSIslam
1
spróbuj użyć nowego chkCubscription (this) .execute (""); zamiast nowego chkCubscription.execute (""); jak napisałeś powyżej.
Ritesh Gune
2
Okropny błąd !! Postępuję zgodnie z samouczkiem i jako @PhilRoggenbuck, mój problem był spowodowany wywołaniem Toast..Show () tuż przed wywołaniem StartActivity (...). Aby to naprawić, zamiast tego przeniosłem toast w nowo wywołanej czynności!
Thierry,
26

Miałem okno dialogowe pokazujące funkcję:

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

Otrzymałem ten błąd i po prostu musiałem sprawdzić isFinishing()przed wywołaniem tego okna dialogowego pokazującego funkcję.

if(!isFinishing())
    showDialog();
Jemshit Iskenderov
źródło
1
nie powinniśmy pisać if(!MyActivity.this.isFinishing())? Jeśli to nie w porządku w MyActivity
Bibaswann Bandyopadhyay
2
Dlaczego Android miałby uruchamiać dowolny kod, skoro już się kończy? Jeśli zastosujemy to rozwiązanie, wyobraź sobie, ile czasu naprawdę powinniśmy wykorzystać isFinishing, aby uniknąć podobnych problemów.
David
@David Myślę, że brakuje w tym kilku szczegółów, takich jak wywołanie okna dialogowego w wątku w tle, ale całkowicie zgadzam się z twoim punktem widzenia, tak jak jest teraz.
Opuszczony
Świetna uwaga, dlaczego do cholery miałbym sprawdzić, czy jest zakończone!
Chibueze Opata
9

Możliwym powodem jest kontekst okna dialogowego ostrzeżenia. Możesz skończyć tę czynność, więc próbuje ona otworzyć się w tym kontekście, ale która jest już zamknięta. Spróbuj zmienić kontekst tego okna dialogowego na pierwszą czynność, ponieważ nie zostanie ona zakończona do końca.

na przykład

zamiast tego.

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

Spróbuj użyć

AlertDialog alertDialog = new AlertDialog.Builder(FirstActivity.getInstance()).create();
Raghavendra
źródło
3
  • po pierwsze nie można rozszerzyć AsyncTask bez nadpisania doInBackground
  • Następnie spróbuj utworzyć AlterDailog z poziomu kreatora, a następnie wywołaj 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
źródło
1
Dziękuję za odpowiedź. Właściwie użyłem metody doInBackground, po prostu nie wspomniałem o tym tutaj, ponieważ nie jest to związane z alertem. Jeśli chodzi o dodanie builder.create (), wygląda na to, że działa dobrze, ale nie wiem, czy będzie działać dobrze dla wszystkich. Jak powiedziałem wcześniej, mój obecny kod również działa dobrze, ale tylko kilka razy dla kilku użytkowników pokazuje, że nie można dodać problemu z oknem. Czy mógłbyś zasugerować mi, jaki może być rzeczywisty problem w moim kodowaniu, co może być przyczyną tego?
MSIslam
w tym przypadku użytkownik kończy aktywność przed wywołaniem onPostExecute, więc nie ma okna do zatrzymania okna dialogowego, co powoduje awarię aplikacji. dodaj flagę do onStop, aby wiedzieć, czy Twoja aktywność nie jest już widoczna, a następnie nie pokazuj okna dialogowego.
moh.sukhni
W rzeczywistości onPostExecute jest wywoływany, ponieważ builder.show () jest pod warunkiem, gdy sprawdzam, czy użytkownik nie jest subskrybowany na podstawie wywołania usługi sieciowej z doInBackground (). Więc jeśli onPostExecute nie zostałby wywołany, nie doszłoby do linii builder.show ().
MSIslam
onPostExecute jest wywoływany domyślnie po doInBackground, nie możesz tego wywołać i cokolwiek tam będzie, zostanie wykonane.
moh.sukhni
1
Twoje zadanie asynchroniczne będzie kontynuowane po tym, jak użytkownik przejdzie z Twojej aktywności, co spowoduje, że builder.show () zawiesi Twoją aplikację, ponieważ nie ma aktywności obsługującej interfejs użytkownika za Ciebie. więc Twoja aplikacja pobiera dane z internetu, ale Twoja aktywność została zniszczona, zanim je otrzymałeś.
moh.sukhni,
1

Tworzę Dialog in onCreatei używam go z showi hide. Dla mnie przyczyną nie było zwolnienie onBackPressed, czyli zakończenie Homedziałalności.

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

Kończyłem Aktywność domową onBackPressedbez zamykania / zamykania okien dialogowych.

Kiedy zamknąłem okna dialogowe, awaria zniknęła.

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
źródło
0

Próbuję to rozwiązało.

 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
źródło
-1

Spróbuj tego :

    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
źródło
-4

z pomysłem na zmienne globalne zapisałem wystąpienie MainActivity w onCreate (); Zmienna globalna Androida

public class ApplicationController extends Application {

    public static MainActivity this_MainActivity;
}

i otwórz okno dialogowe w ten sposób. zadziałało.

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

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

aw wątku otwieram takie okno dialogowe.

AlertDialog.Builder alert = new AlertDialog.Builder(globals.this_MainActivity);
  1. Otwórz MainActivity
  2. Rozpocznij wątek.
  3. Otwórz okno dialogowe z wątku -> praca.
  4. Kliknij przycisk „Wstecz” (zostanie wywołany onCreate i usunie pierwszą MainActivity)
  5. Rozpocznie się nowa MainActivity. (i zapisz jego instancję w plikach globalnych)
  6. Otwórz okno dialogowe z pierwszego wątku -> otworzy się i zadziała.

:)

Kazuhiko Nakayama
źródło
4
Nigdy nie zachowuj statycznego odniesienia do działania. Spowoduje to wyciek pamięci
Leandroid