'Cannot get a dirty matrix!'

前言

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容