'Cannot get a dirty matrix!'

作者: ygx211 | 来源:发表于2021-10-30 00:11 被阅读0次

    前言

    工作中遇到了一个比较难以复现的crash:'Cannot get a dirty matrix!', 自己花了时间去分析并找到了原因和规避方案,在此记录一下,也希望能给遇到这个问题的朋友提供点思路;

    崩溃信息

    这是一个framework native的崩溃,指向的是系统API堆栈,但其实是应用使用API不当造成的,崩溃的完整堆栈如下:


    crash堆栈

    以上是我自己编写的一个demo复现到crash时的堆栈,可以看到这是发生在非主线程的crash, 应用在非主线程调用了View#getLocationOnScreen, 一直到native的代码android::android_view_RenderNode_getTransformMatrix(long, long) 主动抛出来的异常;

    分析

    我们在androidxref上搜一下android_view_RenderNode_getTransformMatrix 这个函数,源码在
    /frameworks/base/core/jni/android_view_RenderNode.cpp (androidxref.com)

    image.png

    /frameworks/base/libs/hwui/RenderProperties.h (androidxref.com)

    image.png
    从以上的源码可以看出当mPrimitiveFields.mMatrixOrPivotDirty为true时候会主动抛出异常,那找到mPrimitiveFields.mMatrixOrPivotDirty何处设置为true何处设置为false尤为关键; image.png
    全局搜索mMatrixOrPivotDirty 发现主要是在RenderProperties.cpp和RenderProperties.h中会被修改,而且修改为false的只有一个地方,其他地方全部是修改为true的,找到是在RenderProperties.cpp的 updateMatrix函数中赋值为false的,而且从代码逻辑上看,只要执行了updateMatrix()函数,mMatrixOrPivotDirty 保证会是false
    image.png

    来看看哪里在调用updateMatrix, 全局搜索后发现前面我们所说的android_view_RenderNode.cpp中有调用updateMatrix,


    image.png

    点进去看发现android_view_RenderNode_getTransformMatrix的这个函数也调用了,而且是在getTransformMatrix之前调用的

    image.png
    崩溃时,从代码上看 updateMatrix将mMatrixOrPivotDirty设置或确保为false, 什么都没做,到getTransformMatrix的时候mMatrixOrPivotDirty却变成了true, 说明在此期间其他线程去把mMatrixOrPivotDirty从false修改为了true, 即出现了多线程访问和修改变量 mMatrixOrPivotDirty 导致 在getTransformMatrix的时候主动抛出了异常Cannot get a dirty matrix!

    前面说过,RenderProperties.cpp和RenderProperties.h中有很多把mMatrixOrPivotDirty 设置为true的地方,是在一些RenderProperties.h setPivotY/setPivotX/setTop/setRight/setLeft/setBottom等函数中进行设置的,再看看发现android_view_RenderNode中有调用setPivotY/setPivotX/setTop/setRight/setLeft/setBottom这些函数


    image.png

    而这些方法均对应了java层的android/view/RenderNode中的方法


    image.png

    java层的android/view/RenderNode中的native方法,很多方法在执行属性动画时都会被调用到,例如:
    Cross Reference: /frameworks/base/core/java/android/view/RenderNode.java (androidxref.com)

    View#setAlpha 》RenderNode#setAlpha 》RenderNode#nSetAlpha 》android_view_RenderNode.cpp的android_view_RenderNode_setRight函数;

    即主线程在执行动画时会去把mMatrixOrPivotDirty修改为true

    验证

    基于以上分析,这里推断出一种复现场景, 同一个View
    1.主线程执行View的属性动画,不断的去触发调用android_view_RenderNode的setPivotY/setPivotX/setTop/setRight/setLeft/setBottom函数,修改mMatrixOrPivotDirty为true
    2.在子线程中去调用这个View的getLocationOnScreen

    验证结果: app起来后不到两分钟就报了Cannot get a dirty matrix!,符合上述的分析

    代码如下,为了增加复现概率,以下代码我在两条非主线程中都调用了getLocationOnScreen

      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)
        
          glSurfaceView.setEGLContextClientVersion(2)
          glSurfaceView.setRenderer(object : GLSurfaceView.Renderer {
              override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {}
              override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {}
              override fun onDrawFrame(gl: GL10?) {
                  animView.getLocationOnScreen(mLocationArray)
              }
          })
    
          Thread() {
              run {
                  mbFlag = true
                
                  while (mbFlag) {
                      animView.getLocationOnScreen(mLocationArray)
                  }
              }
          }.start()
    
          glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
    
          var animatorScaleX = ObjectAnimator.ofFloat(animView, "scaleX", 1.0f, 0.0f)
          animatorScaleX.duration = 200
          animatorScaleX.repeatCount = ObjectAnimator.INFINITE
          animatorScaleX.repeatMode = ObjectAnimator.REVERSE
          animatorScaleX.startDelay = 150
    
          var animatorScaleY = ObjectAnimator.ofFloat(animView, "scaleY", 0.0f, 1.0f)
          animatorScaleY.duration = 300
          animatorScaleY.repeatCount = ObjectAnimator.INFINITE
          animatorScaleY.repeatMode = ObjectAnimator.REVERSE
          animatorScaleY.startDelay = 150
    
          var set = AnimatorSet()
          set.playTogether(animatorScaleX)
          set.start()
          mAnimator = set
    }
    
    override fun onDestroy() {
        super.onDestroy()
    
        mAnimator?.cancel()
        mAnimator = null
        mbFlag = false
    }
    

    结论和规避方案

    结论:在非主线程中调用了View#getLocationOnScreen, 当View本身也在执行动画或其他操作时,可能会出现多线程访问和修改变量 mMatrixOrPivotDirty 最终出发 在getTransformMatrix的时候主动抛出了异常Cannot get a dirty matrix!

    规避方案:
    不要在非主线程调用View#getLocationOnScreen

    我遇到的问题是在GL线程中调用了View#getLocationOnScreen,修改成在主线程调用后,连续自动化测试一个多月已经没有再复现了

    相关文章

      网友评论

        本文标题:'Cannot get a dirty matrix!'

        本文链接:https://www.haomeiwen.com/subject/rzywaltx.html