Android开发进阶经验分享Android

'Cannot get a dirty matrix!'

2021-10-30  本文已影响0人  ygx211

前言

工作中遇到了一个比较难以复现的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,修改成在主线程调用后,连续自动化测试一个多月已经没有再复现了

上一篇下一篇

猜你喜欢

热点阅读