Android问题集:Unable to add window -- token android.os.BinderProxy@bf4921f is not valid;
Posted 王梵
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android问题集:Unable to add window -- token android.os.BinderProxy@bf4921f is not valid;相关的知识,希望对你有一定的参考价值。
问题描述
公司某个产品有反馈,说是在进入某个界面时容易引发崩溃,要到了崩溃日志后发现,确实爆出了一个异常,非必现,偶发bug,一看就大概明白是啥原因了,自己就写了个demo,复现了这个问题,下面是报错日志
E/androidRuntime: FATAL EXCEPTION: main
Process: com.wisely, PID: 12063
android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@bf4921f is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:696)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:347)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
at android.app.Dialog.show(Dialog.java:319)
at com.wisely.DialogUtil.showProgressDialog(DialogUtil.java:54)
at com.wisely.DialogUtil.showProgressDialog(DialogUtil.java:16)
at com.wisely.activity.bug.WindowDestroyBugActivity$1.handleMessage(WindowDestroyBugActivity.java:29)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6114)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:874)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:764)
复现bug的代码
xml比较简单,未列出,当前Activity继承了一个自己写的BaseActivity,其实什么都没做,而且我本身也不建议这种抽取,不过因为这是我的demo集,场景特殊,所以才抽取了这个BaseActivity。
注意,下面的代码是有问题的代码,切勿复制粘贴。
public class WindowDestroyBugActivity extends BaseActivity
Handler mHandler = new Handler()
@Override
public void handleMessage(Message msg)
switch (msg.what)
case 1:
DialogUtil.showProgressDialog(WindowDestroyBugActivity.this, "弹窗喽");
break;
;
/**
* 点击事件的回调,在xml中写的
*/
public void onClick(View v)
switch (v.getId())
case R.id.tv_unable_to_add_window_finish:
finish();
mHandler.sendEmptyMessageDelayed(1, 3000);
break;
@Override
public int getLayoutResource()
return R.layout.activity_unable_to_add_window;
@Override
public String getTitleContent()
return "Unable to add window——token android.os.BinderProxy@2ed5fc06 is not valid;is your activity running?";
/**
* @author wisely
*/
public class DialogUtil
public static void showProgressDialog(Activity activity, String message)
showProgressDialog(activity,message,false);
private static WeakReference<Activity> mWeakReference;
private static ProgressDialog mProgressDialog;
/**
* @param activity 需要弹窗的activity
* @param message 弹窗展示的内容
* @param flag 触摸弹窗外区域,是否取消窗口
* */
public static void showProgressDialog(Activity activity,String message,boolean flag)
if(!isLiving(activity))
return;
if(mWeakReference == null)
mWeakReference = new WeakReference(activity);
activity = mWeakReference.get();
if (mProgressDialog == null)
if (activity.getParent() != null)
mProgressDialog = new ProgressDialog(activity.getParent());
else
mProgressDialog = new ProgressDialog(activity);
if (!mProgressDialog.isShowing())
mProgressDialog.dismiss();
mProgressDialog.setMessage(message);
mProgressDialog.setIndeterminate(false);
mProgressDialog.setCancelable(flag);
mProgressDialog.setIcon(R.mipmap.ic_launcher);
mProgressDialog.show();
else
mProgressDialog.setMessage(message);
/**
* 判断activity是否存活
* */
private static boolean isLiving(Activity activity)
if(activity == null)
Log.d("wisely","activity == null");
return false;
return true;
问题解决
造成上面崩溃的原因很简单,就是因为在要show dialog的时候,忽然发现,dialog依附的activity已经挂掉了,于是程序崩溃。
知道了原因,解决就容易了。
我们只需要将上面DialogUtil中的isLiving方法修改一下,如下
private static boolean isLiving(Activity activity)
if (activity == null)
Log.d("wisely", "activity == null");
return false;
if (activity.isFinishing())
Log.d("wisely", "activity is finishing");
return false;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) //android 4.2
//
// if (activity.isDestroyed())
// Log.d("wisely", "activity is destroy");
// return false;
//
//
return true;
上面的代码很容易理解,注意我注释掉的代码,里面有一个方法,isDestroyed(),它是从4.2版本开始新增的方法。在很多博文里,都有关于它的使用,但我为什么会注释掉它呢?
再看它前面的代码,里面有一个isFinishing()方法,这个方法和isDestroyed()方法到底有啥不同呢?
根据我的打印发现,在onPause(),onStop(),和onDestroy()方法中,isFinishing()方法的返回值一直为true(在onCreate(),onStart(),onResume()方法中返回false),而isDestroy()的返回值,只有在onDestroy()方法中才为true,其它生命周期方法中,为false。
这么算起来,只要前面写了isFinishing(),那么写不写isDestroyed()方法都是一样的。
新问题:
神马?还有新问题,对的。
在DialogUtil中,我们只添加了show进度框的方法,怎么能没加close的方法呢,好的,我们加上,如下
/**
* 关闭进度框
*/
public static void closeProgressDialog()
if (isShowing(mProgressDialog))
mProgressDialog.dismiss();
mProgressDialog = null;
mWeakReference.clear();
mWeakReference = null;
/**
* 判断进度框是否正在显示
*/
private static boolean isShowing(ProgressDialog dialog)
boolean isShowing = dialog != null
&& dialog.isShowing();
Log.d("wisely",">------isShow:"+isShowing);
return isShowing;
代码很明白,但我们调用的时候,还是会存在偶发性bug,下面,我们就自己制造出一个bug。
只列出更改的代码
Handler mHandler = new Handler()
@Override
public void handleMessage(Message msg)
switch (msg.what)
case 1:
DialogUtil.closeProgressDialog();
break;
case 2:
DialogUtil.showProgressDialog(WindowDestroyBugActivity.this, "弹窗喽");
break;
;
/**
* 点击事件的回调,在xml中写的
*/
public void onClick(View v)
switch (v.getId())
case R.id.tv_unable_to_add_window_finish:
DialogUtil.showProgressDialog(WindowDestroyBugActivity.this, "弹窗喽");
finish();
mHandler.sendEmptyMessageDelayed(1, 3000);
break;
逻辑很清晰,先显示一个弹窗,然后finish掉整个activity,过了3s后,再关闭dialog,然后,就爆出了下面的bug。
05-14 23:46:33.867 13456-13456/com.wisely E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.wisely, PID: 13456
java.lang.IllegalArgumentException: View=DecorView@9d7cbf6[] not attached to window manager
at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:479)
at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:388)
at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:126)
at android.app.Dialog.dismissDialog(Dialog.java:360)
at android.app.Dialog.dismiss(Dialog.java:343)
at com.wisely.DialogUtil.closeProgressDialog(DialogUtil.java:67)
at com.wisely.activity.bug.WindowDestroyBugActivity$1.handleMessage(WindowDestroyBugActivity.java:28)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6114)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:874)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:764)
最终,报错的其实是DialogUtil中的dismiss方法
mProgressDialog.dismiss();
not attached to window manager,通俗点讲就是,dialog要关闭,但activity已经挂掉了。这个原因,与在show dialog时非常类似。
再解决
原因明白,解决也很简单
只需要在close的时候,多加一个判断
if (isShowing(mProgressDialog) && isExist_Living(mWeakReference))
....
private static boolean isExist_Living(WeakReference<Activity> weakReference)
if(weakReference != null)
Activity activity = weakReference.get();
if (activity == null)
return false;
if (activity.isFinishing())
return false;
return true;
return false;
好了,问题解决了。
最后一个问题
纳尼?还有问题,不是说好都解决了吗?!
好吧,其实还真有一个,在上面的代码运行时,会发现一个问题,虽然不会导致程序挂掉,但看着也让人不爽快,如下
E/WindowManager: android.view.WindowLeaked: Activity com.wisely.activity.bug.WindowDestroyBugActivity has leaked window DecorView@f9bf091[] that was originally added here
at android.view.ViewRootImpl.<init>(ViewRootImpl.java:423)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:337)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
at android.app.Dialog.show(Dialog.java:319)
at com.wisely.DialogUtil.showProgressDialog(DialogUtil.java:55)
at com.wisely.DialogUtil.showProgressDialog(DialogUtil.java:17)
at com.wisely.activity.bug.WindowDestroyBugActivity.onClick(WindowDestroyBugActivity.java:54)
at java.lang.reflect.Method.invoke(Native Method)
at android.view.View$DeclaredOnClickListener.onClick(View.java:4694)
at android.view.View.performClick(View.java:5611)
at android.view.View$PerformClick.run(View.java:22276)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6114)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:874)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:764)
上面的错误翻译一下就是,你的dialog窗口泄露了,因为没有在activity finish的时候,关闭dialog。
再再解决
思路明细,解决方案自然有,只需要在某个生命周期方法中,调用dialog关闭的方法即可。
那么,问题来了,在哪个生命周期方法中close呢?有3个可选,onPause(),onStop(),onDestroy()。
当然是onPause()方法了,很好,我们在onPause()方法中调用DialogUtil中的closeProgressDialog()方法,但是发现,貌似没有作用,原因为嘛,我们来看closeProgressDialog()中的一个判断
private static boolean isExist_Living(WeakReference<Activity> weakReference)
if(weakReference != null)
Activity activity = weakReference.get();
if (activity == null)
return false;
if (activity.isFinishing())
return false;
return true;
return false;
由于我们是在onPause方法中调用的closeProgressDialog()方法,所以activity.isFinishing()的返回值为true,导致整个isExist_Living()方法的返回值为false,最终在closeProgressDialog()方法中,并不会运行mProgressDialog.dismiss()这句代码。
那怎么办,没办法,只能单独为它来设置一个方法了,如下:
public static void closeProgressDialog(boolean flag)
if (isShowing(mProgressDialog) && flag)
mProgressDialog.dismiss();
mProgressDialog = null;
mWeakReference.clear();
mWeakReference = null;
这个方法与之前的方法类似,唯一的不同之处就在于有一个传入参数,在onPause方法中调用时,直接传入true即可。
@Override
protected void onPause()
super.onPause();
DialogUtil.closeProgressDialog(true);
至于方法的抽取,就不在本篇博文的讨论范围之内了。
总结
偶发的bug,时间久了,也就变成必现了。
以上是关于Android问题集:Unable to add window -- token android.os.BinderProxy@bf4921f is not valid;的主要内容,如果未能解决你的问题,请参考以下文章
android.view.WindowManager$BadTokenException: Unable to add window
bug_android.view.WindowManager$BadTokenException: Unable to add window -- token
AlertDialog Unable to add window
bug_ _ android.view.WindowManager$BadTokenException: Unable to add window -- token