关于Android7.x系统Toast显示异常BadTokenException解决方案

关于Android7.x系统Toast显示异常BadTokenException解决方案

BadTokenException详细及原因

Fatal Exception: android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@fcd9ef6 is not valid; is your activity running?
       at android.view.ViewRootImpl.setView(ViewRootImpl.java:806)
       at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:369)
       at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
       at android.widget.Toast$TN.handleShow(Toast.java:459)
       at android.widget.Toast$TN$2.handleMessage(Toast.java:342)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:186)
       at android.app.ActivityThread.main(ActivityThread.java:6491)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:914)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:804)

这是线上一直存在的一个异常,由于无法复现,所以没有管他。最近几天崩溃数暴增,忍无可忍,通过参考美团的这篇文章把这个异常解决了一下。

复现步骤

调用Toast的show()方法后,将主线程阻塞5s,即可稳定复现这个异常

Toast.makeToast(context, "123", LENGTH_LONG).show();

Thread.sleep(5*1000);

出现这个异常的原因就是Toast显示时会有一个Token,这个Token是由WindowManager创建并管理的,重点是这个Token有时间限制,当超过了一个Toast的显示时长(LENGTH_LONG)后,会把这个Token置为无效。所以把Toast延迟显示后,使用的Token就已经过期了。

这个问题全部发生在7.x系统上,在8.x的系统已经把显示Toast视图的位置添加了try catch捕获。如下代码所示:

public void handleShow(IBinder windowToken) {
    ...
    try {
         mWM.addView(mView, mParams);
         trySendAccessibilityEvent();
     } catch (WindowManager.BadTokenException e) {
         /* ignore */
     }
}

解决方案

在Android7.x系统上,利用反射机制,改由我们自己Handler处理Toast的显示,并添加BadTokenException异常捕获。


    int sdkInt = Build.VERSION.SDK_INT;
    if (sdkInt >= Build.VERSION_CODES.N && sdkInt < Build.VERSION_CODES.O && !isReflectedHandler) {
        reflectTNHandler(mToast);
        //这里为了避免多次反射,使用一个标识来限制
        isReflectedHandler = true;
    }

private static void reflectTNHandler(Toast toast) {
        try {
            Field tNField = toast.getClass().getDeclaredField("mTN");
            if (tNField == null) {
                return;
            }
            tNField.setAccessible(true);
            Object TN = tNField.get(toast);
            if (TN == null) {
                return;
            }
            Field handlerField = TN.getClass().getDeclaredField("mHandler");
            if (handlerField == null) {
                return;
            }
            Timber.d("TN class is %s.", TN.getClass());
            handlerField.setAccessible(true);
            handlerField.set(TN, new ProxyTNHandler(TN));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
  1. 反射拿到Toast的成员变量TN mTN
  2. 反射拿到mTN的成员变量Handler mHandler
  3. 替换为我们自己的Handler实现
  4. 在show Toast时添加异常捕获,并调用mTN响应的Toast显示方法handleShow

//Toast$TN持有的Handler变量
    private static class ProxyTNHandler extends Handler {
        private Object tnObject;
        private Method handleShowMethod;
        private Method handleHideMethod;

        ProxyTNHandler(Object tnObject) {
            this.tnObject = tnObject;
            try {
                this.handleShowMethod = tnObject.getClass().getDeclaredMethod("handleShow", IBinder.class);
                this.handleShowMethod.setAccessible(true);
                Timber.d("handleShow method is %s", handleShowMethod);
                this.handleHideMethod = tnObject.getClass().getDeclaredMethod("handleHide");
                this.handleHideMethod.setAccessible(true);
                Timber.d("handleHide method is %s", handleHideMethod);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0: {
                    //SHOW
                    IBinder token = (IBinder) msg.obj;
                    Timber.d("handleMessage(): token is %s", token);
                    if (handleShowMethod != null) {
                        try {
                            handleShowMethod.invoke(tnObject, token);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        } catch (WindowManager.BadTokenException e) {
                            //显示Toast时添加BadTokenException异常捕获
                            e.printStackTrace();
                            Timber.e(e, "show toast error.");
                        }
                    }
                    break;
                }

                case 1: {
                    //HIDE
                    Timber.d("handleMessage(): hide");
                    if (handleHideMethod != null) {
                        try {
                            handleHideMethod.invoke(tnObject);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                }
                case 2: {
                    //CANCEL
                    Timber.d("handleMessage(): cancel");
                    if (handleHideMethod != null) {
                        try {
                            handleHideMethod.invoke(tnObject);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                }

            }
            super.handleMessage(msg);
        }
    }

猜你喜欢

转载自blog.csdn.net/joye123/article/details/80738113