今日无事,翻看Bugly的错误日志,想着解决几个线上BUG充当这周的周报内容,发现了一个很怪的BUG:
android.view.WindowManager$BadTokenException
Unable to add window -- token android.os.BinderProxy@184bc7e is not valid; is your activity running?
而且大部分集中在Android7.1的系统,是不是很调皮?搜索了一下,也终于是找到了参考,由于我本人和现有机型均无法复现,所以只能参考网上的资料加以修改,等下次版本更新再看情况,此次记录也是给自己做个记忆,方便日后参考!
Toast显示与隐藏
首先Toast显示依赖于一个窗口,这个窗口被WMS管理(WindowManagerService),当需要show的时候这个请求会放在WMS请求队列中,并且会传递一个TN类型的Bider对象给WMS,WMS并生成一个token传递给Android进行显示与隐藏,但是如果UI线程的某个线程发生了阻塞,并且已经NotificationManager检测已经超时就不删除token记录,此时token已经过期,阻塞结束的时候再显示的时候就发生了异常。
在android7.1.1的Toast源码handleShow是这样写的:
mWM.addView(mView, mParams);
而在8.0则是这样的:
try {
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
到这里能看到在发生异常的时候使用了try catch捕获,程序不会挂掉
解决方案:
/**
* @author CH
* @date 2018/6/26
* 部分7.1.1手机崩溃Toast解决方案
*/
public class ToastCompat {
private static Field sField_TN;
private static Field sField_TN_Handler;
private Toast mToast;
static {
try {
sField_TN = Toast.class.getDeclaredField("mTN");
sField_TN.setAccessible(true);
sField_TN_Handler = sField_TN.getType().getDeclaredField("mHandler");
sField_TN_Handler.setAccessible(true);
} catch (Exception e) {
}
}
private static void hook(Toast toast) {
try {
Object tn = sField_TN.get(toast);
Handler preHandler = (Handler) sField_TN_Handler.get(tn);
sField_TN_Handler.set(tn, new SafelyHandlerWarpper(preHandler));
} catch (Exception e) {
}
}
public void showToast(Context context, CharSequence cs, int length) {
if (mToast == null) {
mToast = Toast.makeText(context, cs, length);
} else {
mToast.setText(cs);
}
hook(mToast);
mToast.show();
}
public static class SafelyHandlerWarpper extends Handler {
private Handler impl;
public SafelyHandlerWarpper(Handler impl) {
this.impl = impl;
}
@Override
public void dispatchMessage(Message msg) {
try {
super.dispatchMessage(msg);
} catch (Exception e) {
}
}
@Override
public void handleMessage(Message msg) {
impl.handleMessage(msg);//需要委托给原Handler执行
}
}
}
简单来说就是通过反射注入在发生异常的地方进行try catch