Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

7.1.1设备Toast crash

panwj edited this page Dec 6, 2018 · 3 revisions

Android7.1.1Toast崩溃解决方案

问题

#1664 android.view.WindowManager$BadTokenException
Unable to add window -- window android.view.ViewRootImpl$W@4a51004 has already been added
android.view.ViewRootImpl.setView(ViewRootImpl.java:695)
android.view.ViewRootImpl.setView(ViewRootImpl.java:691)
android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
android.widget.Toast$TN.handleShow(Toast.java:506)
android.widget.Toast$TN2ドル.handleMessage(Toast.java:389)
android.os.Handler.dispatchMessage(Handler.java:102)
android.os.Looper.loop(Looper.java:154) android.app.ActivityThread.main(ActivityThread.java:6292) java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)

产生原因

首先Toast显示依赖于一个窗口,这个窗口被WMS管理(WindowManagerService),当需要show的时候这个请求会放在WMS请求队列中,并且会传递一个TN类型的Bider对象给WMS,WMS并生成一个token传递给Android进行显示与隐藏,但是如果UI线程的某个线程发生了阻塞,并且已经NotificationManager检测已经超时就不删除token记录,此时token已经过期,阻塞结束的时候再显示的时候就发生了异常。

发生版本

该问题仅发生在android 7.1.1 版本, 在该版本toast的显示实现是

mWM.addView(mView, mParams);

而在8.0的实现是

try {
   mWM.addView(mView, mParams);
   trySendAccessibilityEvent();
 } catch (WindowManager.BadTokenException e) {
  
}

解决方案

我们直接在 Toast.show 函数外增加 try-catch 是没有意义的。因为 Toast.show 实际上只是发了一条命令给 NotificationManager 服务。真正的显示需要等 NotificationManager 通知我们的 TN 对象 show 的时候才能触发。NotificationManager 通知给 TN 对象的消息,都会被 TN.mHandler 这个内部对象进行处理

//code Toast.java 
private static class TN {
 final Runnable mHide = new Runnable() {// 通过 mHandler.post(mHide) 执行
 @Override
 public void run() {
 handleHide();
 mNextView = null;
 }
 };
 final Handler mHandler = new Handler() {
 @Override
 public void handleMessage(Message msg) {
 IBinder token = (IBinder) msg.obj;
 handleShow(token);// 处理 show 消息
 }
 };
}

在NotificationManager 通知给 TN 对象显示的时候,TN 对象将给 mHandler 对象发送一条消息,并在 mHandler 的 handleMessage 函数中执行。 当NotificationManager 通知 TN 对象隐藏的时候,将通过 mHandler.post(mHide) 方法,发送隐藏指令。不论采用哪种方式发送的指令,都将执行 Handler 的 dispatchMessage(Message msg) 函数:

//code Handler.java
public void dispatchMessage(Message msg) {
 if (msg.callback != null) {
 handleCallback(msg);// 执行 post(Runnable)形式的消息
 } else {
 ...
 handleMessage(msg);// 执行 sendMessage形式的消息
 }
 }
因此,我们只需要在 dispatchMessage 方法体内加入 try-catch 就可以避免 Toast 崩溃对应用程序的影响:
public void dispatchMessage(Message msg) {
 try {
 super.dispatchMessage(msg);
 } catch(Exception e) {}
}

因此,我们可以定义一个安全的 Handler 装饰器:

private static class SafelyHandlerWarpper extends Handler {
 private Handler impl;
 public SafelyHandlerWarpper(Handler impl) {
 this.impl = impl;
 }
 @Override
 public void dispatchMessage(Message msg) {
 try {
 super.dispatchMessage(msg);
 } catch (Exception e) {}
 }
 @Override
 public void handleMessage(Message msg) {
 impl.handleMessage(msg);//需要委托给原Handler执行
 }
}
由于 TN.mHandler 对象复写了 handleMessage 方法,因此,在 Handler 装饰器里,需要将 handleMessage 方法委托给 TN.mHandler 执行。定义完装饰器之后,我们就可以通过反射往我们的 Toast 对象中注入了:
public class ToastUtils {
 private static Field sField_TN ;
 private static Field sField_TN_Handler ;
 static {
 try {
 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) {}
 }
 private static void hook(Toast toast) {
 try {
 Object tn = sField_TN.get(toast);
 Handler preHandler = (Handler)sField_TN_Handler.get(tn);
 sField_TN_Handler.set(tn,new SafelyHandlerWarpper(preHandler));
 } catch (Exception e) {}
 }
 public static void showToast(Context context,CharSequence cs, int length) {
 Toast toast = Toast.makeText(context,cs,length);
 hook(toast);
 toast.show();
 }
}

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /