1、android.view.WindowManager$BadTokenException
Unable to add window -- token android.os.BinderProxy@a6295bb is not valid; is your activity running?
android.view.ViewRootImpl.setView(ViewRootImpl.java:922)
android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:377)
android.view.WindowManagerImpl.addView(WindowManagerImpl.java:105)
android.widget.Toast.handleShow(Toast.java:747)
android.widget.Toast.handleMessage(Toast.java:622)
android.os.Handler.dispatchMessage(Handler.java:102)
android.os.Looper.loop(Looper.java:154)
android.app.ActivityThread.main(ActivityThread.java:6823)
java.lang.reflect.Method.invoke(Native Method)
该异常表示不能添加窗口,通常是所要依附的view已经不存在导致的。我抓取了特殊一行Toast,说明这个bug是由toast导致的;网上很多BadTokenException异常处理是针对dialog的,这里不是哈;这个bug的机型主要集中在android 6.0,7.1.1,7.1.2 .在网上找到了解决方案:
这个问题由于targetSDKVersion升到26之后,在7.1.1机型上概率性出现。稳定复现的步骤是,在Toast.show()之后,UI线程做了耗时的操作阻塞了Handler message的处理,如使用Thread.sleep(5000),然后这个崩溃就出现了。原因是7.1.1系统对TYPE_TOAST的Window类型做了超时限制,绑定了Window Token,最长超时时间是3.5s,如果UI在这段时间内没有执行完,Toast.show()内部的handler message得不到执行,NotificationManageService那端会把这个Toast取消掉,同时把Toast对于的window token置为无效。等App端真正需要显示Toast时,因为window token已经失效,ViewRootImpl就抛出了上面的异常。
Android 8.0上面,google意识到这个bug,在Toast的内部加了try-catch保护。目前只有7.1.1上面的Toast存在这个问题,崩溃在系统源码里。APP层可以通过自定义Toast类,反射替换TN的内部成员变量mHandler,从而添加try-catch做到workaround,所有使用Toast的地方都使用这个自定义的,不要直接使用系统原生的。
安全的Toast 参考方案:https://github.com/drakeet/ToastCompat
2、 java.lang.NullPointerException
Attempt to invoke virtual method 'void com.xxxx.b.dm.d()' on a null object reference
com.xxx.xxxFragment$8.run(xxxFragment.java:391)
android.os.Handler.handleCallback(Handler.java:808)
android.os.Handler.dispatchMessage(Handler.java:101)
android.os.Looper.loop(Looper.java:166)
android.app.ActivityThread.main(ActivityThread.java:7425)
代码中是这样写的:
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mPresenter.getPreAuthStatus();
}
}, 1000);
看似没什么问题,但如果用户这样操作:刚进行这个延时的时候,用户如果突然退出页面,这个时候getPreAuthStatus()方法并没有执行,然后mPresenter对象跟随页面销毁而清空,但等1秒后执行mPresenter.getPreAuthStatus();方法时突然发现找不到mPresenter对象,空指针异常;针对这个问题有两种解决办法,1、在mPresenter.getPreAuthStatus();调用前做非空判断,这是最简单的,但却不是好的解决方案 。 2、重写延时代码,在当前页面的onDestroy()中关掉回调方法,移除执行的资源,和页面生命周期绑定,这才是handler正确的写法。如下:
@Override
public void onDestroy() {
delayedHandler.removeCallbacks(runnable);
super.onDestroy();
}
private Handler delayedHandler = new Handler();
//在ondestroy()关闭延时器,同步生命周期
private Runnable runnable = () -> mPresenter.getPreAuthStatus();
delayedHandler.postDelayed(runnable, 1000);