Android ViewGroup 崩溃:尝试从空对象引用上的字段“int android.view.View.mViewFlags”读取

Posted

技术标签:

【中文标题】Android ViewGroup 崩溃:尝试从空对象引用上的字段“int android.view.View.mViewFlags”读取【英文标题】:Android ViewGroup crash: Attempt to read from field 'int android.view.View.mViewFlags' on a null object reference 【发布时间】:2016-01-19 11:22:45 【问题描述】:

我们发现了几个后端日志监控报告的此类崩溃案例。崩溃似乎与特定的 UX 故障无关。而且从报告中看,没有迹象表明我们自己的课程是如何参与的(没有任何我们的课程名称的迹象)。以下是典型崩溃的示例:

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 代码的相关错误记录?

【问题讨论】:

我想补充一点,在使用 SwipeRefreshLayout 和空的 RecyclerView 刷卡刷新时,我一直在体验它 @MathieuMaree 你检查过github.com/worker8/TourGuide/pull/34 链接吗?似乎作者在他的案例中找到了解决方法 - 谁知道呢,也许它也会对您有所帮助 请提供可用于重现此崩溃的代码 【参考方案1】:

可能的解决方案

我也有同样的问题。我设置了一个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 我同意,这完全是骇客。我确信有一种安全/正确的方法可以做到这一点,但我敢说“正确”解决方案所需的时间和创造力超过了快速破解的好处,但我之前错了...... @Jacksonkr 感谢您的回答。在我的情况下,只需将removeView 调用放在runnable 中,即posted 在同一视图上调用removeView 即可解决问题。这感觉像是一个“更合适”的解决方案,因为不涉及任何延迟。 您要跳过runOnUiThread 吗?取决于可能使人陷入热水的设置。 通过使用postAtTime() 你无意中做的事情,永远不会调用runnable。我确定您的意思是postDelayed(),但是这解决了您的问题这一事实使我相信这根本不是解决方案;您有效地将视图保存在内存中。对于 RecyclerView 中的动画视图等用例来说,这确实不是一个解决方案。【参考方案2】:

我遇到了同样的问题。我用 Handler 解决了。

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

【讨论】:

从错误消息中非常不明显,但在我的情况下,它正在删除 AnimationListener 中的视图。发布它以返回主线程解决了问题。【参考方案3】:

问题出在ViewGroupdispatchDraw() 方法中。此方法尝试绘制所有ViewGroup 的孩子。当一个孩子是null 时,你会得到一个异常,它很可能来自this line:if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) (注意mViewFlags)。

所以问题是您的某个视图没有正确初始化。恐怕这是我能做的最好的了。

【讨论】:

【参考方案4】:

我们也开始意外地收到此错误。它被追踪到片段动画是问题所在。更具体地说,当针对 Local Maven repository for Support Libraries rev > 26 构建应用时,在片段事务中使用带有 replace() 的自定义动画。

可能的解决方案

Local Maven repository for Support Libraries 降级到第 26 版。请参阅here

【讨论】:

【参考方案5】:

可能的原因: 我遇到了完全相同的问题。事实证明,当我在 onDraw() 调用中添加代码来修改视图树时,它就开始发生了。具体来说,当满足某些条件时,我在派生的 onDraw() 中删除了一个带有子视图的视图。我现在认为这是一件坏事,可能是因为平台正在尝试绘制我现在从视图树中删除的视图。我通过在对 onDraw() 的调用完成后使用 Runnable 发布删除来解决了这个问题。

【讨论】:

【参考方案6】:

重写 dispatchDraw 方法并在其中放入一个 try/catch 块,如下所示:

public void dispatchDraw(Canvas c)
    
        try
        
            super.dispatchDraw(c);
            return;

        
        catch(Exception exception)
        
            return;
        
    

【讨论】:

【参考方案7】:

虽然这很丑陋而且不是好的做法,但我唯一能可靠地工作的就是在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) 

        
    

只需确保您想要的行为正常工作,并且这样做不会破坏其他东西。 同样,不理想,但这是我唯一可以开始工作的事情,而且我绝对确定在我的情况下它不会破坏其他任何东西。

愿你平安:)

【讨论】:

【参考方案8】:

我试图从 Fragment A 导航到 Fragment B,而 Fragment A 是一个需要来自 Fragment 的数据的表单B

所以当我尝试在不填写 A 的情况下进行导航时,它抛出了这个异常。

此外,即使 A 独立于数据 B,它也会抛出此异常。

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

【讨论】:

【参考方案9】:

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

一直在面对这个问题,并意识到如果将它放在Activity 的 UI 线程中,那么它会呈现得很好。

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

【讨论】:

【参考方案10】:

不需要延迟/线程。请通读... 这篇文章很旧,但对于未来的读者来说,它会有所帮助。 我详细解释了我遇到并解决的用例的实际情况。

我有一个 viewflipper,它使用简单的滑入/滑出动画在其子项中翻转下一个和上一个动画。我需要在翻转完成后从 viewflipper 中删除一个视图,并确保它在正确的时间发生,滑入式动画有一个侦听器,我可以在其中删除视图。一切都非常适合翻转到下一个 而翻转到上一个抛出了上述异常。经过反复试验,结果是:

当翻到下一个时,顺序如下:

1-slide-out starts
2-slide-in starts
3-slide-out finishes
4-slide-in finishes

所以在滑入式上设置监听器是正确的,因为两个动画都保证在那里完成。

但是当翻到上一个时,顺序如下:

1-slide-in starts
2-slide-out starts
3-slide-in finishes
4-slide-out finishes

如您所见,当滑入完成时,滑出尚未完成,因此会发生异常。我只是将监听器设置为滑出式,效果很好。

虽然所有其他答案都要求延迟删除视图(这当然有助于确保并发动画全部完成),但您可以看到情况并非如此。

作为一般规则,当您在视图上运行一组动画时(即使持续时间完全相同) - 尽管普遍认为它们会一起完成 - 但它们不会(请记住线程是几乎并发)。

【讨论】:

以上是关于Android ViewGroup 崩溃:尝试从空对象引用上的字段“int android.view.View.mViewFlags”读取的主要内容,如果未能解决你的问题,请参考以下文章

尝试从空对象引用上的字段“android.view.View androidx.recyclerview.widget.RecyclerView$b0.a”读取

Activity 的 Android ViewBinding 需要 ViewGroup

Android:从视图中获取视图组?

Android:在drawerlayout中使用地图膨胀片段时出错

从空值 Laravel 8 创建默认对象

ViewGroup 动画不工作