线上报了一个```Attempt to invoke virtual method ‘void android.view.View.dispatchDetachedFromWindow()’ on a null object reference``bug,日志如下
Attempt to invoke virtual method 'void android.view.View.dispatchDetachedFromWindow()' on a null object reference
1 android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3867)
2 android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3867)
3 android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3867)
4 android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3867)
5 android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3867)
6 android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3867)
7 android.view.ViewRootImpl.dispatchDetachedFromWindow(ViewRootImpl.java:4061)
8 android.view.ViewRootImpl.doDie(ViewRootImpl.java:7160)
9 android.view.ViewRootImpl.die(ViewRootImpl.java:7133)
10 android.view.WindowManagerGlobal.removeViewLocked(WindowManagerGlobal.java:509)
11 android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:447)
12 android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:124)
13 android.app.Dialog.dismissDialog(Dialog.java:410)
14 android.app.Dialog.dismiss(Dialog.java:393)
看到日志很蒙蔽啊,第一眼看我只是掉了个dailog
的dismiss()
,崩溃位置这不是系统view里报的么?怎么修???但是本着牛角尖精神还是去研究了下怎么修。
看当方法调用堆栈可以发现掉了个dailog
的dismiss()
后,dailog
会触发window的
removeViewImmediate
方法,如下:
void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}
if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}
try {
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
然后WindowManager
会调用一堆方法最后到ViewGroup
的dispatchDetachedFromWindow
方法(具体可看源码),在ViewGroup``````dispatchDetachedFromWindow
方法如下,
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
// 先调用自己的
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
// 递归调用每个child的dispatchAttachedToWindow方法
// 典型的深度优先遍历
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}
可以看到里面有个操作就是便利子view调用dispatchAttachedToWindow
方法,而报错的位置也就在这里,从报错信息可以看到是子view为空,但是为什么会为空?这里就要继续看子view的dispatchAttachedToWindow
方法了:
// View中的实现:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
//System.out.println("Attached! " + this);
mAttachInfo = info;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);
}
// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
// As all views in the subtree will already receive dispatchAttachedToWindow
// traversing the subtree again here is not desired.
onVisibilityChanged(this, visibility);
if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
// If nobody has evaluated the drawable state yet, then do it now.
refreshDrawableState();
}
needGlobalAttributesUpdate(false);
}
看到这个方法时有点头绪了,因为看到了我们熟悉的方法onAttachedToWindow()
了,我猜测是不是我在当前window下的某个view的onAttachedToWindow()
方法里做了什么操作?
(为什么说是前window
下的某个view
而不是某个Activity
?因为了解window机制的都知道,android
界面展示是已window
形式存在的,Activity
、Dialog/Toast
等都是依托在window
上的)
而我的这个bug是在dialog上爆出的,所以我去对应的dialog上找有没有子view
重写了onAttachedToWindow()
方法,果不其然有个子view
重写了,并且在里面做了removeView的操作:
if (rootView != null && mBlowRootView != null) {
rootView.removeView(mBlowRootView);
}
看到这里就可以大致找到原因了,是我移除了view
导致ViewGroup
获取的子view
为空了
像一般Attempt to invoke virtual method 'void android.view.View.dispatchDetachedFromWindow()' on a null object reference
这种bug发生,可能原因是因为我们在view进行自己的销毁时提前做了销毁操作,导致为空。
像一般我们日常解bug时对于发生在系统级别的代码,可能会抱有一种难解的心理或者无法解,而有时只要去看看源码梳理下流程一般都可以解出来。