'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,修改成在主線程調用后,連續自動化測試一個多月已經沒有再復現了

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容