Android属性动画源码分析(四)

  之前有点忙,有一阵没写了;好,今天开始属性动画的第四部分,前面三节把属性动画的初始化的过程基本分析了一遍,今天就结合之前写的内容,将属性动画是如何进行"start"的,完整的分析一遍。
  如果没有看过之前的文章,建议看下之前的分析过程,以便可以更好的理解接下来的内容:
[Android属性动画源码分析(一)]
[Android属性动画源码分析(二)]
[Android属性动画源码分析(三)]

        animator.addListener(listener);//动画监听器
        animator.setDuration(3000);//每次动画运行时间
        animator.setInterpolator(new LinearInterpolator());//插值器
        animator.setRepeatCount(ValueAnimator.INFINITE);//重复次数
        animator.start();//动画开始

  前面四行的源码很简单,就不讲了,从start开始讲起,首先进入start的源码:

//ObjectAnimator.java
    @Override
    public void start() {
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        ...
        super.start();
    }

  进入这个AnimationHandler看一下:
  官方文档对它的解释是:

//AnimatorHandler.java
/**
 * This custom, static handler handles the timing pulse that is shared by all active
 * ValueAnimators. This approach ensures that the setting of animation values will happen on the
 * same thread that animations start on, and that all animations will share the same times for
 * calculating their values, which makes synchronizing animations possible.
 *
 * The handler uses the Choreographer by default for doing periodic callbacks. A custom
 * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
 * may be independent of UI frame update. This could be useful in testing.
 *
 * @hide
 */

  该handler主要是用于处理所有活动的属性动画共享的“时间脉冲”,这个时间脉冲是通常所理解的属性动画的从开始到结束每个时间段的“值”,它还保证了一个动画的完整播放都是发生在同一个线程,同时,它还使用了一个关键的类“Choreographer(编舞者)”来处理周期的回调;另一个类AnimationFrameCallBackProvider用来给handler提供用于UI更新的时间脉冲。(这几个类先记下,后面再表)

//AnimationHandler.java
public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();//这个handler的实例对象是一个ThreadLocal对象
public static AnimationHandler getInstance() {
        if (sAnimatorHandler.get() == null) {//为每一个线程创建一个handler对象,这样做是用于确保一个动画的开始和结束都在同一个线程
            sAnimatorHandler.set(new AnimationHandler());
        }
        return sAnimatorHandler.get();
    }
//AnimationHandler.java
void autoCancelBasedOn(ObjectAnimator objectAnimator) {
        for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {//当前回调还未进行添加,
//所以这个for循环不会走,如果走的话,代表停止之前正在播放的动画,具体内容大家可以自行研究
            AnimationFrameCallback cb = mAnimationCallbacks.get(i);
            if (cb == null) {
                continue;
            }
            if (objectAnimator.shouldAutoCancel(cb)) {
                ((Animator) mAnimationCallbacks.get(i)).cancel();
            }
        }
    }

  在判断完是否应该取消之前播放的动画之后,我们就来到了super.start()方法:

//ValuesAnimator.java
public void start() {
        start(false);//这里传入了false,代表正序播放动画
    }
//ValuesAnimator.java
...
        mReversing = playBackwards;
        // Special case: reversing from seek-to-0 should act as if not seeked at all.
        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {//第一个参数就是false,所以不会走这里
            ...
        }
        mStarted = true;//设置当前状态为start状态
        mPaused = false;
        mRunning = false;
        // Resets mLastFrameTime when start() is called, so that if the animation was running,
        // calling start() would put the animation in the
        // started-but-not-yet-reached-the-first-frame phase.
        mLastFrameTime = 0;
        AnimationHandler animationHandler = AnimationHandler.getInstance();//之前提到过的handler,这里进行了初始化(源码在上面)
        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));//(1)将当前动画对象添加到handler中,(第二个long的参数看初始化的值可知为0)

        if (mStartDelay == 0 || mSeekFraction >= 0) {//由初始化代码得知mStartDelay == 0,所以这里会走
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            startAnimation();//(2)
            if (mSeekFraction == -1) {//初始化值是-1,这里会走
                // No seek, start at play time 0. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);(3)
            } else {
                //本次不会走这里
                setCurrentFraction(mSeekFraction);
            }
        }

  初始化的时候主要的工作:(1)将当前动画给了AnimatonHandler;(2)调用了startAnimaton()方法;(3)调用了setCurrentPlayTime()方法,并且传入了参数0,接下来按顺序分析下调用的这三个方法:
