In version 7.x, Token verification is added to Toast, which is true. However, when show() is called to display Toast, if a time-consuming operation gets stuck in the main thread for more than 5 seconds, BadTokenException exception will be thrown. At the beginning of 8.x system, Google will try catch internally. The 7.x system is a permanent pain, which can only be fixed by ourselves.
Repair plan I
Reflect the Context of the agent View, try catch within the Context, and deal with the badtokeneexception problem of Toast
. BadTokenListener, Toast throws a BadTokenException listener
publicinterface BadTokenListener { /** * Callback when Toast throws BadTokenException * * @param toast Toast instance with exception */ void onBadTokenCaught(@NonNull Toast toast); }
. SafeToastContext, the Context used for wrapping Toast
publicclass 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() { //Proxy original Context returnnew ApplicationContextWrapper(super.getApplicationContext()); } privateclass ApplicationContextWrapper extends ContextWrapper { public ApplicationContextWrapper(Context base) { super(base); } @Override public Object getSystemService(String name) { if (Context.WINDOW_SERVICE.equals(name)) { //Get the original WindowManager and give it to WindowManagerWrapper agent to catch BadTokenException exception Context baseContext = getBaseContext(); returnnew WindowManagerWrapper((WindowManager) baseContext.getSystemService(name)); } returnsuper.getSystemService(name); } } privateclass WindowManagerWrapper implements WindowManager { /** * Wrapped WindowManager instance */ 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) { //Use the tool in addView to catch BadTokenException exception 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, replacing Toast's facade
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); returnnew 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); } returnthis; } @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; } /** * Reflection sets the Context in View. Toast will get the Context of View to get WindowManager */ private static void setContextCompat(@NonNull View view, @NonNull Context context) { //7.1.1 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(); } } } }
. use ToastCompat instead of Toast
ToastCompat.makeText(context,"I am Toast Content", Toast.LENGTH_SHORT) .setBadTokenListener(toast -> Logger.d("Toast: " + toast + " => Throw out BadTokenException abnormal") ) .show();
Repair plan II
Hook Toast, replace the Handler of TN object in Toast, and try catch the method of dispatchMessage()
public class SafeToast { privatestatic Field sField_TN; privatestatic Field sField_TN_Handler; static { try { //Reflection getting TN object 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(); } } /** * Replace Toast's original Handler */ privatestaticclass 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) { //Try catch the processing method of distributing Message try { super.dispatchMessage(msg); } catch (WindowManager.BadTokenException e) { e.printStackTrace(); if (mBadTokenListener != null) { mBadTokenListener.onBadTokenCaught(mToast); } } } @Override public void handleMessage(Message msg) { //Need to delegate to the original Handler for execution mOriginImpl.handleMessage(msg); } } }
. use safeload instead of Toast
SafeToast.show(context,"I am Toast Content", Toast.LENGTH_SHORT, toast -> Logger.d("Toast: " + toast + " => Throw out BadTokenException abnormal"));