在buider.show()时出现“android.view.WindowManager$BadTokenException: Unable to add window”错误。

135

在我的主 activity 中,我需要调用一个内部类,并在该类的方法中显示 AlertDialog。在其关闭后,当用户按下“OK”按钮时,跳转到 Google Play 进行购买。

大多数情况下都能正常工作,但对于某些用户来说,在调用 builder.show() 时会出现崩溃,从崩溃日志中可以看到 "android.view.WindowManager$BadTokenException: Unable to add window" 错误提示。请给出建议。

我的代码基本上是这样的:

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
这不是完整的代码,这是相当大的代码,所以我只添加了在崩溃报告中发现问题的部分。 - MSIslam
好的。通常你只需要发布函数名称并注释说明你在那里做了很多事情(正如你现在所做的)。这样更容易理解。 :) - Shobhit Puri
你是从这个活动导航到其他活动吗? - Shobhit Puri
1
你写了这样的注释 //send to some other activity。我认为如果你在去往新的Activity的部分加上注释,这个错误就会消失。这个错误似乎是因为你的对话框在完全关闭之前,新的Activity已经开始了。在 onPostExecute() 中,你有一个警告对话框,并且你正在给出 login Activity 的上下文。但是你正在导航到另一个Activity,所以上下文变得不正确。因此你会得到这个错误!请参考 http://stackoverflow.com/questions/15104677/alertdialog-creating-exception-in-android 相似的问题。 - Shobhit Puri
显示剩余7条评论
11个回答

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

问题:

当应用程序试图通过打开对话框从后台线程(AsyncTask)通知用户时,会出现此异常。

如果您正在尝试从后台线程(通常是Asynctask的onPostExecute()方法)修改UI,并且如果活动进入完成阶段,即明确调用finish()、用户按下home或back按钮或由Android清除活动,则会出现此错误。

原因:

此异常的原因是,正如异常消息所说,该活动已经完成,但您正在尝试显示一个具有已完成活动的上下文的对话框。由于没有窗口来显示对话框,Android运行时会抛出此异常。

解决方案:

使用Android调用的isFinishing()方法来检查此活动是否正在完成:无论是显式finish()调用还是由Android进行的活动清理。使用此方法可以很容易地避免在活动完成时从后台线程中打开对话框。

同时,需要维护对活动的弱引用(而不是强引用,以便在不需要时可以销毁活动),并在执行任何使用此活动引用的UI操作之前检查该活动是否未完成(即显示对话框)。

例如:

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创建了一种特殊类型的窗口令牌, 称为应用程序窗口令牌,它唯一标识应用程序的顶级容器窗口。 活动管理器将此令牌分别提供给应用程序和窗口管理器, 并且每次应用程序想要向屏幕添加新窗口时,应用程序都会将该令牌发送给窗口管理器。 这确保了应用程序与窗口管理器之间的安全交互(通过使其无法在其他应用程序之上添加窗口), 并且还使活动管理器能够直接向窗口管理器发出请求。


登录活动的空白final字段loginActivityWeakRef可能尚未被初始化。我尝试了以下方式:private final WeakReference<login> loginActivityWeakRef = new WeakReference<login>(login.this);不确定是否正确。 - MSIslam
同时,我将WeakReference<login> loginActivityWeakRef中的final关键字删除了,因为它在构造函数中显示错误。 - MSIslam
1
尝试使用新的chkCubscription(this).execute("");而不是您上面发布的new chkCubscription.execute("");。 - Ritesh Gune
1
它也可以是一个Toast。 - phrogg
2
可怕的错误!!我正在跟随教程,就像@PhilRoggenbuck一样,我的问题是由于在调用StartActivity(...)之前调用Toast..Show()引起的。为了解决这个问题,我将Toast移动到新调用的活动中! - Thierry
显示剩余2条评论

31

我有一个显示对话框的函数:

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

我遇到了这个错误,只需要在调用此对话框显示函数之前检查isFinishing()即可。

if(!isFinishing())
    showDialog();

1
我们应该写 if(!MyActivity.this.isFinishing()) 吗?如果在 MyActivity 中不正确的话。 - Bibaswann Bandyopadhyay
2
如果Android已经完成,为什么还要运行任何代码呢?如果我们遵循这个解决方案,那么想象一下我们应该多少次真正使用isFinishing来避免类似的问题。 - David
@David 我认为这里缺少一些细节,比如对话框是在后台线程中调用的,但现在我完全同意你的观点。 - Abandoned Cart
很好的观点,我为什么需要检查isFinishing! - Chibueze Opata

9
可能的原因是警告对话框的上下文。您可能已经完成了该活动,因此它正在尝试在该上下文中打开,但该上下文已关闭。 尝试将该对话框的上下文更改为您的第一个活动,因为它直到最后才会结束。
例如,
而不是这样。
AlertDialog alertDialog = new AlertDialog.Builder(this).create();

尝试使用

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

3
  • first you cannot extend AsyncTask without override doInBackground
  • second try to create AlterDailog from the builder then call 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实际上被调用了,因为builder.show()在一个条件下,我正在检查用户是否已经订阅了基于doInBackground()的Web服务调用结果。因此,如果onPostExecute没有被调用,它就不会到达builder.show()行。 - MSIslam
好的,明白了。在这种情况下,当你提到“用户在onPostExecute被调用之前退出了你的activity”时,这实际上是什么意思?用户在关闭对话框后导航到其他活动页面。但是如果进入alertdialog.builder中的onClick方法之前,不是应该先调用builder.show()吗?或者是在执行builder.show()之后实际上发生了崩溃?因为我在关闭对话框后导航到其他活动页面了。(为了更好地理解,我已经稍微修改了我的代码) - MSIslam
1
您的异步任务将在用户从您的活动中导航后继续工作,这将导致builder.show()崩溃您的应用程序,因为没有活动来处理UI。因此,在获取数据之前,您的应用正在从Web中提取数据,但是您的活动已被销毁。 - moh.sukhni
好的,我相信我现在理解了情况。感谢您的评论。 - MSIslam
显示剩余7条评论

1
我正在onCreate中创建对话框,并使用showhide。对我来说,根本原因是没有通过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();

1
我尝试了这个,它解决了问题。
 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();

在代码片段中,使用requireActivity()代替requireContext() - Hmerman6006
或者在片段中使用requireActivity()而不是requireContext() - undefined

0

我遇到了这个错误,但是我的错误来自于 Toasts,而不是 Dialog。

我的布局中有 Activity 和 Fragments。Toast 的代码在 Activity 类中。Fragments 在 Activity 之前加载。

我认为 Toast 代码在 Context/Activity 完成初始化之前被触发了。我认为是命令中的 getApplicationContext() 导致了这个问题:Toast.makeText(getApplicationContext(), "onMenutItemActionCollapse called", Toast.LENGTH_SHORT).show();


你能解释一下这个:“Context/Activity finished initializing”是什么意思吗?你在哪个活动方法中调用toast呢? - Kishan Solanki
在Context完成加载之前,我调用了Toast。我猜是因为Fragment在Activity之前加载,所以我在Fragment中调用了Toast。Context在Activity中加载。 - Gene

0

我已经阅读了这篇文章以及其他许多回复,但是对于我的情况无法解决问题;最终我发现,在创建Builder时我使用了通过GetApplicationContext检索到的上下文而不是活动,这是我的问题所在。

我知道这不是这里的情况,但由于这是关于此异常的第一个和最佳线程,我在这里提出,以便对他人有用。


0
在我的情况下,我重构了代码并将对话框的创建放在一个单独的类中。我只传递了点击的View,因为View已经包含了一个上下文对象。尽管所有操作都在主线程上运行,但这导致了相同的错误消息。
然后,我改为同时传递Activity,并在对话框的创建中使用它的上下文 -> 现在一切都正常工作了。
    fun showDialogToDeletePhoto(baseActivity: BaseActivity, clickedParent: View, deletePhotoClickedListener: DeletePhotoClickedListener) {
        val dialog = AlertDialog.Builder(baseActivity) // <-- here
   .setTitle(baseActivity.getString(R.string.alert_delete_picture_dialog_title))
...
}

抱歉,我无法正确格式化代码片段 :(


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

}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接