(1)

    //AnimatonHandler.java
    /**
     * Register to get a callback on the next frame after the delay.
     */
    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {//这里的size是0,所以这里会走
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {当前的list还无数据,所以这里会进入
            mAnimationCallbacks.add(callback);//这里添加的是ObjectAnimator对象
        }

        if (delay > 0) {//当前delay是0
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }

  这个mFrameCallback是谁呢,我们看下它的定义:

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

  是一个Choreographer#FrameCallBack类型,这个mFrameCallback是怎么运作的呢,在分析完后面的内容在继续分析,这里先标记下。
  我们在看下getProvider().postFrameCallBack(mFrameCallback);的内容

    //AnimatonHandler.java
    private AnimationFrameCallbackProvider getProvider() {
        if (mProvider == null) {
            mProvider = new MyFrameCallbackProvider();
        }
        return mProvider;
    }
    //MyFrameCallbackProvider.java
     /**
     * Default provider of timing pulse that uses Choreographer for frame callbacks.
     */
    private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

        final Choreographer mChoreographer = Choreographer.getInstance();

        @Override
        public void postFrameCallback(Choreographer.FrameCallback callback) {
            mChoreographer.postFrameCallback(callback);
        }

        @Override
        public void postCommitCallback(Runnable runnable) {
            mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
        }

        @Override
        public long getFrameTime() {
            return mChoreographer.getFrameTime();
        }

        @Override
        public long getFrameDelay() {
            return Choreographer.getFrameDelay();
        }

        @Override
        public void setFrameDelay(long delay) {
            Choreographer.setFrameDelay(delay);
        }
    }

  由上面可以看到,MyFrameCallBackProvider相当于Choregrapher的代理类,所以接下来这块的分析我们直接进入Choregrapher类去查看,关于这个类的相关机制,鉴于篇幅有限,我们就踩在巨人的肩膀上,可以参考这篇文章:Android消息机制Looper与VSync的传播 ,我们只看相关动画的部分:

//Choreographer.java
public void postFrameCallback(FrameCallback callback) {
        postFrameCallbackDelayed(callback, 0);
    }

  上边的方法传入了回调对象,延迟时间为0,进入postFrameCallbackDelayed:

//Choreographer.java
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
        ...\\省略一些判断过程
        }

        postCallbackDelayedInternal(CALLBACK_ANIMATION,
                callback, FRAME_CALLBACK_TOKEN, delayMillis);
    }

  在上边的方法里,最后会调用postCallBackDelayedInternal方法进行最终处理,callBacktype是CALLBACK_ANIMATION,action就是回调对象,token是FRAME_CALLBACK_TOKEN,delayMillis是0:

//Choreographer.java
private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        ...
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;//这里delayMillis = 0
           mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);//回调存入了CALLBACK_ANIMATION队列中

            if (dueTime <= now) {
                scheduleFrameLocked(now);//由于dueTime == now,这里将被执行,进入垂直同步过程
            } else {
                ...
            }
        }
    }

  由上面的代码可知,我们将回调对象最终存入了mCallBackQueues[CALLBACK_ANIMATION]中(这是一个存放各个类型的数组,每个数组元素是一个CallbackQueue用于存放不同类型的回调:

//Choreographer.java
private final CallbackQueue[] mCallbackQueues;

  由之前的代码分析可知,我们播放的动画是CALLBACK_ANIMATION类型,addCallbackLocked方法我们将在下面进行分析)然后编舞者将会执行一次垂直同步(具体参见相关文章,这里不展开),一系列的过程后,会调用Choregrapher中 CallBackRecord对象的run方法,最终进入doFrame方法:

//Choreographer.java
void doFrame(long frameTimeNanos, int frame) {
       ...
        try {
            ...
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        ...
    }

(补充,根据VSYNC机制,每次接收到垂直同步信号时,都会经过一系列的分发执行到doFrame方法)
  这里只列出一些关键部分,通过这部分可以看出,doFrame方法调用了doCallBacks方法用来处理不同类型的回调,由于不涉及其他三种类型,只看传入的类型是Choreographer.CALLBACK_ANIMATION,doCallBacks是如何处理的

//Choreographer.java
void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            final long now = System.nanoTime();
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);//取出回调方法
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;
           ...
        try {
            ...
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "RunCallback: type=" + callbackType
                            + ", action=" + c.action + ", token=" + c.token
                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
                }
                c.run(frameTimeNanos);//执行回调
            }
        } finally {
            synchronized (mLock) {
                mCallbacksRunning = false;
                do {//这里recycle了相关类型的回调
                    final CallbackRecord next = callbacks.next;
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
            }
            ...
        }
    }

  上边的代码主要做了两件事,取出相应类型的“回调队列”(其实是个链表),执行队列中每个回调的run方法;
先说第一件事,前面说过要分析一下addCallbackLocked方法,正好在这里和extractDueCallbacksLocked一起分析:
这两个方法都是CallbackQueue里的方法:

mCallbackQueues[CALLBACK_ANIMATION].addCallbackLocked(now, callback, token);//这里只是列出之前的调用及传参
//Choreographer#CallbackQueue
public void addCallbackLocked(long dueTime, Object action, Object token) {
            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
            CallbackRecord entry = mHead;
            if (entry == null) {
                mHead = callback;
                return;
            }
            if (dueTime < entry.dueTime) {
                callback.next = entry;
                mHead = callback;
                return;
            }
            while (entry.next != null) {
                if (dueTime < entry.next.dueTime) {
                    callback.next = entry.next;
                    break;
                }
                entry = entry.next;
            }
            entry.next = callback;
        }

  上边的代码就是按照dueTime的顺序将通过obtainCallbackLocked生成的CallbackRecord放到链表的合适位置进行存储(CallbackRecord就是一个链表的存储结构);

//Choreographer#CallbackQueue
private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
        CallbackRecord callback = mCallbackPool;
        if (callback == null) {
            callback = new CallbackRecord();
        } else {
            mCallbackPool = callback.next;
            callback.next = null;
        }
        callback.dueTime = dueTime;
        callback.action = action;//注意,动画本身的callback放到了这里
        callback.token = token;
        return callback;
    }

  上边就是生成一个CallbackRecord的过程,动画播放的callback放到了CallbackRecord的action中。
  接下来看看取出的时候是怎么做的:

//Choreographer#CallbackQueue
public CallbackRecord extractDueCallbacksLocked(long now) {
            CallbackRecord callbacks = mHead;
            if (callbacks == null || callbacks.dueTime > now) {
                return null;
            }

            CallbackRecord last = callbacks;
            CallbackRecord next = last.next;
            while (next != null) {
                if (next.dueTime > now) {
                    last.next = null;
                    break;
                }
                last = next;
                next = next.next;
            }
            mHead = next;
            return callbacks;
        }

  总体来说,就是将链表头部返回,并且移除了当前时间之后的元素。
  上边一系列方法看完以后就拿到了callbacks,通过for循环对这个链表遍历执行每个CallbackRecord的run方法:

//Choreographer#CallbackRecord
public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }

  由前边的代码可知,这里的token就是FRAME_CALLBACK_TOKEN,所以将执行doFrame方法,还记得这个action是谁吗,没错,就是前面说要在后面分析的mFrameCallback!
  到这里,getProvider().postFrameCallback(mFrameCallback);的过程就分析完了,总结起来就是,我们的这次animation创建了一个AnimationFrameCallbackProvider,在AnimationHander里添加了一个FrameCallback类型的回调(如果之前没有的话),这个FrameCallback将通过provider发送给Choregrapher(编舞者),被编舞者封装为CallbackRecord类型的内容,在一次垂直同步信号分发时,这个callbackRecord将会被调用,并最终调用FrameCallback的doFrame方法,同时,本次animation也添加到了AnimationHander的回调列表中,接下来的一篇文章我们将开始分析mFrameCallback的doFrame方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • Android系统从4.1(API 16)开始加入Choreographer这个类来控制同步处理输入(Input)...
    DeltaTech阅读 35,402评论 22 139
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_x阅读 15,968评论 3 119
  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,691评论 0 10
  • 目录君 离开胖达官府的时侯,拓桑扶着一鸣一个劲的喊爹爹,试图唤回一点一鸣的记忆,可一鸣瞳孔无神,根本不知道拓桑在说...
    有井阅读 373评论 3 4
  • 谁说湖面下面也一定平静? 小小的鱼儿虽难以激起太多涟漪, 偶尔的跳动也是它存在的痕迹, 当阳光再次铺洒大地, 曾经...
    三人行则必有我师焉阅读 223评论 0 0