一、前言
android的WindowManagerService(簡稱wms)是系統(tǒng)框架一個非常龐大復(fù)雜的一個系統(tǒng)模塊,它主要由三大塊組成:wms數(shù)據(jù)結(jié)構(gòu),wms大遍歷,wms的窗口動畫
wms數(shù)據(jù)結(jié)構(gòu)就是wms的所有WindowState(繼承windowcontainer)集合的數(shù)據(jù)結(jié)構(gòu),比如有ActivityRecord(包含1個或者多個WindowState),比如有WindowState,其中ActivityRecord具體表現(xiàn)實例就是Activity,WindowState具體表現(xiàn)實例有狀態(tài)欄、導(dǎo)航鍵、輸入法等。
wms大遍歷(performSurfacePlacement)就是對當(dāng)前所有存在的window進行窗口大小計算和窗口繪制狀態(tài)更新,最后把窗口Surface更新到surfaceflinger。
wms的窗口動畫是其中一個比較重要的子功能,wms的窗口動畫負責(zé)窗口間的切換動畫的實現(xiàn)。
接下來我們從android動畫原理開始來逐步介紹wms的窗口動畫
二、android動畫的一個demo
android動畫主要有三種類型:view的動畫、window的動畫、畫布對象的動畫(ondraw里面的畫圖api)
首先我們來看一個android動畫的簡單實現(xiàn)的demo
Choreographer mChoreographer = Choreographer.getInstance();
Animation mAnimation = null;
public void start(Animation anim) {
mAnimation = anim;
scheduleAnimation();
}
private void scheduleAnimation() {
mChoreographer.postFrameCallback(Choreographer.CALLBACK_ANIMTION, mUpdateRunnable, null);
}
private Runnable mUpdateRunnable = new Runnable() {
@Override
public void run() {
if (mAnimation != null) {
long time = SystemClock.uptimeMillis();
Transformation transform = new Transformation();
//根據(jù)當(dāng)前time計算transform
boolean more = mAnimation.getTransformation(time, transform);
//根據(jù)transform進行渲染,改變view的屬性(大小、位置、透明度等)?改變窗口的屬性(大小、位置、透明度等)?
PERFORM_RENDER_WITH_TRANSFORMATION(transform);
//通過time的計算可以計算出動畫是否繼續(xù)還是結(jié)束
if (more) {
scheduleAnimation();
} else {
mAnimation = null;
}
}
}
};
從這個例子可以看出android的動畫就是借用Choreographer來通過vsync原理逐幀控制動畫的播放(需要對Choreographer有一定的了解),中間update變量transform包含了動畫的基本元素:Matrix、透明度,然后根據(jù)這兩個元素對顯示對象(view或者畫布對象或者window?)進行當(dāng)前時間的繪制,逐幀顯示,最終用戶看到的就是一個動畫,從systrace可以看到
ValueAnimator屬性動畫的實現(xiàn)原理也是類似于這個demo的實現(xiàn)
三、WindowManagerService窗口動畫機制
android的WindowManagerService窗口動畫機制一直在優(yōu)化進步,主要體現(xiàn)在:
1、在androidP以前的版本,主要是通過WindowAnimator主動畫類中的mChoreographer來通過vsync原理逐幀控制窗口動畫的播放
具體窗口的動畫變化由WindowStateAnimator的stepAnimationLocked來控制,通過改變窗口的大小、位置、透明度(通過SurfaceControl代理實現(xiàn)對surfaceflinger的調(diào)用),來最終達到窗口動畫的實現(xiàn)
有興趣的可以去仔細研究下這部分代碼的實現(xiàn),雖然是歷史版本的舊代碼,但是這個對wms的學(xué)習(xí)理解有很大的幫助。
/*frameworks/base/services/core/java/com/android/wm/WindowAnimator.java */
/** Locked on mService.mWindowMap. */
private void animateLocked(long frameTimeNs) {
這個方案有個很大的缺陷,那就是動畫的所有實現(xiàn)的代碼都包含在wms的主鎖mGlobalLock里面,從動畫主要方法的命名后綴locked可以得知,那么意味動畫會跟wms其他所有流程搶CPU資源,就容易導(dǎo)致wms主鎖的卡頓,在某些復(fù)雜的用戶場景下,容易導(dǎo)致手機的卡頓,給用戶帶來糟糕的體驗。
2、在androidP及之后的版本,google對窗口動畫進行了重構(gòu),主要思想是通過ValueAnimator屬性動畫來播放窗口動畫,把窗口動畫播放從wms主鎖脫離出來,這樣動畫就不會占用wms資源,從而達到優(yōu)化系統(tǒng)框架運行速度的效果,同時把部分動畫放到app遠端播放(比如狀態(tài)欄、導(dǎo)航鍵動畫,比如多任務(wù)動畫),達到系統(tǒng)和APP雙端協(xié)調(diào)播放復(fù)雜的跨端動畫效果
3、wms的新窗口動畫主要分為兩種類型,LocalAnimationAdapter和RemoteAnimationAdapter,分別實現(xiàn)了wms本地窗口動畫和遠程窗口動畫。遠程窗口動畫機制,主要是為了實現(xiàn)android的兩個新功能特意開發(fā)的機制,一個是從桌面點擊app圖標(biāo)進入app的入場動畫和app退出的出場動畫,一個是在app界面,通過拖動底部指示條進入桌面的滑動效果動畫,這兩個動畫效果最先是iphone實現(xiàn)的,google為了仿iphone的實現(xiàn),所以開發(fā)了遠程動畫機制,最終能達到iphone的動畫效果,提高了android手機的復(fù)雜動畫效果
4、本文主要介紹android新動畫的流程和實現(xiàn)的原理,主要介紹了LocalAnimationAdapter本地窗口動畫實現(xiàn)原理
四、新動畫機制Local窗口動畫流程
本地窗口動畫具體場景:可以在設(shè)置首頁點擊其中一項菜單,進入設(shè)置某項子菜單,然后就會有一個Local窗口動畫的播放
LocalAnimationAdapter,字面意思就是wms本地窗口動畫,該動畫在SurfaceAnimationThread(線程名android.anim.lf)線程播放,注意看該類的注釋
(本文剩余源碼基于androidS原生源碼)
/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationThread.java */
/**
* Thread for running {@link SurfaceAnimationRunner} that does not hold the window manager lock.
*/
public final class SurfaceAnimationThread extends ServiceThread {
這個才是新動畫機制的核心要義,不占用wms主鎖,就不會占用wms的資源,這個已經(jīng)是對wms很大的優(yōu)化了,android歷史版本因為wms鎖卡頓的問題太多了
接下來我們通過閱讀源碼來分析LocalAnimationAdapter的實現(xiàn)流程,先看下整體的Local窗口動畫時序圖
1、動畫播放源頭類AppTransitionController的方法handleAppTransitionReady
在一次wms大遍歷(performSurfacePlacement)流程結(jié)束之后,就會檢查app transition是否已經(jīng)準(zhǔn)備好,opening 的app準(zhǔn)備好需要滿足app的starting窗口是否已經(jīng)displayed或者app的window是否已經(jīng)alldrawn,只要滿足其中一個條件,就說明app的窗口動畫流程可以開始了了。
/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */
void handleAppTransitionReady() {
mTempTransitionReasons.clear();
//檢查app transition是否已經(jīng)準(zhǔn)備好
if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
|| !transitionGoodToGo(mDisplayContent.mChangingContainers,
mTempTransitionReasons)) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");
首先會獲取當(dāng)前需要opening和closing的app window列表(ActivityRecord類型)
/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */
final ActivityRecord topOpeningApp =
getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);
final ActivityRecord topClosingApp =
getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);
然后在applyAnimations方法里面對window列表進行遍歷WindowContainer的動畫applyAnimation方法的調(diào)用
/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */
private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
@TransitionOldType int transit, boolean visible, LayoutParams animLp,
boolean voiceInteraction) {
final int wcsCount = wcs.size();
for (int i = 0; i < wcsCount; i++) {
final WindowContainer wc = wcs.valueAt(i);
final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();
for (int j = 0; j < apps.size(); ++j) {
final ActivityRecord app = apps.valueAt(j);
if (app.isDescendantOf(wc)) {
transitioningDescendants.add(app);
}
}
wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
}
}
2、在WindowContainer,會先收集getAnimationAdpater當(dāng)前window的動畫適配器
/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */
protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
@TransitionOldType int transit, boolean isVoiceInteraction,
@Nullable ArrayList<WindowContainer> sources) {
final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
transit, enter, isVoiceInteraction);
如果是普通的窗口動畫,比如app內(nèi)部activity的切換,當(dāng)前的場景是設(shè)置主菜單跳轉(zhuǎn)子菜單,根據(jù)當(dāng)前場景獲取到具體的transit,transit=TRANSIT_OLD_ACTIVITY_OPEN,然后再結(jié)合enter為true或者false,可以最終可以找到設(shè)置主菜單的動畫xml資源是activity_open_exit.xml,設(shè)置子菜單的動畫xml資源是activity_open_enter.xml,在獲取到具體xml資源名字后,通過AnimationUtils.loadAnimation方法把xml資源轉(zhuǎn)成Animation對象。
之后就會創(chuàng)建一個WindowAnimationSpec對象,并把Animation對象作為構(gòu)造方法的第一個參數(shù)傳給了WindowAnimationSpec
/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */
final Animation a = loadAnimation(lp, transit, enter, isVoiceInteratction);
AnimationAdapter adapter = new LocalAnimationAdapter(
//創(chuàng)建了一個WindowAnimatonSpec對象作為LocalAnimationAdapter的初始化參數(shù)
new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
getDisplayContent().mAppTransition.canSkipFirstFrame(),
appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius),
getSurfaceAnimationRunner());
這里創(chuàng)建LocalAnimationAdapter對象的時候同時創(chuàng)建了一個WindowAnimatonSpec對象作為LocalAnimationAdapter的初始化參數(shù),這個類WindowAnimatonSpec比較重要,是在后續(xù)窗口動畫播放的時候具體的實現(xiàn)類,后面再分析
在獲取到具體的Adaper對象之后,就開始執(zhí)行startAnimation方法,這個方法里面主要調(diào)用了mSurfaceAnimator對象,來實現(xiàn)startAnimation
/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
mSurfaceFreezer);
tip: WindowContainer這個類是wms的最重要類之一,它是所有window的基類,充分學(xué)習(xí)理解該類可以對wms的所有window的樹狀圖有一定的理解
3、SurfaceAnimator類,字面上的意思就是window動畫實現(xiàn)是交給它來實現(xiàn)surfacecontrol的動畫(舊窗口動畫是通過WindowSurfaceController控制surfacecontrol),該類的就是窗口動畫的中控,它的主要作用是在startAnimation的時候,對要進行動畫的surfacecontrol創(chuàng)建一個parent的surfacecontrol類型的mLeash對象,leash的翻譯是用皮帶系住的意思,相當(dāng)于把要進行動畫的surfacecontrol用皮帶系住,通過操控mLeash對象來實現(xiàn)窗口的大小、位置、透明度等動畫屬性的改變。
/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */
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;
final SurfaceControl surface = mAnimatable.getSurfaceControl();
mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
if (mLeash == null) {
//重點關(guān)注這個mLeash對象,該對象是窗口動畫專屬surfacecontrol包裝對象
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);
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
}
然后在動畫結(jié)束之后,mLeash對象會走銷毀的流程,同時動畫的surfacecontrol進行reparent還原操作。
/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */
static boolean removeLeash(Transaction t, Animatable animatable, @NonNull SurfaceControl leash,
boolean destroy) {
boolean scheduleAnim = false;
final SurfaceControl surface = animatable.getSurfaceControl();
final SurfaceControl parent = animatable.getParentSurfaceControl();
final boolean reparent = surface != null;
if (reparent) {
if (surface.isValid() && parent != null && parent.isValid()) {
t.reparent(surface, parent);
scheduleAnim = true;
}
}
窗口動畫中控最終調(diào)用了動畫的適配類LocalAnimationAdapter的startAnimation方法。
/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
4、LocalAnimationAdapter是窗口動畫的適配類,繼承自AnimationAdaper,LocalAnimationAdapter實現(xiàn)的是wms本地窗口動畫,所以LocalAnimationAdapter可以理解成本地窗口動畫的中轉(zhuǎn)類。在startAnimation方法里面,調(diào)用了SurfaceAnimationRunner來最終實現(xiàn)動畫的播放。
/*frameworks/base/services/core/java/com/android/wm/LocalAnimationAdapter.java */
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
@AnimationType int type, OnAnimationFinishedCallback finishCallback) {
mAnimator.startAnimation(mSpec, animationLeash, t,
() -> finishCallback.onAnimationFinished(type, this));
}
5、SurfaceAnimationRunner是本地窗口動畫真正的實現(xiàn)類,主要需要關(guān)注的方法是startAnimationLocked,首先這個方法已經(jīng)通過mChoreographer切換到SurfaceAnimationThread線程來執(zhí)行,然后創(chuàng)建了ValueAnimator屬性動畫對象,交由ValueAnimator屬性動畫對象的addUpdateListener方法來實現(xiàn)逐幀控制動畫mLeash對象(surfacecontrol類型)的變化,具體的update方法的實現(xiàn)是在WindowAnimatonSpec類的apply方法里面。
/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationRunner.java */
private void startAnimationLocked(RunningAnimation a) {
final ValueAnimator anim = mAnimatorFactory.makeAnimator();
anim.overrideDurationScale(1.0f);
anim.setDuration(a.mAnimSpec.getDuration());
anim.addUpdateListener(animation -> {
synchronized (mCancelLock) {
if (!a.mCancelled) {
final long duration = anim.getDuration();
long currentPlayTime = anim.getCurrentPlayTime();
if (currentPlayTime > duration) {
currentPlayTime = duration;
}
applyTransformation(a, mFrameTransaction, currentPlayTime);
}
}
scheduleApplyTransaction();
});
………………
a.mAnim = anim;
mRunningAnimations.put(a.mLeash, a);
//窗口動畫最終調(diào)用了屬性動畫播放
anim.start();
anim.doAnimationFrame(mChoreographer.getFrameTime());
}
再來看下apply方法的具體實現(xiàn),通過之前以具體xml資源創(chuàng)建的mAnimation對象,根據(jù)當(dāng)前時間片currentPlayTime獲取到當(dāng)前的tmp.transformation,對leash對象實現(xiàn)了Matrix(大小,位置),Alpha,Crop等transformation變化,再通過Transaction 交給surfaceflinger顯示,從而實現(xiàn)了動畫當(dāng)前時間片的顯示效果。對比舊動畫機制,這個transformation變化是在WindowStateAnimator類里面實現(xiàn)的。為什么要重點關(guān)注這個方法呢?因為如果窗口動畫出bug了(位置大小不對?透明度異常?),就可以在這個方法里面打印window的相關(guān)參數(shù)來初步定位原因。
/*frameworks/base/services/core/java/com/android/wm/WindowAnimatonSpec.java */
@Override
public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
final TmpValues tmp = mThreadLocalTmps.get();
tmp.transformation.clear();
mAnimation.getTransformation(currentPlayTime, tmp.transformation);
tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
t.setAlpha(leash, tmp.transformation.getAlpha());
boolean cropSet = false;
if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {
if (tmp.transformation.hasClipRect()) {
t.setWindowCrop(leash, tmp.transformation.getClipRect());
cropSet = true;
}
} else {
mTmpRect.set(mRootTaskBounds);
if (tmp.transformation.hasClipRect()) {
mTmpRect.intersect(tmp.transformation.getClipRect());
}
t.setWindowCrop(leash, mTmpRect);
cropSet = true;
}
}
6、ValueAnimator類,從上面的介紹可以得知,窗口動畫的最終本質(zhì)就是一個ValueAnimator屬性動畫,理解了這一點,就相當(dāng)于把窗口動畫簡單化了,最終的實現(xiàn)就類比于我們普通app的屬性動畫的實現(xiàn)(app屬性動畫的對象是view,窗口屬性動畫的對象是window),只不過整個流程比較復(fù)雜而已,但是最終的實現(xiàn)原理是一樣的,殊途同歸,這個才是android窗口動畫機制的精髓所在。
/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationRunner.java */
ValueAnimator anim = mAnimatorFactory.makeAnimator();
anim.addUpdateListener(animation -> {
applyTransformation(a, mFrameTransaction, currentPlayTime);
});
anim.start();
總結(jié)
本文只是講解了WindowManagerService窗口動畫之本地窗口動畫的原生實現(xiàn)流程,主要是WindowManagerService的子類比較多,所以我們從動畫的源頭handleAppTransitionReady一步一步分析了它的整個流程,從整個流程來看,本地窗口動畫的邏輯比較清晰,線路也比較單一,比較容易學(xué)習(xí)和理解,最終我們看到,窗口動畫的原理就是一個屬性動畫,在動畫update方法里面操控了窗口surface的屬性變化,從而實現(xiàn)了窗口動畫的逐幀播放。另外還有一個重點需要關(guān)注到,那就是wms的窗口動畫不需要占用wms主鎖,而且是單獨線程,這樣的設(shè)計也能在一定程度上優(yōu)化系統(tǒng)卡頓的問題。