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

看到日志很蒙蔽啊,第一眼看我只是掉了个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 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时对于发生在系统级别的代码,可能会抱有一种难解的心理或者无法解,而有时只要去看看源码梳理下流程一般都可以解出来。

更多相关文章

  1. Android重启应用和重启手机
  2. Android——简单对话框
  3. Android调用第三方App
  4. Android实现模拟点击的一种方法
  5. Android调用相机相册并显示照片在ImageView
  6. Android(安卓)中三种使用线程的方法
  7. Android显示网络图片相关实现方法浅谈
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. PHP:PDO->fetch()和fetchAll()遍历,sessio
  2. 【PHP文件操作】文件目录操作基本操作及
  3. 【PHP会话控制】理解会话控制cookie和ses
  4. 数据库查询构造器
  5. TF卡里面的文件名目录名或卷标语法不正确
  6. mysql数据库ddl与dml语言实操
  7. 名词解释:外贸推广人员口中的谷歌SEO是什
  8. Java 给 Word 添加数字签名
  9. 变换Mac上的墙纸的4种方法
  10. LDAP/SASL/GSSAPI/Kerberos编程API(5)--k