BadTokenException: Unable to add window -- token android.os.BinderProxy

须彭亮
2023-12-01

由于遇到的是BadTokenException这个异常,所以搜资料总结了一下:


这个异常总共有一下几种出现方式:

  • 1.Unable to add window --token null is not valid; is your activity running

  • **2.Unable to add window --token null is not for an application **

  • 3.Unable to add window -- token android.os.BinderProxy@XXX is not valid;
    is your activity running

  • **4.Unable to add window -- token android.app.LocalActivityManager
    $LocalActivityRecord @xxx is not valid; is your activity running
    **

处理方式分别是

情形1.android.view.WindowManager$BadTokenException: Unable to add window --token null is not valid; is your activity running?异常处理。

$BadTokenException: Unable to add window -- 
token null is not valid; is your activity running 

E/AndroidRuntime(1412): at android.view.ViewRootImpl.setView(ViewRootImpl.java:538) 
......

该异常多见于Popup Window组件的使用中抛出。

原因:错误在PopupWindow.showAtLocation(findViewById(R.id.main), Gravity.BOTTOM,0,0); popwindow必须依附于某一个view,而在oncreate中view还没有加载完毕,必须要等activity的生命周期函数全部执行完毕,你需要依附的view加载好后才可以执行popwindow。

解决办法:showAtLocation()函数可以这样改:

//修正后代码
findviewById(R.id.mView).post(new Runnable() {
                        @Override
                        public void run() {
                                popwindow.showAtLocation(mView, Gravity.CENTER, 0, 0);

                        }
                });

总结: PopupWindow必须在某个事件中显示或者是开启一个新线程去调用,不能直接在onCreate方法中显示一个Popupwindow,否则永远会有以上的错误。

参考
http://stackoverflow.com/questions/4187673/problems-creating-a-popup-window-in-android-activity

 

情形2.android.view.WindowManager$BadTokenException: Unable to add window --token null is not for an application ?异常处理

$BadTokenException: Unable to add window -- 
token null is not for an application 

E/AndroidRuntime(1412): at android.view.ViewRootImpl.setView(ViewRootImpl.java:538) 
......

该异常多见于AlertDialog组件的使用中抛出。

//抛异常代码
new AlertDialog.Builder(getApplicationContext())  //不能用getApplicationContext()
            .setIcon(android.R.drawable.ic_dialog_alert)  
            .setTitle("Warnning")  
            .setPositiveButton("Yes", positiveListener)
            .setNegativeButton(  "No", negativeListener)
            .create().show(); 

原因:导致报这个错是在于new AlertDialog.Builder(mcontext),虽然这里的参数是AlertDialog.Builder(Context context),但我们不能使用getApplicationContext()获得的Context,而必须使用Activity,因为只有一个Activity才能添加一个窗体。

解决方法:将new AlertDialog.Builder(Context context)中的参数用Activity.this(Activity是你的Activity的名称)或者getActivity()来填充就可以正确的创建一个Dialog了。

//修正后代码
new AlertDialog.Builder(this)  //this可以替换为MainActivity.this或getActivity()
            .setIcon(android.R.drawable.ic_dialog_alert)  
            .setTitle("Warnning")  
            .setPositiveButton("Yes", positiveListener)
            .setNegativeButton(  "No", negativeListener)
            .create().show(); 

参考
http://stackoverflow.com/questions/20779377/android-custom-dialog-gives-an-error

情形3.android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@XXX is not valid; is your activity running?异常处理。

android.view.WindowManager$BadTokenException: Unable to add window -- 
token android.os.BinderProxy@4250d6d8 is not valid; is your activity running?
    at android.view.ViewRootImpl.setView(ViewRootImpl.java:698)
   
    ......
    at dalvik.system.NativeStart.main(Native Method)

原因:从错误信息我们也可以明白其原因,此问题根本原因就是由于将要弹出的dialog所要依附的View已经不存在导致的。当界面销毁后再弹出来;或者界面跳转时我们的view发生改变,dialog依附的context发生变化或者界面未运行了。

解决方法:界面已经销毁引起的错误就只能判断界面是否存在然后再弹出了。

//修正后代码
if(!isFinishing()) {
     alert.show();
 }

参考
http://stackoverflow.com/questions/25554279/unable-to-add-window-token-android-os-binderproxy4250d6d8-is-not-valid-is-your

https://github.com/VKCOM/vk-android-sdk/issues/21

情形4.android.view.WindowManager$BadTokenException: Unable to add window -- token android.app.LocalActivityManager$LocalActivityRecord @xxx is not valid; is your activity running? 异常处理

android.view.WindowManager$BadTokenException: Unable to add window -- 
token android.app.LocalActivityManager$LocalActivityRecord@43e5b158
is not valid; is your activity running?  
//异常代码
TipDialog dialog = new TipDialog(XXX.this) ;

原因:因为new对话框的时候,参数context 指定成了this,即指向当前子Activity的context。但子Activity是动态创建的,不能保证一直存在。其父Activity的context是稳定存在的,所以有下面的解决办法。

解决方法:将context替换为getParent()即可。 注意:要创建dialog对象,上下文环境必须是activity,同时若ActivityGroup中嵌套ActivityGroup,嵌套多少就该使用多少个getParent()。

//修正后代码,只有最多一个parent的情形
TipDialog dialog = new TipDialog(getParent()) ; 
//修正后代码,适用于一个或多个parent的情形
Activity activity = TestActivity.this;  
while (activity.getParent() != null) {  
    activity = activity.getParent();  
 }  
              
TipDialog dialog = new TipDialog(activity) ;                   

参考
http://stackoverflow.com/questions/9914195/webview-in-activity-group-crashing-on-dialogs

注:为什么要使用getParent我们可以从ActivityGroup的内部机制来理解:

TabActivity的父类是ActivityGroup,而ActivityGroup的父类是Activity。因此从Ams的角度来看,ActivityGroup与普通的Activity没有什么区别,其生命周期包括标准的start,stop,resume,destroy等,而且系统中只允许同时允许一个ActivityGroup.但ActivityGroup内部有一个重要成员变量,其类型为LocalActivityManager,该类的最大特点在于它可以访问应用进程的主类,即ActivityThread类。Ams要启动某个Activity或者赞同某个Activity都是通过ActivityThread类执行的,而LocalActivityManager类就意味着可以通过它来装载不同的Activity,并控制Activity的不同的状态。注意,这里是装载,而不是启动,这点很重要。所谓的启动,一般是指会创建一个进程(如果所在的应用经常还不存在)运行该Activity,而装载仅仅是指把该Activity作为一个普通类进行加载,并创建一个该类的对象而已,而该类的任何函数都没有被运行。装载Activity对象的过程对AmS来讲是完全不可见的,那些嵌入的Activity仅仅贡献了自己所包含的Window窗口而已。而子Activity的不同状态是通过moveToState来处理的。

所以子Activity不是像普通的Activity一样,它只是提供Window而已,所以在创建Dialog时就应该使用getParent获取ActivityGroup真正的Activity,才可以加Dialog加入Activity中。

以上摘自https://www.jianshu.com/p/4c5fafe08fa7

我遇到的情况属于第三种,但是并没有解决我的问题,于是我又找到了这篇

 

android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@41791b20 is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:546)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:302)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:216)
at android.view.WindowManagerImpl$CompatModeWrapper.addView(WindowManagerImpl.java:141)
at android.view.Window$LocalWindowManager.addView(Window.java:537)
at android.app.Dialog.show(Dialog.java:278)
at android.app.AlertDialog$Builder.show(AlertDialog.java:991)
at android.widget.TextView.onTouchEvent(TextView.java:8430)
at android.view.View.dispatchTouchEvent(View.java:5553)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2027)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2027)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1762)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1953)
at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1397)
at android.app.Activity.dispatchTouchEvent(Activity.java:2431)
at dalvik.system.NativeStart.main(Native Method)

1,错误分析:

从错误信息我们也可以明白其原因,此问题根本原因就是由于将要弹出的dialog所要依附的View已经不存在导致的。

2,什么地方可能照成此问题:当界面销毁后再弹出来;或者界面跳转时我们的view发生改变,dialog依附的context发生变化或者界面未运行了。

此外,很多时候我们需要通过一个非组件类来调用一个view类的方法来弹出dialog或Toast,这样就需要再提供一个静态context来创建这个dialog或者Toast

例如我们在一个view中通过一个静态类来弹出一个对话框:

AlertDialog.Builder builder = new AlertDialog.Builder(mContextNew);

当然并不是所有静态context都是可以用来创建dialog的,例如***App().getApplication().getApplicationContext()这个context就不行,因为它并不代表哪一个Activity或者View。。这样就无法add这个dialog。

此view用于绑定显示数据,我们在其构造方法中初始化一个静态变量mContextNew为此view的mContext。这样我们就可以通过一个静态类来弹出对话框了,只需传入这个静态的context(mContextNew)就可以了。。

但是这个静态的context如果只在构造方法中初始化的话是会存在问题的,因为如果另起了一个界面其绑定数据的view也是用的这个view那么这个静态context就会被重新修改。。

因此当这个新的界面finish后返回到上次的界面,这个静态的context是刚才已经finish的view的context。因此如果仍然传入这个静态变量通过一个静态类来弹出对话框就会出现上述找不到window的错误了。

解决办法:

对于tab页出现的错误可以用其父类的context来弹出dialog;  对于界面已经销毁引起的错误就只能判断界面是否存在然后再弹出了;

对于利用静态context来弹出的dialog可以通过规避的方式来解决,比如避免出现静态context被修改。。但是这样就可能限制了我们程序的功能。。

因此我们可以通过在bind数据时时时更新这个静态context就可以解决此问题了,这样就可以保证这个静态的context在任何view中都是当前的界面的view的context。就不会出现找不到其父类window了。

以上摘自https://www.cnblogs.com/awkflf11/p/5293267.html

这个说明更加清晰一些,是说给AlertDialog.Builder builder = new AlertDialog.Builder(mContextNew);传入的静态context已经发生了变化,但是要弹出的Dialog还是用的老的context,所以我们要更新这个context才行,所以我查了我的代码,确定是这个错误。贴一下我的代码

public void showProgressDialog(Context context) {

        if(progressDialog == null) {
            progressDialog = new ProgressDialog(context);
            progressDialog.setMessage("正在加载中...");
            progressDialog.setCancelable(false);
        }
        progressDialog.show();

    }


    public void hideProgressDialog(){
        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
            progressDialog=null;    
        }

    }

在网络请求之前调用showProgressDialog(Context context),网络请求之后调用hideProgressDialog().以上两个方法都是在MainActivity的Tab中的fragment中调用的,当我点击返回键返回到桌面,再回到MainActivity中的这个Fragment中时,调用showProagressDialog(Context context),由于我判断了当progressDialog== null时才会创建ProgressDialog所以此时progressDialog已经创建过了是存在这个对象的,所以不会重新创建,但是我们此时用的context是上次销毁的Mainactivity对象,而不是此时新创建的MainActivity,所以就会报错。解决办法就是在progressDialog.dismiss();方法后面添加progressDialog=null,下次ProgressDialog会重新创建,而且使用总是最新的Mainactivity对象。

 

 类似资料:

相关阅读

相关文章

相关问答