Toast Exception : java.lang.IllegalStateException: View has already been added to the window manager.
Crash堆栈如下:
Exception:java.lang.IllegalStateException: View com.autonavi.skin.view.SkinRelativeLayout{95730 V.E...... ......I. 0,0-0,0} has already been added to the window manager. at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:328) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93) at android.widget.Toast$TN.handleShow(Toast.java:498) at android.widget.Toast$TN$1.handleMessage(Toast.java:401) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6494) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
在项目中显示Toast用到了自定义布局mLayContent,而mLayContent是重复利用的。代码示例:
Toast toast = new Toast(this); toast.setDuration(1000); toast.setView(mLayContent);
纯粹从发生Crash的异常堆栈来分析,针对这种异常,常规处理是在设置Toast布局前先检测下mLayContent父布局是否为空,如果不为空则从父布局中移除掉mLayContent布局。然而,这个方法在这边并没什么用。下面对Toast显示的源码逻辑处理进行分析。
一、Toast.java类源码分析
public void handleShow(IBinder windowToken) { if (mView != mNextView) { // remove the old view if necessary handleHide(); mView = mNextView; Context context = mView.getContext().getApplicationContext(); String packageName = mView.getContext().getOpPackageName(); if (context == null) { context = mView.getContext(); } mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); if (mView.getParent() != null) { mWM.removeView(mView); } mWM.addView(mView, mParams); trySendAccessibilityEvent(); } } public void handleHide() { if (mView != null) { if (mView.getParent() != null) { mWM.removeViewImmediate(mView); } mView = null; } }
上面是Toast显示和隐藏的简化代码,从代码里面可以看到,显示和隐藏的时候都有对当前添加的布局是否含有父布局进行检查,如果已经添加过布局,则从WindowManager中移除这个布局。
在Crash发生之前,查看Log可以看到Toast的显示和隐藏操作非常频繁,频繁操作导致时序上的问题,在上层添加是否含有父布局的判断是没用的。到framework层WindowManager显示Toast的时候一样会报异常。
二、WindowManagerGlobal.java类源码分析
Toast显示的布局是通过WindowManager接口去添加的,找到继承WindowManager接口的实现类WindowManagerImpl。可以看到WindowManagerImpl最终是通过WindowManagerGlobal单例对象来显示的。
接着看WindowManagerGlobal里面的addView方法,可以看到抛出
" has already been added to the window manager."
异常的地方。addView方法简化代码如下:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException(“view must not be null”);
}
if (display == null) {
throw new IllegalArgumentException(“display must not be null”);
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException(“Params must be WindowManager.LayoutParams”);
}
synchronized (mLock) {
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don’t wait for MSG_DIE to make it’s way through root’s queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException(“View ” + view + ” has already been added to the window manager.”);
}
}
}
}
三、异常解决方法
既然是时序上的问题导致布局重复添加异常,解决方法很简单,不进行布局复用,每次显示Toast之前都用新的Inflate出来的布局就行。
并不是每个Android平台或车机上都会出现这个问题,依赖出现异常的车机或系统做下特别的适配处理。
四、其他参考资料:
扩展阅读:
转载请注明出处:陈文管的博客 – Toast 自定义布局重复添加异常分析
扫码或搜索:文呓
微信公众号 扫一扫关注