前言
工作中遇到了一個比較難以復現的crash:'Cannot get a dirty matrix!', 自己花了時間去分析并找到了原因和規避方案,在此記錄一下,也希望能給遇到這個問題的朋友提供點思路;
崩潰信息
這是一個framework native的崩潰,指向的是系統API堆棧,但其實是應用使用API不當造成的,崩潰的完整堆棧如下:
以上是我自己編寫的一個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)
/frameworks/base/libs/hwui/RenderProperties.h (androidxref.com)
從以上的源碼可以看出當mPrimitiveFields.mMatrixOrPivotDirty為true時候會主動拋出異常,那找到mPrimitiveFields.mMatrixOrPivotDirty何處設置為true何處設置為false尤為關鍵;
全局搜索mMatrixOrPivotDirty 發現主要是在RenderProperties.cpp和RenderProperties.h中會被修改,而且修改為false的只有一個地方,其他地方全部是修改為true的,找到是在RenderProperties.cpp的 updateMatrix函數中賦值為false的,而且從代碼邏輯上看,只要執行了updateMatrix()函數,mMatrixOrPivotDirty 保證會是false
來看看哪里在調用updateMatrix, 全局搜索后發現前面我們所說的android_view_RenderNode.cpp中有調用updateMatrix,
點進去看發現android_view_RenderNode_getTransformMatrix的這個函數也調用了,而且是在getTransformMatrix之前調用的
崩潰時,從代碼上看 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這些函數
而這些方法均對應了java層的android/view/RenderNode中的方法
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,修改成在主線程調用后,連續自動化測試一個多月已經沒有再復現了