Android ViewGroup 崩溃:尝试在空对象引用上读取字段'int android.view.View.mViewFlags'。

75
我们在后端日志监控中发现了几个这种崩溃的案例。似乎这些崩溃与特定的用户体验故障无关。从报告中可以看出,我们自己的类没有参与其中(没有任何我们的类名的迹象)。以下是一个典型崩溃的示例:
java.lang.NullPointerException: Attempt to read from field 'int android.view.View.mViewFlags' on a null object reference 
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3357) 
at android.view.View.updateDisplayListIfDirty(View.java:14288) 
at android.view.View.getDisplayList(View.java:14315) 
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3549) 
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3528) 
at android.view.View.updateDisplayListIfDirty(View.java:14253) 
at android.view.View.getDisplayList(View.java:14315) 
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3549) 
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3528) 
at android.view.View.updateDisplayListIfDirty(View.java:14253) 
at android.view.View.getDisplayList(View.java:14315) 
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3549) 
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3528) 
at android.view.View.updateDisplayListIfDirty(View.java:14253) 
at android.view.View.getDisplayList(View.java:14315) 
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3549) 
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3528) 
at android.view.View.updateDisplayListIfDirty(View.java:14253) 
at android.view.View.getDisplayList(View.java:14315) 
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3549) 
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3528) 
at android.view.View.updateDisplayListIfDirty(View.java:14253) 
at android.view.View.getDisplayList(View.java:14315) 
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3549) 
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3528) 
at android.view.View.updateDisplayListIfDirty(View.java:14253) 
at android.view.View.getDisplayList(View.java:14315) 
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3549) 
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3528) 
at android.view.View.updateDisplayListIfDirty(View.java:14253) 
at android.view.View.getDisplayList(View.java:14315) 
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3549) 
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3528) 
at android.view.View.updateDisplayListIfDirty(View.java:14253) 
at android.view.View.getDisplayList(View.java:14315) 
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3549) 
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3528) 
at android.view.View.updateDisplayListIfDirty(View.java:14253) 
at android.view.View.getDisplayList(View.java:14315) 
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:273) 
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:279) 
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:318) 
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2561) 
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2377) 
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2007) 
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1086) 
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6453) 
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:846) 
at android.view.Choreographer.doCallbacks(Choreographer.java:647) 
at android.view.Choreographer.doFrame(Choreographer.java:601) 
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:829) 
at android.os.Handler.handleCallback(Handler.java:739) 
at android.os.Handler.dispatchMessage(Handler.java:95) 
at android.os.Looper.loop(Looper.java:135) 
at android.app.ActivityThread.main(ActivityThread.java:5254) 
at java.lang.reflect.Method.invoke(Native Method) 
at java.lang.reflect.Method.invoke(Method.java:372) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:927) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:713) 

有没有人知道在Android代码中是否有相关的漏洞已记录?


3
我想补充一点,当我使用SwipeRefreshLayout和空的RecyclerView进行下拉刷新时,我一直遇到这个问题。 - MathieuMaree
@MathieuMaree 你有检查 https://github.com/worker8/TourGuide/pull/34 链接吗?作者似乎在他的情况下找到了一种解决方法 - 谁知道,也许它也能帮助你。 - Konstantin Loginov
2
请提供可用于重现此崩溃的代码。 - gio
11个回答

31

可能的解决方案

我曾经遇到过同样的问题。我设置了一个animation,在onAnimationEnd中删除被动画化的对象时出现了问题。我的解决方法是设置一个异步的Runnable,在动画停止后等待100毫秒,然后再移除被动画化的对象:

之前被动画化的对象是this._loader

private void removeLoader() {
    final ContentContainer self = this; // "CustomContainer" needs to match the type of `this`
    Handler h = new Handler();
    h.postAtTime(new Runnable() {
        @Override
        public void run() {
            MainActivity.instance.runOnUiThread(new Runnable() { 
                @Override
                public void run() {
                    try {
                        if(self._loader == null) {
                            // there is no loader. quit now while you still have the chance!!
                            return;
                        }
                        while(self._loader.getParent() != null) {
                            removeView(self._loader);
                        }
                    } catch(Exception e) {
                        Crashlytics.logException(e);
                        e.printStackTrace();
                    }

                    self._loader = null;
                }
            });
        }
    }, 100);
}

