View not attached to window manager异常
开发的越多,遇到的问题也就越多。知识的圈子不断扩大,接触到的问题也就越多。
1、异常
Activity退出后,又进行了Dialog的操作会抛出下面的异常,这个异常比较少见。但是确实暴露出代码逻辑的不严谨,有待强化。
2、分析原因
根据异常的堆栈,从源码开始看。跳过前面的各种调度,直接从Dialog开始:
public void show() {
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
...
mWindowManager.addView(mDecor, l);
...
}mWindowManager是WindowManager类型,WindowManager是一个抽象的接口,真正的实现类是WindowManagerImpl,调用了其中的addView()方法(关于WindowManager参考《理解WindowManager》)。
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}mGlobal是WindowManagerGlobal类型对象,它的addView()方法如下:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
...
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}ViewRootImpl最终将Dialog中的View添加到视图中,通过setView()方法:
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
int res; /* = WindowManagerImpl.ADD_OKAY; */
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
...
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
mAdded = false;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
...
}中间省去大量的代码,这里判断Activity是否已经finish,如果添加给Dialog添加View依附的Activity已经结束,就抛出上面的异常。
3、解决办法
经过上面的分析,抛出这个问题的原因确实是和时序有一定的关系。
3.1、Activity退出时关闭
最好的做法就是在Activity退出的时候关闭依附于该Activity的Dialog。
@Override
protected void onDestroy() {
super.onDestroy();
if (null != mDialog && mDialog.isShowing()) {
mDialog.dismiss();
mDialog = null;
}
}并且有异步延时任务完成后也不再弹出Dialog,在需要弹出或者关闭Dialog的时候进行严谨的条件限制:
public void run() {
...
//子线程完成任务后切回主线程
//Dialog依附的Activity没有销毁,Dialog已初始化且没有弹出
if (activity.isFinishing() && null != mDialog && !mDialog.isShowing()) {
mDialog.show();
}
...
}3.2、try-catch
关闭Dialog弹窗的时候判断条件,并进行可能的异常捕捉。不过不建议这种不负责任的做法,不是所有的异常try就行了,要归根结底从根源上解决。
try {
mDialog.dismiss();
} catch (Exception e) {
}参考资料:
