深入源码系列:聊聊android属性动画执行线程问题(invalidate(false))
前言
这篇博文是为了解决一个问题,在之前的博客已经提过了,就是属性动画的执行结束的回调,不是在主线程。
首先,阅读这篇文章,大家一定要有一个基础,就是,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爆出异常了,再发送到主线程去执行即可。
更多相关文章
- Android中的动画和原理(帧动画和补间动画)
- 安卓开机logo和开机动画的几种实现方法
- android 简单地设置Activity界面的跳转动画
- Android(安卓)Api Demo学习之Activity
- Android(安卓)断点续传,手写多线程下载文件、数据库存储进度
- Android(安卓)Watchdog(看门狗)分析
- Android补间动画详情
- Android(安卓)中的线程形态 -- AsyncTask,HandlerThread,IntentSer
- android中的通信机制总结1:使用handler来进行通信