'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)
/frameworks/base/libs/hwui/RenderProperties.h (androidxref.com)
从以上的源码可以看出当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之前调用的
崩溃时,从代码上看 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,修改成在主线程调用后,连续自动化测试一个多月已经没有再复现了