android

Toast BadTokenException 简单的修复方案

2020-06-28  本文已影响0人  WLHere

简单的修复方案,不用反射,也不用编译期修改代码
Demo地址:https://github.com/WLHere/SafeToast

Toast在android 7.1.1经常有如下崩溃

android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@864f33a is not valid; is your activity running?

at android.view.ViewRootImpl.setView(ViewRootImpl.java:683)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
at com.wuba.town.util.SafeToastServiceWindowManagerWrapper.addView(SafeToastService.kt:62) at android.widget.ToastTN.handleShow(Toast.java:502)
at android.widget.ToastTN2.handleMessage(Toast.java:381)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6211)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:793)

问题根源:NotificationManagerService通知Toast show后,会发送一个延迟消息(延迟2s或3.5s)取消token。如果因为UI线程卡顿导致Toast没有及时显示,待Toast显示时就会出现这个问题。

下边介绍一种不用反射和编译动态修改代码的修复方案。
Toast.TN.handleShow是从applicationContext获取WindowManager。我们重写Application的getSystemService方法,对于从Toast来的调用返回兼容的WindowManager,然后try catch add view方法。

class App : Application() {

    override fun getSystemService(name: String): Any? {
        return SafeToastService.getSystemService(name, super.getSystemService(name))
    }
}
object SafeToastService {
    private var mWindowManager: WindowManager? = null

    fun getSystemService(name: String, baseService: Any?): Any? {
        if (Build.VERSION.SDK_INT <= 25) {// 兼容android 7.1.1 toast崩溃问题
            if (name == Context.WINDOW_SERVICE && callFromToast()) {
                if (mWindowManager == null) {
                    mWindowManager = WindowManagerWrapper(baseService as WindowManager)
                }
                return mWindowManager
            }
        }
        return baseService
    }

    private fun callFromToast(): Boolean {
        var fromToast = false
        try {
            // android.widget.Toast$TN.handleShow
            val traces = Thread.currentThread().stackTrace
            if (traces != null) {
                for (trace in traces) {
                    if ("android.widget.Toast\$TN" == trace.className && "handleShow" == trace.methodName) {
                        fromToast = true
                        break
                    }
                }
            }
        } catch (ignored: Throwable) {
        }
        return fromToast
    }

    class WindowManagerWrapper(private val baseManager: WindowManager) : WindowManager {
        override fun getDefaultDisplay(): Display {
            return baseManager.defaultDisplay
        }

        override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
            try {
                baseManager.addView(view, params)
            } catch (e: WindowManager.BadTokenException) {
                Log.w("bwl", "add window failed", e)
            }
        }

        override fun updateViewLayout(view: View?, params: ViewGroup.LayoutParams?) {
            baseManager.updateViewLayout(view, params)
        }

        override fun removeView(view: View?) {
            baseManager.removeView(view)
        }

        override fun removeViewImmediate(view: View?) {
            baseManager.removeViewImmediate(view)
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读