干杯


@DeanWild 这是一个彻头彻尾的hack,我同意。我相信有一种线程安全/正确的方法来做到这一点,但我敢说,为了得到一个“适当”的解决方案所需的时间和创造力超过了快速hack的好处,但我以前也错过了... - Jacksonkr
2
@Jacksonkr 感谢您的回答。在我的情况下,将 removeView 调用放入一个 runnable 中,并在相同视图上 postrunnable 可以解决问题。这感觉像是一个“更合适”的解决方案,因为没有涉及到延迟。 - Daniel F
你跳过了 runOnUiThread 吗?根据设置,这可能会让人陷入麻烦。 - Jacksonkr
1
通过使用postAtTime(),您无意中从未调用可运行项。我相信您想使用postDelayed(),但是这解决了您的问题,这让我相信这根本不是解决方案;您实际上正在保留视图的内存。对于在RecyclerView中动画化视图等用例,这真的不是一个解决方案。 - Paul Lammertsma

18

我曾遇到同样的问题,我使用Handler解决了它。

new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                   // remove fragment from here
                }
            });

非常不明显的错误信息,但在我的情况下是在AnimationListener中删除了视图。将其发布到主线程以解决问题。 - David Berry

17
问题出在ViewGroupdispatchDraw()方法中。该方法尝试绘制所有ViewGroup的子节点,当一个子节点为null时,会导致异常,最有可能来自于这一行if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {(请注意mViewFlags)。
因此问题是你的某个视图没有正确初始化。恐怕这就是我能做的最好的了。

4
我们突然也开始遇到了这个错误。经过追踪发现,片段动画是问题所在。更具体地说,当使用自定义动画与 replace() 在一次片段事务中时,此问题会出现,并且应用程序是针对大于Local Maven repository for Support Libraries rev> 26构建的。
可能的解决方法是将Local Maven repository for Support Libraries降级为rev 26。请参见这里

2

可能的原因: 我遇到了完全相同的问题。后来发现,当我在onDraw()调用中添加修改视图树的代码时,它开始出现这种情况。具体来说,当满足某些条件时,在我的派生onDraw()中删除了一个带有子项的视图。我现在认为这是一件不好的事情,可能是因为平台正在尝试绘制我已从视图树中删除的视图。通过将删除操作发布到Runnable中,在onDraw()调用完成后再执行删除操作,我解决了这个问题。


1

0

虽然这样做不太好看也不是最佳实践,但我能够可靠地工作的唯一方法就是在dispatchDraw()中捕获异常,如下所示:

override fun dispatchDraw(canvas: Canvas?) {
        /*
         * We're doing this because of the below exception that is out of our control:
         * java.lang.NullPointerException: Attempt to read from field
         * 'int android.view.View.mViewFlags' on a null object reference at
         * android.view.ViewGroup.dispatchDraw(ViewGroup.java:4111)
         */
        try {
            super.dispatchDraw(canvas)
        } catch (e: NullPointerException) {

        }
    }

只需确保您所期望的行为正常工作,并且在执行此操作时不会破坏其他内容。 再次强调,这并不是理想的解决方案,但这是我能够找到的唯一可行的方法,并且我非常确定它不会破坏其他任何内容。

祝您平安 :)


0

这是一个线程问题。可能是因为您正在刷新您的ViewPager或其他适配器。

我一直在面对这个问题,发现如果将其放置在您的Activity的UI线程中,它就会正常渲染。

activity?.runOnUiThread{
   // Add Your UI Updating Methods Here
}

0

我试图从片段A导航到片段B,但片段A是一个需要来自片段B数据的表单。

因此,当我尝试在不填写A的情况下导航时,它会抛出异常。

而且,即使A与数据B无关,它也会抛出异常。

我不知道为什么,但我添加了一个条件,用户必须在导航之前填写表单,这解决了问题。


0
重写dispatchDraw方法并在其中加入try/catch块,如下所示:
public void dispatchDraw(Canvas c)
    {
        try
        {
            super.dispatchDraw(c);
            return;

        }
        catch(Exception exception)
        {
            return;
        }
    }

永远不要这样做!也许会发生不好的事情。 - kaitian521
我在几年前的一个生产应用程序中做过这个。直到今天它一直运行良好。 - Pierpaolo Pierpaoli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接