Toast 自定义布局重复添加异常分析

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显示的源码逻辑处理进行分析。

1、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的时候一样会报异常。

2、WindowManagerGlobal.java类源码分析

Toast显示的布局是通过WindowManager接口去添加的,找到继承WindowManager接口的实现类WindowManagerImpl。可以看到WindowManagerImpl最终是通过WindowManagerGlobal单例对象来显示的。

WindowManager addView方法

接着看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 .”);

}

}

}

}

3、异常解决方法

既然是时序上的问题导致布局重复添加异常,解决方法很简单,不进行布局复用,每次显示Toast之前都用新的Inflate出来的布局就行。

4、其他参考资料:

Toast源码深度分析

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章