Android属性アニメーションソース分析(四)

15211 ワード

前はちょっと忙しくて、しばらく書いていませんでした.はい、今日から属性アニメーションの第4部を始めて、前の3節は属性アニメーションの初期化の過程を基本的に分析して、今日は前に書いた内容と結びつけて、属性アニメーションをどのように“start”を行ったのか、完全に分析します. 前の文章を読んでいない場合は、以下の内容をよりよく理解できるように、前の分析過程を見ておくことをお勧めします:[Android属性アニメーションソース分析(一)][Android属性アニメーションソース分析(二)][Android属性アニメーションソース分析(三)]
        animator.addListener(listener);//     
        animator.setDuration(3000);//        
        animator.setInterpolator(new LinearInterpolator());//   
        animator.setRepeatCount(ValueAnimator.INFINITE);//    
        animator.start();//    

前の4行のソースコードは簡単で、話さないで、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は主にすべてのアクティブなプロパティアニメーションが共有する「時間パルス」を処理するために使用され、この時間パルスは通常理解されるプロパティアニメーションの開始から終了までの各期間の「値」であり、1つのアニメーションの完全な再生が同じスレッドで発生することを保証すると同時に、また、サイクルのコールバックを処理するために重要なクラス「Choreographer(ダンサー)」も使用されています.もう1つのクラスAnimationFrameCallBackProviderは、handlerにUI更新のための時間パルスを提供するために使用される.(これらのクラスは先にメモして、後で表にします)
//AnimationHandler.java
public final static ThreadLocal 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が入力され,次に呼び出された3つのメソッドを順番に解析する:(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]に格納される(これは各タイプの配列を格納し、各配列要素は異なるタイプのコールバックを格納するために1つの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メソッドに実行される)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);
            }
            ...
        }
    }

上のコードは主に2つのことをして、対応するタイプの「コールバックキュー」(実はチェーンテーブル)を取り出して、キュー内の各コールバックrunメソッドを実行します.まず最初に、addCallbackLockedの方法を分析すると言ったが、ちょうどここでextractDueCallbacksLockedと一緒に分析した.この2つの方法は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はAnimationFrameCallback Providerを作成し、AnimationHanderにFrameCallbackタイプのコールバックを追加しました(前になかったら)、このFrameCallbackはproviderを通じてChoregrapher(ダンサー)に送信され、ダンサーによってCallbackRecordタイプの内容にカプセル化され、このcallbackRecordは、垂直同期信号配信時に呼び出され、最終的にFrameCallbackのdoFrameメソッドが呼び出されます.また、AnimationHanderのコールバックリストには、今回のanimationも追加されています.次の記事では、mFrameCallbackのdoFrameメソッドの解析を開始します.