这篇博文是为了解决一个问题,在之前的博客已经提过了,就是属性动画的执行结束的回调,不是在主线程。
首先,阅读这篇文章,大家一定要有一个基础,就是,android不一定只能在主线程才能更新UI。这方面我不多说,想多了解的同学大家自己去查阅博客。
下面回到我们这个问题,在探究源码前,我们先来验证一下
(多说一句,android动画相当在计算位置坐标的时候相当蛋疼,所以楼主一直不愿意做涉及到ui的动画,但是ConstraintLayout给了一个直接通过xml可以执行的动画,相当爽,晚点咱们再聊聊)
代码很简单,就不写了,就是在普通的线程里面,执行动画,毫无意外崩溃了,接下来再看看报错
没错,报错了,但是,提示的却不是必须在UI线程调用,而是,线程里面必须又Looper?
大家有没有想到,哪个线程里面是有Looper的呢?没错,就是HandlerThread,这是google给我封装好的线程,我们来试试。
val mhandler = Handler(handlerT.looper);
mhandler.post() {
iv.animate().scaleX(2f).scaleY(2f).setDuration(1000)
Thread.sleep(100000)
// ObjectAnimator.ofInt(constraintlayou_root, "translationX", 0, 100, 0, 100)
// .setDuration(3000)
// .start();
}
毫不意外,这里顺利运行了。关于animate()大家可以了解下,是Google给我们封装好的动画,很好用。并且,大家可以发现,我再这个线程里面调用了,Thread.sleep,但是,动画完全没有任何影响。这是为什么呢?
下面就要进入我们的源码分析时间了。
先给出一个写的比较好的博客
Android 属性动画详解与源码分析。
但这这个博客存在一个问题,就是分析到jni或者反射调用后就没有接着往下分析了。所以楼主不会从0开始继续分析这个地方,而是调出关键节点,梳理清楚,这样在很长一段时间后,不需要看长篇大论的源码,就能快速回忆起关键的地方。
首先,略过中间部分不谈,最关键的,就是,通过jni或者反射调用到scaleX之后,又发生了什么。
这里给一个debug栈的截图
可以看出,这里走的是jni调用。然后走到了invalidateViewProperty(),该函数如下
/**
* Quick invalidation for View property changes (alpha, translationXY, etc.). We don't want to
* set any flags or handle all of the cases handled by the default invalidation methods.
* Instead, we just want to schedule a traversal in ViewRootImpl with the appropriate
* dirty rect. This method calls into fast invalidation methods in ViewGroup that
* walk up the hierarchy, transforming the dirty rect as necessary.
*
* The method also handles normal invalidation logic if display list properties are not
* being used in this view. The invalidateParent and forceRedraw flags are used by that
* backup approach, to handle these cases used in the various property-setting methods.
*
* @param invalidateParent Force a call to invalidateParentCaches() if display list properties
* are not being used in this view
* @param forceRedraw Mark the view as DRAWN to force the invalidation to propagate, if display
* list properties are not being used in this view
*/
@UnsupportedAppUsage
void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
if (!isHardwareAccelerated()
|| !mRenderNode.hasDisplayList()
|| (mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
if (invalidateParent) {
invalidateParentCaches();
}
if (forceRedraw) {
mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
}
invalidate(false);
} else {
damageInParent();
}
}
根据后面的堆栈来判断,走的是damageInParent()方法,为什么会走这里呢?
说到这里,楼主又想起来一个知识点,那就是,子线程调用invalidate,一定会崩溃吗?
这里给出一个知识点,关于invalidate和requestLayout的区别了。网上有一个博客很好
深度分析requestLayout、invalidate与postInvalidate
简单点说,invalidate只会重绘自己的区域,而requeslayout会走measure,layout和draw,所以性能比invalidate低很多。
并且,requestLayout还要求必须在创造他的线程里面执行(注意这里为什么强调,创造他的线程,而不是主线程呢
)
仔细看invalidate()源码,最后会走到ViewGroup.invalidateChild(),
/**
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*
* @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
* draw state in descendants.
*/
@Deprecated
@Override
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
onDescendantInvalidated(child, child);
return;
}
ViewParent parent = this;
if (attachInfo != null) {
// If the child is drawing an animation, we want to copy this flag onto
// ourselves and the parent to make sure the invalidate request goes
// through
final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
// Check whether the child that requests the invalidate is fully opaque
// Views being animated or transformed are not considered opaque because we may
// be invalidating their old position and need the parent to paint behind them.
Matrix childMatrix = child.getMatrix();
// Mark the child as dirty, using the appropriate flag
// Make sure we do not set both flags at the same time
if (child.mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
if (!childMatrix.isIdentity() ||
(mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
Matrix transformMatrix;
if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
Transformation t = attachInfo.mTmpTransformation;
boolean transformed = getChildStaticTransformation(child, t);
if (transformed) {
transformMatrix = attachInfo.mTmpMatrix;
transformMatrix.set(t.getMatrix());
if (!childMatrix.isIdentity()) {
transformMatrix.preConcat(childMatrix);
}
} else {
transformMatrix = childMatrix;
}
} else {
transformMatrix = childMatrix;
}
transformMatrix.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}
// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null) {
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
}
}
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
代码很长,但是,跟我们相关的目前只有最前面的四句
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
onDescendantInvalidated(child, child);
return;
}
很明显,这里满足attachInfo不为空,并且支持已经加速,所以走了onDescendantInvalidated,最后走到根ViewRootImpl,的这个方法。
@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
// TODO: Re-enable after camera is fixed or consider targetSdk checking this
// checkThread();
if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
mIsAnimating = true;
}
invalidate();
}
看到没有,checkThread被注释掉了!!!
这说明什么?说明我们不用理会在主线程刷新这个条件了!!!
这也说明了,我们的view更新,其实不用一定在主线程!
那么,子线程执行刷新的条件是什么?没错,就是我们前面的invalidateChild函数里面,满足条件的内容,也就是attachInfo不为null,并且支持硬件加速,那么就能在有Looper()的子线程里面刷新view()
,注意标红的地方。至于attachInfo是什么,这个其实可以牵扯出另一个很有意思的知识点,那就是View.post在什么时候执行,具体可以参考我之前的博客。由线上问题引发的思考——View.post到底何时执行
其实还有一个点没有考虑到,就是下面这段代码的if里面
void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
if (!isHardwareAccelerated()
|| !mRenderNode.hasDisplayList()
|| (mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
if (invalidateParent) {
invalidateParentCaches();
}
if (forceRedraw) {
mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
}
invalidate(false);
} else {
damageInParent();
}
}
这里的if,楼主试了好多次,发现走的都是else,至于if里面的条件怎么满足,以及这几个参数是什么意思,楼主现在也有点懵,等完全掌握了,再来补充这个知识点吧。另外,就算这里走了invalidate(false)其实对我们结果也没有影响,应该最后也会到上面说的invalidateChild里面,所以,就不用太纠结这里了。
但是,这个改变是从什么时候开始的呢?
说实话,我也不知道,因为就目前的几个版本来看,7.0,8.0,10.0都是这样(楼主使用的是10.0的代码)。目前还没有追根溯源到哪一个api,不过从上线情况来看,应该是6.0以上支持硬件加速的都支持(线上就是在HandlerThread里面执行属性动画)。
这篇文章写的比较精简,是因为其实中间很多步骤并不影响结果,而我们的问题只需要关注几个可以改变结果的关键点,要想要完整的链路分析可以看上面我提到的博客。
从中我们可以得到一个猜想,是不是大部分手机,都支持硬件加速?如果不支持硬件加速怎么办?为了线上万无一失,其实可以使用try{}catch的方式,如果checkThread爆出异常了,再发送到主线程去执行即可。