buider.show()上的“ android.view.WindowManager $ BadTokenException:无法添加窗口”


118

从我的主 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 2013年

这不是完整的代码,这是一个很大的代码,因此我仅在此处添加了我看到崩溃报告中的问题的部分
MSIslam 2013年

好的。通常,您可以只张贴函数名称并注释您正在那里做很多事情(就像您现在所做的那样)。它更容易理解。:)。
Shobhit Puri 2013年

您是否正在从某处导航到其他活动?
Shobhit Puri 2013年

1
您写的是评论//send to some other activity。我认为,如果您要注释要进行新活动的零件,则该错误将消失。似乎发生此错误是因为您之前的对话框已完全消除,您的新活动开始了。在中onPostExecute(),您将看到警报对话框,并且正在提供login活动的上下文。但是您正在导航到其他活动,因此上下文变得错误。因此,您会收到此错误!参见stackoverflow.com/questions/15104677/…类似问题。
Shobhit Puri

Answers:


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

问题:

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

如果您尝试从后台线程(通常是从AsyncTask的onPostExecute()中)修改UI,并且如果该活动进入完成阶段(即)显式调用finish(),则用户按下主页或后退按钮或由Android进行活动清除得到这个错误。

原因:

发生此异常的原因是,如异常消息所示,活动已完成,但是您正在尝试显示带有已完成活动的上下文的对话框。由于对话框没有窗口显示,因此android运行时将引发此异常。

解:

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

还要weak reference为活动维护一个(而不是强引用),以便在不需要该活动时可以销毁该活动,并在使用该活动引用执行任何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将  创建一种特殊的窗口令牌,称为应用程序窗口令牌,它唯一地标识应用程序的顶级容器窗口。活动管理器将此令牌同时提供给应用程序和窗口管理器,并且应用程序每次要向屏幕添加新窗口时,都会将令牌发送给窗口管理器。这样可以确保应用程序和窗口管理器之间的安全交互(通过使其无法在其他应用程序之上添加窗口),还可以使活动管理器轻松直接向窗口管理器发出请求。


这很有道理!您的解决方案对我来说也很棒。(y)
MSIslam 2013年

消息“空白的最终字段loginActivityWeakRef可能尚未初始化”,并以此方式尝试:私有的最终WeakReference <login> loginActivityWeakRef = new WeakReference <login>(login.this); 不知道这是对的吗
MSIslam 2013年

我也删除了WeakReference <login> loginActivityWeakRef之前的最终版本,因为它在构造函数中显示错误。
MSIslam 2013年

1
尝试使用新的chkCubscription(this).execute(“”); 而不是新的chkCubscription.execute(“”); 如您在上面发布的。
Ritesh Gune 2013年

2
可怕的错误!我正在学习一个教程,并且以@PhilRoggenbuck的身份出现问题是由于在调用StartActivity(...)之前调用Toast..Show()引起的。为了解决这个问题,我将吐司面包移到了新调用的活动中!
蒂埃里

26

我有显示功能的对话框:

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我认为这缺少一些细节,例如在后台线程中调用对话框,但是我完全同意您现在的观点。
被遗弃的购物车

好点,为什么我需要检查isFinishing!
Chibueze Opata

9

可能的原因是警报对话框的上下文。您可能已经完成了该活动,因此它试图在该上下文中打开但已关闭。尝试将对话框的上下文更改为您的第一个活动,因为它直到结束都不会结束。

例如

而不是这个。

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

尝试使用

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

3
  • 首先,您不能在没有覆盖doInBackground的情况下扩展AsyncTask
  • 第二次尝试从构建器创建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(),它似乎可以正常工作,但是不知道它是否对每个人都可以正常工作。如II所述,我当前的代码也可以正常工作,但是对于少数用户来说只有几次,它显示无法添加窗口的问题。您能否建议我编码中的实际问题是什么可能导致此问题?
MSIslam

在这种情况下,用户在调用onPostExecute之前退出了您的活动,因此没有用于保存对话框的窗口,这将导致您的应用程序崩溃。在onStop上添加标志,以了解您的活动是否不再可见,然后不显示对话框。
moh.sukhni 2013年

onPostExecute实际上被调用,因为builder.show()处于一种条件下,当我根据doInBackground()的Web服务调用结果检查用户是否未订阅时。因此,如果未调用onPostExecute,则不会到达builder.show()行。
MSIslam 2013年

onPostExecute默认在doInBackground之后调用,您将无法调用它,并且将执行任何操作。
moh.sukhni 2013年

1
在用户从您的活动中导航之后,您的异步任务将继续工作,这将导致builder.show()破坏您的应用程序,因为没有活动可为您处理UI。因此您的应用正在从网络上提取数据,但是在获取数据之前您的活动已被破坏。
moh.sukhni 2013年

1

我正在创建Dialog 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
切勿对活动保持静态引用。这将导致内存泄漏
Leandroid
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.