<meta charset="utf-8">
7.x版本复凳,對(duì)Toast添加了Token驗(yàn)證瘤泪,這本是對(duì)的,但是調(diào)用show()顯示Toast時(shí)育八,如果有耗時(shí)操作卡住了主線程超過5秒对途,就會(huì)拋出BadTokenException的異常,而8.x系統(tǒng)開始髓棋,Google則在內(nèi)部進(jìn)行了try-catch实檀。而7.x系統(tǒng)則是永久的痛,只能靠我們自己來修復(fù)了按声。
修復(fù)方案一
反射代理View的Context膳犹,Context內(nèi)進(jìn)行try-catch,處理Toast的BadTokenException問題
- BadTokenListener签则,Toast拋出BadTokenException監(jiān)聽器
public interface BadTokenListener {
/**
* 當(dāng)Toast拋出BadTokenException時(shí)回調(diào)
*
* @param toast 發(fā)生異常的Toast實(shí)例
*/
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實(shí)例
*/
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動(dòng)刀豺旬,捕獲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.removeView(view);
}
}
}
- ToastCompat,替代Toast的門面
public class ToastCompat extends Toast {
private Toast mToast;
public ToastCompat(Context context, Toast toast) {
super(context);
mToast = toast;
}
public static ToastCompat makeText(Context context, CharSequence text, int duration) {
@SuppressLint("ShowToast")
Toast toast = Toast.makeText(context, text, duration);
setContextCompat(toast.getView(), context);
return new ToastCompat(context, toast);
}
public static Toast makeText(Context context, @StringRes int resId, int duration)
throws Resources.NotFoundException {
return makeText(context, context.getResources().getText(resId), duration);
}
public @NonNull
ToastCompat setBadTokenListener(@NonNull BadTokenListener listener) {
final Context context = getView().getContext();
if (context instanceof SafeToastContext) {
((SafeToastContext) context).setBadTokenListener(listener);
}
return this;
}
@Override
public void show() {
mToast.show();
}
@Override
public void setDuration(int duration) {
mToast.setDuration(duration);
}
@Override
public void setGravity(int gravity, int xOffset, int yOffset) {
mToast.setGravity(gravity, xOffset, yOffset);
}
@Override
public void setMargin(float horizontalMargin, float verticalMargin) {
mToast.setMargin(horizontalMargin, verticalMargin);
}
@Override
public void setText(int resId) {
mToast.setText(resId);
}
@Override
public void setText(CharSequence s) {
mToast.setText(s);
}
@Override
public void setView(View view) {
mToast.setView(view);
setContextCompat(view, new SafeToastContext(view.getContext(), this));
}
@Override
public float getHorizontalMargin() {
return mToast.getHorizontalMargin();
}
@Override
public float getVerticalMargin() {
return mToast.getVerticalMargin();
}
@Override
public int getDuration() {
return mToast.getDuration();
}
@Override
public int getGravity() {
return mToast.getGravity();
}
@Override
public int getXOffset() {
return mToast.getXOffset();
}
@Override
public int getYOffset() {
return mToast.getYOffset();
}
@Override
public View getView() {
return mToast.getView();
}
@NonNull
public Toast getBaseToast() {
return mToast;
}
/**
* 反射設(shè)置View中的Context柒凉,Toast會(huì)獲取View的Context來獲取WindowManager
*/
private static void setContextCompat(@NonNull View view, @NonNull Context context) {
//7.1.1版本才進(jìn)行處理
if (Build.VERSION.SDK_INT == 25) {
try {
Field field = View.class.getDeclaredField("mContext");
field.setAccessible(true);
field.set(view, context);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
}
- 使用ToastCompat代替Toast
ToastCompat.makeText(context,"我是Toast的內(nèi)容", Toast.LENGTH_SHORT)
.setBadTokenListener(toast ->
Logger.d("Toast:" + toast + " => 拋出BadTokenException異常")
)
.show();
修復(fù)方案二
對(duì)Toast進(jìn)行Hook族阅,替換Toast中TN對(duì)象的Handler,對(duì)分發(fā)消息dispatchMessage()方法進(jìn)行try-catch
public class SafeToast {
private static Field sField_TN;
private static Field sField_TN_Handler;
static {
try {
//反射獲取TN對(duì)象
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) {
e.printStackTrace();
}
}
private SafeToast() {
}
@SuppressLint("ShowToast")
public static void show(Context context, CharSequence message, int duration) {
show(context, message, duration, null);
}
@SuppressLint("ShowToast")
public static void show(Context context, CharSequence message, int duration, BadTokenListener badTokenListener) {
Toast toast = Toast.makeText(context.getApplicationContext(), message, duration);
hook(toast, badTokenListener);
toast.setDuration(duration);
toast.setText(message);
toast.show();
}
@SuppressLint("ShowToast")
public static void show(Context context, @StringRes int resId, int duration) {
show(context, resId, duration, null);
}
@SuppressLint("ShowToast")
public static void show(Context context, @StringRes int resId, int duration, BadTokenListener badTokenListener) {
Toast toast = Toast.makeText(context.getApplicationContext(), resId, duration);
hook(toast, badTokenListener);
toast.setDuration(duration);
toast.setText(context.getString(resId));
toast.show();
}
private static void hook(Toast toast, BadTokenListener badTokenListener) {
try {
Object tn = sField_TN.get(toast);
Handler originHandler = (Handler) sField_TN_Handler.get(tn);
sField_TN_Handler.set(tn, new SafeHandler(toast, originHandler, badTokenListener));
} catch (Throwable e) {
e.printStackTrace();
}
}
/**
* 替換Toast原有的Handler
*/
private static class SafeHandler extends Handler {
private Toast mToast;
private Handler mOriginImpl;
private BadTokenListener mBadTokenListener;
SafeHandler(Toast toast, Handler originHandler, BadTokenListener badTokenListener) {
mToast = toast;
mOriginImpl = originHandler;
mBadTokenListener = badTokenListener;
}
@Override
public void dispatchMessage(Message msg) {
//對(duì)分發(fā)Message的處理方法進(jìn)行try-catch
try {
super.dispatchMessage(msg);
} catch (WindowManager.BadTokenException e) {
e.printStackTrace();
if (mBadTokenListener != null) {
mBadTokenListener.onBadTokenCaught(mToast);
}
}
}
@Override
public void handleMessage(Message msg) {
//需要委托給原Handler執(zhí)行
mOriginImpl.handleMessage(msg);
}
}
}
- 使用SafeToast代替Toast
SafeToast.show(context,"我是Toast的內(nèi)容", Toast.LENGTH_SHORT, toast ->
Logger.d("Toast:" + toast + " => 拋出BadTokenException異常"));
作者:愛寫代碼的何蜀黍
鏈接:http://www.reibang.com/p/256103c59d45