Android WMS動畫系統(tǒng)初探(一)

基于AndroidR源碼分析

Android WMS動畫系統(tǒng)初探(一)

Android WMS動畫系統(tǒng)初探(二)

Android WMS動畫系統(tǒng)初探(三)

Android 動畫原理

Android中動畫的工作過程:在某一個時間點敢会,調用getTransformation()倘零,根據mStartTime和mDuration,計算出當前的進度钥顽,在根據mInterpolator計算出轉換的進度腮考,然后計算出屬性的當前值诚欠,保存在matrix中使碾。
再調用Matrix.getValues將屬性值取出底洗,運用在動畫目標上款筑。

Animation 和 Transform

[圖片上傳失敗...(image-8c5ae5-1636101404926)]

Animation
在給定了初始狀態(tài)智蝠、結束狀態(tài)、啟動時間與持續(xù)時間后奈梳,可以為使用者計算其動畫目標在任意時刻的變換(Transformation)

子類:TranslateAnimation杈湾,ScaleAnimation,RotateAnimation颈嚼,AlphaAnimation

Transformation
描述了一個變換毛秘,包含兩個分量:透明度和一個二維變換矩陣

Choreographer

無論APP或者系統(tǒng),都是可以直接向Choreographer注冊FrameCallback來實現(xiàn)動畫驅動的阻课。

Choreographer 類似 Handler叫挟,處理回調的時機為屏幕的垂直同步(VSync)事件到來之時,其處理回調的過程被當作渲染下一幀的工作的一部分

postCallback(int callbackType, Runnable action, Object token)

在下一次 VSync 時執(zhí)行 action 所指定的操作限煞。

callbackType 的取值:

CALLBACK_INPUT:處理輸入事件

CALLBACK_ANIMATION:處理動畫事

CALLBACK_TRAVERSAL:處理布局

postCallbackDelayed(int callbackType, Runnable action, Object token, delayMillis)

比 postCallback 增加了一個延遲

postFrameCallback(FrameCallback callback)

在下一次 VSync 時執(zhí)行 callback 指定的回調抹恳。與 postCallback 本質沒有太大區(qū)別,其回調類型強制為 CALLBACK_ANIMATION署驻。FrameCallback 接口的定義函數(shù)為:doFrame(long frameTimeNanos)奋献,參數(shù)是各納秒級的時間戳這個函數(shù)是為處理動畫幀所涉及的postFrameCallbackDelayed(FrameCallback callback, int timeDelayed)比postFrameCallback 增加了一個延遲

WMS的動畫系統(tǒng)

窗口動畫的本質

對于View動畫,動畫的目標就是View旺上,而對于窗口來說瓶蚂,動畫的目標其實都是Surface,對不同層級的SurfaceControl進行操縱宣吱,會產生不同的動畫效果窃这。

目標WindowContainer 名稱 舉例
WindowState 窗口動畫 Toast的彈出動畫、PopupWindow的彈出動畫
AppWindowToken 過渡動畫 App從桌面啟動的動畫
Task Task動畫 Recents的動畫征候,PIP動畫
DisplayContent 全屏動畫 轉屏動畫

WMS類結構

[圖片上傳失敗...(image-b25ef9-1636101404926)]

WMS結構層次

如上圖 WMS的結構層次可以簡單概括為:

RootWindowContainer -> DisplayContent -> DisplayArea -> Task -> WindowToken -> WindowState

[圖片上傳失敗...(image-2bdd8a-1636101404926)]

根據操縱層級的不同我把動畫分類為:窗口動畫杭攻、過渡動畫、Task動畫疤坝、全屏動畫等等

窗口動畫

窗口動畫的啟動入口

在DisplayContent的applySurfaceChangesTransaction函數(shù)中兆解,會調用每個窗口的WindowStateAnimator#commitFinishDrawingLocked,這個函數(shù)是用于處理繪制狀態(tài)為COMMIT_DRAW_PENDING或READY_TO_SHOW的窗口跑揉,因為窗口到了這兩個狀態(tài)才能做窗口動畫锅睛。

隨后會調用WindowState#performShowLocked,并調用WSA的applyEnterAnimationLocked,最后把繪制狀態(tài)改為HAS_DRAWN衣撬。

當進入WSA的applyEnterAnimationLocked之后乖订,后面走的是Surface動畫的統(tǒng)一流程,這個我們在后面統(tǒng)一講。當窗口的狀態(tài)變成HAS_DRAW后具练,會在prepareSurface中被show出來,這樣子窗口已經變?yōu)榭梢娞鹞蓿㈤_始做動畫.

DisplayContent#applySurfaceChangesTransaction

    void applySurfaceChangesTransaction() {

        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyWindowSurfaceChanges");
        try {
            // 這里會調用WindowState#performShowLocked
            forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        // 這里的流程最終會調用mSurfaceControl.show()真正顯示出surface
        prepareSurfaces();
    }

[圖片上傳失敗...(image-d5ed20-1636101404926)]

在窗口布局(relayout)階段調用到

WindowStateAnimator#commitFinishDrawingLocked ->

WindowState#performShowLocked ->

WindowStateAnimator#applyEnterAnimationLocked

開啟窗口動畫流程

WindowStateAnimator#applyAnimationLocked

    boolean applyAnimationLocked(int transit, boolean isEntrance) {
        if (mWin.isAnimating() && mAnimationIsEntrance == isEntrance) {
            // If we are trying to apply an animation, but already running
            // an animation of the same type, then just leave that one alone.
            return true;
        }

        // 設置輸入法相關動畫
        final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
        if (isEntrance && isImeWindow) {
            mWin.getDisplayContent().adjustForImeIfNeeded();
            mWin.setDisplayLayoutNeeded();
            mService.mWindowPlacerLocked.requestTraversal();
        }

        // Only apply an animation if the display isn't frozen.  If it is
        // frozen, there is no reason to animate and it can cause strange
        // artifacts when we unfreeze the display if some different animation
        // is running.
        if (mWin.mToken.okToAnimate()) {
            // 通過DisplayPolicy選擇StatusBar或NavigationBar的動畫
            int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimation(mWin, transit);
            int attr = -1;
            Animation a = null;
            if (anim != DisplayPolicy.ANIMATION_STYLEABLE) {
                if (anim != DisplayPolicy.ANIMATION_NONE) {
                    Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#loadAnimation");
                    // 加載動畫
                    a = AnimationUtils.loadAnimation(mContext, anim);
                    Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                }
            } else {
                // 選擇默認動畫
                switch (transit) {
                    case WindowManagerPolicy.TRANSIT_ENTER:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_EXIT:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_SHOW:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_HIDE:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
                        break;
                }
                // 加載動畫
                if (attr >= 0) {
                    a = mWin.getDisplayContent().mAppTransition.loadAnimationAttr(
                            mWin.mAttrs, attr, TRANSIT_NONE);
                }
            }
            ...
            if (a != null) {
                if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#startAnimation");
                // 流程轉到WindowState#startAnimation 執(zhí)行動畫
                mWin.startAnimation(a);
                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                mAnimationIsEntrance = isEntrance;
            }
        } else if (!isImeWindow) {
            mWin.cancelAnimation();
        }

        if (!isEntrance && isImeWindow) {
            mWin.getDisplayContent().adjustForImeIfNeeded();
        }

        return mWin.isAnimating(PARENTS);
    }

WindowState#startAnimation

    void startAnimation(Animation anim) {

        // If we are an inset provider, all our animations are driven by the inset client.
        if (mControllableInsetProvider != null) {
            return;
        }

        final DisplayInfo displayInfo = getDisplayInfo();
        // 重置Animation扛点,并設置mInitialized為true
        anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(),
                displayInfo.appWidth, displayInfo.appHeight);
        // 設置動畫最長時間,默認10s
        anim.restrictDuration(MAX_ANIMATION_DURATION);
        // 設置動畫scale
        anim.scaleCurrentDuration(mWmService.getWindowAnimationScaleLocked());
        // 構建LocalAnimationAdapter岂丘,封裝了WindowAnimationSpec和SurfaceAnimationRunner
        // WindowAnimationSpec中封裝了animation陵究、surface位置、stackBounds等信息
        // SurfaceAnimationRunner創(chuàng)建于WMS構建之時奥帘,
        final AnimationAdapter adapter = new LocalAnimationAdapter(
                new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */,
                        0 /* windowCornerRadius */),
                mWmService.mSurfaceAnimationRunner);
        // mSurfaceAnimator.startAnimation
        startAnimation(getPendingTransaction(), adapter);
        // 再次調用WMS.scheduleAnimationLocked()
        commitPendingTransaction();
    }

SurfaceAnimationRunner

    // com/android/server/wm/WindowManagerService.java
    private WindowManagerService(Context context, InputManagerService inputManager,
            boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy,
            ActivityTaskManagerService atm, Supplier<SurfaceControl.Transaction> transactionFactory,
            Supplier<Surface> surfaceFactory,
            Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
        ...
                mSurfaceAnimationRunner = new SurfaceAnimationRunner(mTransactionFactory,
                mPowerManagerInternal);
        ...
    }

    // com/android/server/wm/SurfaceAnimationRunner.java
        SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider,
            AnimatorFactory animatorFactory, Transaction frameTransaction,
            PowerManagerInternal powerManagerInternal) {
        // 從ThreadLocal取出SF的Choreographer
        mSurfaceAnimationHandler.runWithScissors(() -> mChoreographer = getSfInstance(),
                0 /* timeout */);
        mFrameTransaction = frameTransaction;
        mAnimationHandler = new AnimationHandler();
        mAnimationHandler.setProvider(callbackProvider != null
                ? callbackProvider
                : new SfVsyncFrameCallbackProvider(mChoreographer));
        // factory用于創(chuàng)建SfValueAnimator
        mAnimatorFactory = animatorFactory != null
                ? animatorFactory
                : SfValueAnimator::new;
        mPowerManagerInternal = powerManagerInternal;
    }

    private class SfValueAnimator extends ValueAnimator {

        SfValueAnimator() {
            setFloatValues(0f, 1f);
        }

        @Override
        public AnimationHandler getAnimationHandler() {
            return mAnimationHandler;
        }
    }

什么是Leash

frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java中定義了一個mSurfaceAnimator成員變量

SurfaceAnimator的startAnimation方法中創(chuàng)建Leash铜邮,可以通過SurfaceAnimator的類注釋了解Leash

/**
 * A class that can run animations on objects that have a set of child surfaces. We do this by
 * reparenting all child surfaces of an object onto a new surface, called the "Leash". The Leash
 * gets attached in the surface hierarchy where the the children were attached to. We then hand off
 * the Leash to the component handling the animation, which is specified by the
 * {@link AnimationAdapter}. When the animation is done animating, our callback to finish the
 * animation will be invoked, at which we reparent the children back to the original parent.
 */
class SurfaceAnimator {

這個類可以針對那種存在多個child surface的對象進行動畫,在執(zhí)行動畫的過程中會創(chuàng)建一個沒有Buffer的Surface---“Leash”寨蹋,將所有child surface綁定到leash上松蒜,leash同時也會綁定到原先這些child surface綁定的位置。然后我們將leash給到AnimationAdapter去執(zhí)行動畫已旧,執(zhí)行動畫結束后會將所有child surface重新綁定到原先的父節(jié)點上秸苗。

為什么引入Leash可以參考此文:Android P——LockFreeAnimation

SurfaceAnimator#startAnimation

    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable SurfaceFreezer freezer) {
        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
        mAnimation = anim;
        mAnimationType = type;
        mAnimationFinishedCallback = animationFinishedCallback;
        // step1 : 先獲取當前需要執(zhí)行動畫的surface
        final SurfaceControl surface = mAnimatable.getSurfaceControl();
        if (surface == null) {
            Slog.w(TAG, "Unable to start animation, surface is null or no children.");
            cancelAnimation();
            return;
        }
        mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
        if (mLeash == null) {
            // step2 : 用step1的surface創(chuàng)建一個leash
            mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                    mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                    0 /* y */, hidden, mService.mTransactionFactory);
            mAnimatable.onAnimationLeashCreated(t, mLeash);
        }
        mAnimatable.onLeashAnimationStarting(t, mLeash);
        if (mAnimationStartDelayed) {
            if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed");
            return;
        }
        // step3 : 將leash傳給AnimationAdapter,執(zhí)行動畫
        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
    }

  1. 先獲取當前需要執(zhí)行動畫的surface
  2. 用step1的surface創(chuàng)建一個leash,這個流程看樣子會遞歸調用到根節(jié)點到DisplayContent中运褪,這里不做深入
  3. 將leash傳給AnimationAdapter惊楼,執(zhí)行動畫

mAnimation.startAnimation這一步最終會通過LocalAnimationAdapter找到WMS里的SurfaceAnimationRunner進行執(zhí)行。

這是 WindowContainer與SurfaceAnimtor秸讹、SurfaceAnimationRunner的持有關系 :

[圖片上傳失敗...(image-f741c0-1636101404926)]

SurfaceAnimationRunner#startAnimation

    void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
            Runnable finishCallback) {
        synchronized (mLock) {
            // 封裝RunningAnimation對象
            final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
                    finishCallback);
            // 加入mPendingAnimations這個ArrayMap
            mPendingAnimations.put(animationLeash, runningAnim);
            if (!mAnimationStartDeferred) {
                // 等待下一次Vsync執(zhí)行startAnimations()檀咙,開始執(zhí)行動畫
                mChoreographer.postFrameCallback(this::startAnimations);
            }

            // 一些動畫(例如移動動畫)需要立即應用初始變換。
            applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
        }
    }

往編舞者上拋的runnable是執(zhí)行startAnimations方法

SurfaceAnimationRunner#startAnimations ->
SurfaceAnimationRunner#startPendingAnimationsLocked
會從mPendingAnimations遍歷RunningAnimation并執(zhí)行startAnimationLocked

SurfaceAnimationRunner#startAnimationLocked

    @GuardedBy("mLock")
    private void startAnimationLocked(RunningAnimation a) {
        // 使用AnimationFactory創(chuàng)建一個SfValueAnimator
        final ValueAnimator anim = mAnimatorFactory.makeAnimator();

        // Animation length is already expected to be scaled.
        anim.overrideDurationScale(1.0f);
        anim.setDuration(a.mAnimSpec.getDuration());
        // 實現(xiàn)UpdaterListener處理每一幀動畫
        anim.addUpdateListener(animation -> {
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    final long duration = anim.getDuration();
                    long currentPlayTime = anim.getCurrentPlayTime();
                    if (currentPlayTime > duration) {
                        currentPlayTime = duration;
                    }
                    // 計算Transformation應用到leash中
                    // 實際執(zhí)行的是前面封裝的WindowAnimationSpec#apply方法
                    // 這里會計算真正要執(zhí)行的的動畫(Transformation)效果
                    // 這一步的目標是為mFrameTransaction設置要執(zhí)行的事務
                    applyTransformation(a, mFrameTransaction, currentPlayTime);
                }
            }

            // Transaction will be applied in the commit phase.
            // 在下一個Vsync信號到來時璃诀,提交動畫事務(mFrameTransaction)
            scheduleApplyTransaction();
        });

        // 設置動畫開始和完成時的處理
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                synchronized (mCancelLock) {
                    if (!a.mCancelled) {
                        // TODO: change this back to use show instead of alpha when b/138459974 is
                        // fixed.
                        mFrameTransaction.setAlpha(a.mLeash, 1);
                    }
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                synchronized (mLock) {
                    mRunningAnimations.remove(a.mLeash);
                    synchronized (mCancelLock) {
                        if (!a.mCancelled) {
                            // Post on other thread that we can push final state without jank.
                            mAnimationThreadHandler.post(a.mFinishCallback);
                        }
                    }
                }
            }
        });
        a.mAnim = anim;
        // 動畫啟動前將這個ValueAnimator加入mRunningAnimations這個ArrayMap
        mRunningAnimations.put(a.mLeash, a);
        // 真正開啟動畫
        anim.start();
        if (a.mAnimSpec.canSkipFirstFrame()) {
            // If we can skip the first frame, we start one frame later.
            anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
        }

        // 通過手動應用動畫框架立即啟動動畫弧可。 否則,開始時間只會在下一個幀中設置文虏,導致延遲侣诺。
        anim.doAnimationFrame(mChoreographer.getFrameTime());
    }

這一步構建了一個SfValueAnimator來真正的驅動動畫,每一幀的處理是通過WindowAnimationSpec構建真正要執(zhí)行的動畫事務氧秘,然后使用mChoreographer.postCallback在下一個vsync信號到來時提交動畫事務年鸳。
ValueAnimator驅動動畫的原理本文就不做深入了。

下一篇文章我將進一步分析Activiy的過渡動畫和屏幕旋轉動畫的相關流程丸相。

過渡動畫

Android WMS動畫系統(tǒng)初探(二)

屏幕旋轉動畫

Android WMS動畫系統(tǒng)初探(三)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末搔确,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌膳算,老刑警劉巖座硕,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涕蜂,居然都是意外死亡华匾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門机隙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜘拉,“玉大人,你說我怎么就攤上這事有鹿⌒裥瘢” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵葱跋,是天一觀的道長持寄。 經常有香客問我,道長娱俺,這世上最難降的妖魔是什么稍味? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮矢否,結果婚禮上仲闽,老公的妹妹穿的比我還像新娘。我一直安慰自己僵朗,他們只是感情好赖欣,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著验庙,像睡著了一般顶吮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粪薛,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天悴了,我揣著相機與錄音,去河邊找鬼违寿。 笑死湃交,一個胖子當著我的面吹牛,可吹牛的內容都是我干的藤巢。 我是一名探鬼主播搞莺,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼掂咒!你這毒婦竟也來了才沧?” 一聲冷哼從身側響起迈喉,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎温圆,沒想到半個月后挨摸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡岁歉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年得运,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刨裆。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡澈圈,死狀恐怖,靈堂內的尸體忽然破棺而出帆啃,到底是詐尸還是另有隱情,我是刑警寧澤窍帝,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布努潘,位于F島的核電站,受9級特大地震影響坤学,放射性物質發(fā)生泄漏疯坤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一深浮、第九天 我趴在偏房一處隱蔽的房頂上張望压怠。 院中可真熱鬧,春花似錦飞苇、人聲如沸菌瘫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雨让。三九已至,卻和暖如春忿等,著一層夾襖步出監(jiān)牢的瞬間栖忠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工贸街, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留庵寞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓薛匪,卻偏偏與公主長得像捐川,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蛋辈,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容