«Android.view.WindowManager $ BadTokenException: невозможно добавить окно» в buider.show ()


120

Из основного activityмне нужно вызвать внутренний класс и метод внутри класса, который мне нужно показать AlertDialog. После его закрытия и нажатия кнопки ОК переадресовать в Google Play для покупки.

В большинстве случаев все работает идеально, но у некоторых пользователей происходит сбой, builder.show()и я вижу « "android.view.WindowManager$BadTokenException:Невозможно добавить окно» из журнала сбоев. Пожалуйста, предложите.

Мой код примерно такой:

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

Я также видел ошибку в другом предупреждении, где я не пересылаю никому activity. Это просто так:

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
Если это ваш полный код, действительно ли вам нужен AsyncTask?
Shobhit Puri

Это не полный код, это довольно большой код, поэтому я добавил сюда только ту часть, где я вижу проблему из отчета о
сбоях

ладно, хорошо. Обычно вы можете просто опубликовать имя функции и комментарий, что вы делаете там кучу вещей (как вы это сделали сейчас). Это легче понять. :).
Shobhit Puri

Вы где-то переходите к другому занятию из этого занятия?
Shobhit Puri

1
Вы написали это комментарии //send to some other activity. Я думаю, что если вы прокомментируете часть, в которой собираетесь перейти к новому Activity, эта ошибка исчезнет. Кажется, что ошибка возникает из-за того, что ваш предыдущий диалог полностью закрывается, начинается ваше новое действие. В onPostExecute(), у вас есть диалоговое окно с предупреждением, и вы даете контекст loginActivity. Но вы переходите к другому действию, поэтому контекст становится неверным. Следовательно, вы получаете эту ошибку! См. Stackoverflow.com/questions/15104677/… аналогичный вопрос.
Shobhit Puri

Ответы:


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

Проблема:

Это исключение возникает, когда приложение пытается уведомить пользователя из фонового потока (AsyncTask), открывая Dialog.

Если вы пытаетесь изменить пользовательский интерфейс из фонового потока (обычно из onPostExecute () из AsyncTask), и если действие переходит на этап завершения, то есть) явно вызывает finish (), пользователь нажимает кнопку домой или назад или очищает действие, выполненное Android, тогда вы получить эту ошибку.

Причина:

Причина этого исключения в том, что, как говорится в сообщении об исключении, действие завершено, но вы пытаетесь отобразить диалог с контекстом завершенного действия. Поскольку в диалоговом окне нет окна для отображения среды выполнения Android, возникает это исключение.

Решение:

Используйте isFinishing()метод, который вызывается Android, чтобы проверить, находится ли это действие в процессе завершения: будь то явный вызов finish () или очистка действия, выполненная Android. Используя этот метод, очень легко избежать открытия диалогового окна из фонового потока, когда действие завершается.

