7.x版本,对Toast添加了Token验证,这本是对的,但是调用show()显示Toast时,如果有耗时操作卡住了主线程超过5秒,就会抛出BadTokenException的异常,而8.x系统开始,Google则在内部进行了try-catch。而7.x系统则是永久的痛,只能靠我们自己来修复了。
修复方案一
反射代理View的Context,Context内进行try-catch,处理Toast的BadTokenException问题
- BadTokenListener,Toast抛出BadTokenException监听器
public interface BadTokenListener {
/**
* 当Toast抛出BadTokenException时回调
*
* @param toast 发生异常的Toast实例
*/
void onBadTokenCaught(@NonNull Toast toast);
}
- SafeToastContext,包裹Toast使用的Context
public class SafeToastContext extends ContextWrapper {
private Toast mToast;
private BadTokenListener mBadTokenListener;
public SafeToastContext(Context base, Toast toast) {
super(base);
mToast = toast;
}
public void setBadTokenListener(@NonNull BadTokenListener badTokenListener) {
mBadTokenListener = badTokenListener;
}
@Override
public Context getApplicationContext() {
//代理原本的Context
return new ApplicationContextWrapper(super.getApplicationContext());
}
private class ApplicationContextWrapper extends ContextWrapper {
public ApplicationContextWrapper(Context base) {
super(base);
}
@Override
public Object getSystemService(String name) {
if (Context.WINDOW_SERVICE.equals(name)) {
//获取原来的WindowManager,交给WindowManagerWrapper代理,捕获BadTokenException异常
Context baseContext = getBaseContext();
return new WindowManagerWrapper((WindowManager) baseContext.getSystemService(name));
}
return super.getSystemService(name);
}
}
private class WindowManagerWrapper implements WindowManager {
/**
* 被包裹的WindowManager实例
*/
private WindowManager mImpl;
public WindowManagerWrapper(@NonNull WindowManager readImpl) {
mImpl = readImpl;
}
@Override
public Display getDefaultDisplay() {
return mImpl.getDefaultDisplay();
}
@Override
public void removeViewImmediate(View view) {
mImpl.removeViewImmediate(view);
}
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
//在addView动刀,捕获BadTokenException异常
try {
mImpl.addView(view, params);
} catch (BadTokenException e) {
e.printStackTrace();
if (mBadTokenListener != null) {
mBadTokenListener.onBadTokenCaught(mToast);
}
}
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mImpl.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mImpl.