‘void android.view.View.dispatchDetachedFromWindow() on a null object reference’ 修复记录
线上报了一个```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 reference1 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时对于发生在系统级别的代码,可能会抱有一种难解的心理或者无法解,而有时只要去看看源码梳理下流程一般都可以解出来。
更多相关文章
- Android实现模拟点击的一种方法
- Android scrollview嵌套listview 滑动事件冲突的解决方法
- Android Studio sync 失败解决方法
- Android全屏的两种方法
- android SQLiteOpenHelper 对SQLite的操作