簡(jiǎn)單的修復(fù)方案,不用反射,也不用編譯期修改代碼
Demo地址:https://github.com/WLHere/SafeToast
Toast在android 7.1.1經(jīng)常有如下崩潰
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.SafeToastServiceTN.handleShow(Toast.java:502)
at android.widget.Toast2.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)
問(wèn)題根源:NotificationManagerService通知Toast show后,會(huì)發(fā)送一個(gè)延遲消息(延遲2s或3.5s)取消token。如果因?yàn)閁I線程卡頓導(dǎo)致Toast沒(méi)有及時(shí)顯示,待Toast顯示時(shí)就會(huì)出現(xiàn)這個(gè)問(wèn)題。
下邊介紹一種不用反射和編譯動(dòng)態(tài)修改代碼的修復(fù)方案。
Toast.TN.handleShow是從applicationContext獲取WindowManager。我們重寫(xiě)Application的getSystemService方法,對(duì)于從Toast來(lái)的調(diào)用返回兼容的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崩潰問(wèn)題
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)
}
}
}