前言

这篇博文是为了解决一个问题,在之前的博客已经提过了,就是属性动画的执行结束的回调,不是在主线程。
首先,阅读这篇文章,大家一定要有一个基础,就是,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爆出异常了,再发送到主线程去执行即可。

更多相关文章

  1. Android中的动画和原理(帧动画和补间动画)
  2. 安卓开机logo和开机动画的几种实现方法
  3. android 简单地设置Activity界面的跳转动画
  4. Android(安卓)Api Demo学习之Activity
  5. Android(安卓)断点续传,手写多线程下载文件、数据库存储进度
  6. Android(安卓)Watchdog(看门狗)分析
  7. Android补间动画详情
  8. Android(安卓)中的线程形态 -- AsyncTask,HandlerThread,IntentSer
  9. android中的通信机制总结1:使用handler来进行通信

随机推荐

  1. android 基于百度地图api获取经纬度
  2. Android中打电话的数据流程
  3. Android(安卓)最火框架XUtils之注解机制
  4. Android中WebView和JavaScript通信
  5. android中http访问总结
  6. Android(安卓)SDK 2.2 下载安装方法
  7. Android(安卓)属性动画 源码解析 深入了
  8. android系统信息(内存、cpu、sd卡、电量、
  9. Android中使用log4j
  10. Android中GridView组件的使用