Также сохраните a weak referenceдля действия (а не сильную ссылку, чтобы действие можно было уничтожить, когда оно не понадобится) и проверьте, не завершается ли действие, прежде чем выполнять какой-либо пользовательский интерфейс с использованием этой ссылки на действие (т.

напр .

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

Обновить :

Жетоны окон:

Как следует из названия, токен окна - это особый тип токена Binder, который диспетчер окон использует для уникальной идентификации окна в системе. Маркеры окон важны для безопасности, потому что они не позволяют вредоносным приложениям рисовать поверх окон других приложений. Диспетчер окон защищает от этого, требуя, чтобы приложения передавали токен окна своего приложения как часть каждого запроса на добавление или удаление окна. Если токены не совпадают, оконный менеджер отклоняет запрос и выдает исключение BadTokenException . Без оконных токенов этот необходимый шаг идентификации был бы невозможен, и оконный менеджер не смог бы защитить себя от вредоносных приложений.

 Реальный сценарий:

Когда приложение запускается в первый раз,  ActivityManagerService  создает специальный вид токена окна, называемый токеном окна приложения, который однозначно идентифицирует окно контейнера верхнего уровня приложения. Диспетчер операций передает этот токен как приложению, так и диспетчеру окон, и приложение отправляет токен диспетчеру окон каждый раз, когда оно хочет добавить новое окно на экран. Это обеспечивает безопасное взаимодействие между приложением и диспетчером окон (делая невозможным добавление окон поверх других приложений), а также упрощает диспетчеру действий возможность отправлять прямые запросы диспетчеру окон.


В этом есть большой смысл! Также ваше решение мне нравится. (y)
MSIslam

Сообщение «Пустое конечное поле loginActivityWeakRef, возможно, не было инициализировано», которое было предпринято следующим образом: private final WeakReference <login> loginActivityWeakRef = new WeakReference <login> (login.this); не уверен, что это правильно
MSIslam

Также я удалил последний перед WeakReference <login> loginActivityWeakRef, поскольку он показывал ошибку в конструкторе.
MSIslam

1
попробуйте использовать новую подписку chkCubscription (this) .execute (""); вместо нового chkCubscription.execute (""); как вы писали выше.
Ritesh Gune

2
Ужасная ошибка !! Я следую руководству, и как @PhilRoggenbuck, моя проблема была вызвана вызовом Toast..Show () непосредственно перед вызовом StartActivity (...). Чтобы исправить это, я вместо этого переместил тост во вновь вызванное действие!
Тьерри

27

У меня был диалог, показывающий функцию:

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

Я получал эту ошибку, и мне просто нужно было проверить, isFinishing()прежде чем вызывать это диалоговое окно, показывающее функцию.

if(!isFinishing())
    showDialog();

1
нельзя писать if(!MyActivity.this.isFinishing())? Если это неправильно в MyActivity
Бибасванн Бандйопадхай

2
Зачем Android запускать какой-либо код, если он уже завершается? Если мы последуем этому решению, то представьте, сколько раз нам действительно нужно использовать isFinishing, чтобы избежать подобных проблем.
Дэвид

@David Я думаю, что здесь не хватает некоторых деталей, таких как диалог, вызываемый в фоновом потоке, но я полностью согласен с вашей точкой зрения, как сейчас.
Брошенная тележка

Отличный момент, какого черта мне нужно проверять на isFinishing!
Chibueze Opata,

9

Возможная причина - контекст диалогового окна предупреждения. Возможно, вы закончили это действие, поэтому оно пытается открыться в этом контексте, но оно уже закрыто. Попробуйте изменить контекст этого диалога на свое первое действие, потому что оно не будет завершено до конца.

например

а не это.

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

попробуй использовать

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

3
  • сначала вы не можете расширить AsyncTask без переопределения doInBackground
  • во-вторых, попробуйте создать AlterDailog из построителя, затем вызовите 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
Спасибо за ответ. Я действительно использовал метод doInBackground, просто не упомянул об этом здесь, поскольку он не имеет отношения к предупреждению. Что касается добавления builder.create (), похоже, он работает нормально, но не знаю, подойдет ли он для всех. Как я уже сказал ранее, мой текущий код также работает нормально, но только несколько раз для нескольких пользователей он показывает, что невозможно добавить окно. Не могли бы вы подсказать мне, в чем может быть реальная проблема в моем кодировании, что может вызвать это?
MSIslam

в этом случае пользователь завершает вашу деятельность до вызова onPostExecute, поэтому нет окна для хранения диалога, и это приводит к сбою вашего приложения. добавьте флаг в onStop, чтобы знать, если ваша активность больше не отображается, а затем не показывать диалог.
moh.sukhni 06

onPostExecute фактически вызывается, поскольку builder.show () находится в состоянии, когда я проверяю, не подписан ли пользователь на основе результата вызова веб-службы из doInBackground (). Так что, если бы onPostExecute не был вызван, он бы не попал в строку builder.show ().
MSIslam

onPostExecute вызывается по умолчанию после doInBackground, вы не можете его вызвать, и все, что там есть, будет выполнено.
moh.sukhni

1
ваша асинхронная задача продолжит работу после того, как пользователь перейдет из вашей активности, и это заставит builder.show () взломать ваше приложение, потому что нет активности для обработки пользовательского интерфейса за вас. Итак, ваше приложение извлекает данные из Интернета, но ваша активность была уничтожена до того, как вы получили данные.
moh.sukhni 06

1

Я создаю диалог onCreateи использую его вместе с showи hide. Для меня основной причиной было не увольнение onBackPressed, а завершение Homeдеятельности.

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

Я заканчивал домашнее задание, 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) {
                                networkErrorDialog.dismiss() ;
                                homeLocationErrorDialog.dismiss() ;
                                currentLocationErrorDialog.dismiss() ;
                                Home.this.finish();
                                return;
                            }
                        }).create().show();

0

Я пытаюсь это решить.

 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

Попробуй это :

    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

с этой идеей глобальных переменных я сохранил экземпляр MainActivity в onCreate (); Глобальная переменная Android

public class ApplicationController extends Application {

    public static MainActivity this_MainActivity;
}

и откройте диалог, подобный этому. это сработало.

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

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

и в ветке я открываю такой диалог.

AlertDialog.Builder alert = new AlertDialog.Builder(globals.this_MainActivity);
  1. Открыть MainActivity
  2. Начать обсуждение.
  3. Открыть диалог из потока -> работа.
  4. Нажмите кнопку «Назад» (будет вызвана функция onCreate и первая MainActivity удалится)
  5. Запустится новая MainActivity. (и сохраните его в глобальных объектах)
  6. Открыть диалог из первого потока -> он откроется и будет работать.

:)


4
Никогда не храните статическую ссылку на Activity. Это вызовет утечку памяти
Leandroid 03
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.