当前位置: 首页 > 工具软件 > Viewa > 使用案例 >

‘void android.view.View.dispatchDetachedFromWindow() on a null object reference’ 修复记录

诸嘉澍
2023-12-01

线上报了一个```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)

看到日志很蒙蔽啊,第一眼看我只是掉了个dailogdismiss(),崩溃位置这不是系统view里报的么?怎么修???但是本着牛角尖精神还是去研究了下怎么修。

问题定位

看当方法调用堆栈可以发现掉了个dailogdismiss()后,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会调用一堆方法最后到ViewGroupdispatchDetachedFromWindow方法(具体可看源码),在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<OnAttachStateChangeListener> 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形式存在的,ActivityDialog/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时对于发生在系统级别的代码,可能会抱有一种难解的心理或者无法解,而有时只要去看看源码梳理下流程一般都可以解出来。

 类似资料: