Android 7.x Toast BadTokenException处理
source link: http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ%3D%3D&%3Bmid=2247489811&%3Bidx=1&%3Bsn=add55ae7e4b02dec76e00d8dd6ca6db9
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
code小生 一个专注大前端领域的技术平台 公众号回复 Android
加入安卓技术群
作者:爱写代码的何蜀黍
链接:https://www.jianshu.com/p/256103c59d45
声明:本文已获 爱写代码的何蜀黍
授权发表,转发等请联系原作者授权
7.x版本,对Toast添加了Token验证,这本是对的,但是调用show()显示Toast时,如果有耗时操作卡住了主线程超过5秒,就会抛出BadTokenException的异常,而8.x系统开始,Google则在内部进行了try-catch。而7.x系统则是永久的痛,只能靠我们自己来修复了。
Api对比.png修复方案一
反射代理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.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; } /** * 反射设置View中的Context,Toast会获取View的Context来获取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(); } } } }
-
使用ToastCompat代替Toast
ToastCompat.makeText(context,"我是Toast的内容", Toast.LENGTH_SHORT) .setBadTokenListener(toast -> Logger.d("Toast:" + toast + " => 抛出BadTokenException异常") ) .show();
修复方案二
对Toast进行Hook,替换Toast中TN对象的Handler,对分发消息dispatchMessage()方法进行try-catch
public class SafeToast { private static Field sField_TN; private static Field sField_TN_Handler; static { try { //反射获取TN对象 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) { //对分发Message的处理方法进行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执行 mOriginImpl.handleMessage(msg); } } }
-
使用SafeToast代替Toast
SafeToast.show(context,"我是Toast的内容", Toast.LENGTH_SHORT, toast -> Logger.d("Toast:" + toast + " => 抛出BadTokenException异常"));
Recommend
-
44
ToastCompat An Android library to hook and fix Toast BadTokenException Usage implementation 'me.drakeet.support:toastcompat:1.1.0' ToastCompat.makeText(context, "hello world...
-
39
UniversalToast:一个简洁优雅的toast组件,支持点击和GIF&安全
-
40
Toast与Snackbar的那点事
-
71
-
23
背景: 对于 Android 开发者来说,BadTokenException 问题都有直接或间接的遇到,尤其是在使用 Toast 和 Dialog 过程,因为这两类场景的展示过程都发生在异步,如果在展示之前,主线程消息耗时过多导致服务端(System...
-
8
[本文结构] 问题概述: 今天SDK的bita测试遇到不少异常的crash,其中crash的关键日志都是类似这种: com.example.wegame,8928838,android....
-
5
react-native-fast-toast A Toast component for react-native, supports Android, IOS, Web, Windows. Features Normal, Success, Danger and Warning toasts Customizable and Icon support Smooth animatio...
-
1
我们都清楚,Toast显示时长有两个选择,长显示是3.5秒,端显示是2秒。那如果想要做到长时间显示,该怎么做呢?有个历史遗留的app通过开一个线程,不断调用show方法进行实现,这些年也没出过问题,直到系统版本更新到了Android9.0。实现方式大概如下: ...
-
2
❤️Android 快别用Toast了,来试试Snackbar❤️ – Android开发中文站 你的位置:Android开发中文站 > Android开发 >
-
1
本篇带来的是: Android用于提示信息的一个控件——Toast(吐司)!Toast是一种很方便的消息提示框,会在 屏幕中显示一个消息提示框,没任何按钮,也不会获得焦点一段时间过...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK