一、前言
android的WindowManagerService(簡稱wms)是系統(tǒng)框架一個(gè)非常龐大復(fù)雜的一個(gè)系統(tǒng)模塊于游,它主要由三大塊組成:wms數(shù)據(jù)結(jié)構(gòu)毁葱,wms大遍歷,wms的窗口動(dòng)畫
wms數(shù)據(jù)結(jié)構(gòu)就是wms的所有WindowState(繼承windowcontainer)集合的數(shù)據(jù)結(jié)構(gòu)贰剥,比如有ActivityRecord(包含1個(gè)或者多個(gè)WindowState)倾剿,比如有WindowState,其中ActivityRecord具體表現(xiàn)實(shí)例就是Activity,WindowState具體表現(xiàn)實(shí)例有狀態(tài)欄前痘、導(dǎo)航鍵凛捏、輸入法等。
wms大遍歷(performSurfacePlacement)就是對(duì)當(dāng)前所有存在的window進(jìn)行窗口大小計(jì)算和窗口繪制狀態(tài)更新芹缔,最后把窗口Surface更新到surfaceflinger坯癣。
wms的窗口動(dòng)畫是其中一個(gè)比較重要的子功能,wms的窗口動(dòng)畫負(fù)責(zé)窗口間的切換動(dòng)畫的實(shí)現(xiàn)最欠。
接下來我們從android動(dòng)畫原理開始來逐步介紹wms的窗口動(dòng)畫
二示罗、android動(dòng)畫的一個(gè)demo
android動(dòng)畫主要有三種類型:view的動(dòng)畫、window的動(dòng)畫芝硬、畫布對(duì)象的動(dòng)畫(ondraw里面的畫圖api)
首先我們來看一個(gè)android動(dòng)畫的簡單實(shí)現(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計(jì)算transform
boolean more = mAnimation.getTransformation(time, transform);
//根據(jù)transform進(jìn)行渲染蚜点,改變view的屬性(大小、位置拌阴、透明度等)绍绘?改變窗口的屬性(大小、位置迟赃、透明度等)陪拘?
PERFORM_RENDER_WITH_TRANSFORMATION(transform);
//通過time的計(jì)算可以計(jì)算出動(dòng)畫是否繼續(xù)還是結(jié)束
if (more) {
scheduleAnimation();
} else {
mAnimation = null;
}
}
}
};
從這個(gè)例子可以看出android的動(dòng)畫就是借用Choreographer來通過vsync原理逐幀控制動(dòng)畫的播放(需要對(duì)Choreographer有一定的了解)捺氢,中間update變量transform包含了動(dòng)畫的基本元素:Matrix藻丢、透明度,然后根據(jù)這兩個(gè)元素對(duì)顯示對(duì)象(view或者畫布對(duì)象或者window摄乒?)進(jìn)行當(dāng)前時(shí)間的繪制悠反,逐幀顯示,最終用戶看到的就是一個(gè)動(dòng)畫馍佑,從systrace可以看到
ValueAnimator屬性動(dòng)畫的實(shí)現(xiàn)原理也是類似于這個(gè)demo的實(shí)現(xiàn)
三斋否、WindowManagerService窗口動(dòng)畫機(jī)制
android的WindowManagerService窗口動(dòng)畫機(jī)制一直在優(yōu)化進(jìn)步,主要體現(xiàn)在:
1拭荤、在androidP以前的版本茵臭,主要是通過WindowAnimator主動(dòng)畫類中的mChoreographer來通過vsync原理逐幀控制窗口動(dòng)畫的播放
具體窗口的動(dòng)畫變化由WindowStateAnimator的stepAnimationLocked來控制,通過改變窗口的大小舅世、位置旦委、透明度(通過SurfaceControl代理實(shí)現(xiàn)對(duì)surfaceflinger的調(diào)用),來最終達(dá)到窗口動(dòng)畫的實(shí)現(xiàn)
有興趣的可以去仔細(xì)研究下這部分代碼的實(shí)現(xiàn)雏亚,雖然是歷史版本的舊代碼缨硝,但是這個(gè)對(duì)wms的學(xué)習(xí)理解有很大的幫助。
/*frameworks/base/services/core/java/com/android/wm/WindowAnimator.java */
/** Locked on mService.mWindowMap. */
private void animateLocked(long frameTimeNs) {
這個(gè)方案有個(gè)很大的缺陷罢低,那就是動(dòng)畫的所有實(shí)現(xiàn)的代碼都包含在wms的主鎖mGlobalLock里面查辩,從動(dòng)畫主要方法的命名后綴locked可以得知,那么意味動(dòng)畫會(huì)跟wms其他所有流程搶CPU資源,就容易導(dǎo)致wms主鎖的卡頓宜岛,在某些復(fù)雜的用戶場(chǎng)景下长踊,容易導(dǎo)致手機(jī)的卡頓,給用戶帶來糟糕的體驗(yàn)萍倡。
2身弊、在androidP及之后的版本,google對(duì)窗口動(dòng)畫進(jìn)行了重構(gòu)遣铝,主要思想是通過ValueAnimator屬性動(dòng)畫來播放窗口動(dòng)畫佑刷,把窗口動(dòng)畫播放從wms主鎖脫離出來,這樣動(dòng)畫就不會(huì)占用wms資源酿炸,從而達(dá)到優(yōu)化系統(tǒng)框架運(yùn)行速度的效果,同時(shí)把部分動(dòng)畫放到app遠(yuǎn)端播放(比如狀態(tài)欄涨冀、導(dǎo)航鍵動(dòng)畫填硕,比如多任務(wù)動(dòng)畫),達(dá)到系統(tǒng)和APP雙端協(xié)調(diào)播放復(fù)雜的跨端動(dòng)畫效果
3鹿鳖、wms的新窗口動(dòng)畫主要分為兩種類型扁眯,LocalAnimationAdapter和RemoteAnimationAdapter,分別實(shí)現(xiàn)了wms本地窗口動(dòng)畫和遠(yuǎn)程窗口動(dòng)畫翅帜。遠(yuǎn)程窗口動(dòng)畫機(jī)制姻檀,主要是為了實(shí)現(xiàn)android的兩個(gè)新功能特意開發(fā)的機(jī)制,一個(gè)是從桌面點(diǎn)擊app圖標(biāo)進(jìn)入app的入場(chǎng)動(dòng)畫和app退出的出場(chǎng)動(dòng)畫涝滴,一個(gè)是在app界面绣版,通過拖動(dòng)底部指示條進(jìn)入桌面的滑動(dòng)效果動(dòng)畫,這兩個(gè)動(dòng)畫效果最先是iphone實(shí)現(xiàn)的歼疮,google為了仿iphone的實(shí)現(xiàn)杂抽,所以開發(fā)了遠(yuǎn)程動(dòng)畫機(jī)制,最終能達(dá)到iphone的動(dòng)畫效果韩脏,提高了android手機(jī)的復(fù)雜動(dòng)畫效果
4缩麸、本文主要介紹android新動(dòng)畫的流程和實(shí)現(xiàn)的原理,主要介紹了LocalAnimationAdapter本地窗口動(dòng)畫實(shí)現(xiàn)原理
四赡矢、新動(dòng)畫機(jī)制Local窗口動(dòng)畫流程
本地窗口動(dòng)畫具體場(chǎng)景:可以在設(shè)置首頁點(diǎn)擊其中一項(xiàng)菜單杭朱,進(jìn)入設(shè)置某項(xiàng)子菜單,然后就會(huì)有一個(gè)Local窗口動(dòng)畫的播放
LocalAnimationAdapter吹散,字面意思就是wms本地窗口動(dòng)畫弧械,該動(dòng)畫在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 {
這個(gè)才是新動(dòng)畫機(jī)制的核心要義送浊,不占用wms主鎖梦谜,就不會(huì)占用wms的資源,這個(gè)已經(jīng)是對(duì)wms很大的優(yōu)化了,android歷史版本因?yàn)閣ms鎖卡頓的問題太多了
接下來我們通過閱讀源碼來分析LocalAnimationAdapter的實(shí)現(xiàn)流程唁桩,先看下整體的Local窗口動(dòng)畫時(shí)序圖
1闭树、動(dòng)畫播放源頭類AppTransitionController的方法handleAppTransitionReady
在一次wms大遍歷(performSurfacePlacement)流程結(jié)束之后,就會(huì)檢查app transition是否已經(jīng)準(zhǔn)備好荒澡,opening 的app準(zhǔn)備好需要滿足app的starting窗口是否已經(jīng)displayed或者app的window是否已經(jīng)alldrawn报辱,只要滿足其中一個(gè)條件,就說明app的窗口動(dòng)畫流程可以開始了了单山。
/*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");
首先會(huì)獲取當(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方法里面對(duì)window列表進(jìn)行遍歷WindowContainer的動(dòng)畫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,會(huì)先收集getAnimationAdpater當(dāng)前window的動(dòng)畫適配器
/*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);
如果是普通的窗口動(dòng)畫米奸,比如app內(nèi)部activity的切換昼接,當(dāng)前的場(chǎng)景是設(shè)置主菜單跳轉(zhuǎn)子菜單,根據(jù)當(dāng)前場(chǎng)景獲取到具體的transit悴晰,transit=TRANSIT_OLD_ACTIVITY_OPEN慢睡,然后再結(jié)合enter為true或者false,可以最終可以找到設(shè)置主菜單的動(dòng)畫xml資源是activity_open_exit.xml铡溪,設(shè)置子菜單的動(dòng)畫xml資源是activity_open_enter.xml漂辐,在獲取到具體xml資源名字后,通過AnimationUtils.loadAnimation方法把xml資源轉(zhuǎn)成Animation對(duì)象棕硫。
之后就會(huì)創(chuàng)建一個(gè)WindowAnimationSpec對(duì)象髓涯,并把Animation對(duì)象作為構(gòu)造方法的第一個(gè)參數(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)建了一個(gè)WindowAnimatonSpec對(duì)象作為LocalAnimationAdapter的初始化參數(shù)
new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
getDisplayContent().mAppTransition.canSkipFirstFrame(),
appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius),
getSurfaceAnimationRunner());
這里創(chuàng)建LocalAnimationAdapter對(duì)象的時(shí)候同時(shí)創(chuàng)建了一個(gè)WindowAnimatonSpec對(duì)象作為LocalAnimationAdapter的初始化參數(shù),這個(gè)類WindowAnimatonSpec比較重要哈扮,是在后續(xù)窗口動(dòng)畫播放的時(shí)候具體的實(shí)現(xiàn)類纬纪,后面再分析
在獲取到具體的Adaper對(duì)象之后,就開始執(zhí)行startAnimation方法灶泵,這個(gè)方法里面主要調(diào)用了mSurfaceAnimator對(duì)象育八,來實(shí)現(xiàn)startAnimation
/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
mSurfaceFreezer);
tip: WindowContainer這個(gè)類是wms的最重要類之一,它是所有window的基類赦邻,充分學(xué)習(xí)理解該類可以對(duì)wms的所有window的樹狀圖有一定的理解
3髓棋、SurfaceAnimator類,字面上的意思就是window動(dòng)畫實(shí)現(xiàn)是交給它來實(shí)現(xiàn)surfacecontrol的動(dòng)畫(舊窗口動(dòng)畫是通過WindowSurfaceController控制surfacecontrol)惶洲,該類的就是窗口動(dòng)畫的中控按声,它的主要作用是在startAnimation的時(shí)候,對(duì)要進(jìn)行動(dòng)畫的surfacecontrol創(chuàng)建一個(gè)parent的surfacecontrol類型的mLeash對(duì)象恬吕,leash的翻譯是用皮帶系住的意思签则,相當(dāng)于把要進(jìn)行動(dòng)畫的surfacecontrol用皮帶系住,通過操控mLeash對(duì)象來實(shí)現(xiàn)窗口的大小铐料、位置渐裂、透明度等動(dòng)畫屬性的改變豺旬。
/*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) {
//重點(diǎn)關(guān)注這個(gè)mLeash對(duì)象,該對(duì)象是窗口動(dòng)畫專屬surfacecontrol包裝對(duì)象
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);
}
然后在動(dòng)畫結(jié)束之后柒凉,mLeash對(duì)象會(huì)走銷毀的流程族阅,同時(shí)動(dòng)畫的surfacecontrol進(jìn)行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;
}
}
窗口動(dòng)畫中控最終調(diào)用了動(dòng)畫的適配類LocalAnimationAdapter的startAnimation方法膝捞。
/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
4坦刀、LocalAnimationAdapter是窗口動(dòng)畫的適配類,繼承自AnimationAdaper蔬咬,LocalAnimationAdapter實(shí)現(xiàn)的是wms本地窗口動(dòng)畫鲤遥,所以LocalAnimationAdapter可以理解成本地窗口動(dòng)畫的中轉(zhuǎn)類。在startAnimation方法里面林艘,調(diào)用了SurfaceAnimationRunner來最終實(shí)現(xiàn)動(dòng)畫的播放盖奈。
/*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是本地窗口動(dòng)畫真正的實(shí)現(xiàn)類北启,主要需要關(guān)注的方法是startAnimationLocked卜朗,首先這個(gè)方法已經(jīng)通過mChoreographer切換到SurfaceAnimationThread線程來執(zhí)行,然后創(chuàng)建了ValueAnimator屬性動(dòng)畫對(duì)象咕村,交由ValueAnimator屬性動(dòng)畫對(duì)象的addUpdateListener方法來實(shí)現(xiàn)逐幀控制動(dòng)畫mLeash對(duì)象(surfacecontrol類型)的變化,具體的update方法的實(shí)現(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);
//窗口動(dòng)畫最終調(diào)用了屬性動(dòng)畫播放
anim.start();
anim.doAnimationFrame(mChoreographer.getFrameTime());
}
再來看下apply方法的具體實(shí)現(xiàn)懈涛,通過之前以具體xml資源創(chuàng)建的mAnimation對(duì)象,根據(jù)當(dāng)前時(shí)間片currentPlayTime獲取到當(dāng)前的tmp.transformation泳猬,對(duì)leash對(duì)象實(shí)現(xiàn)了Matrix(大小批钠,位置),Alpha得封,Crop等transformation變化埋心,再通過Transaction 交給surfaceflinger顯示,從而實(shí)現(xiàn)了動(dòng)畫當(dāng)前時(shí)間片的顯示效果忙上。對(duì)比舊動(dòng)畫機(jī)制拷呆,這個(gè)transformation變化是在WindowStateAnimator類里面實(shí)現(xiàn)的。為什么要重點(diǎn)關(guān)注這個(gè)方法呢疫粥?因?yàn)槿绻翱趧?dòng)畫出bug了(位置大小不對(duì)茬斧?透明度異常?)梗逮,就可以在這個(gè)方法里面打印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類慷彤,從上面的介紹可以得知娄蔼,窗口動(dòng)畫的最終本質(zhì)就是一個(gè)ValueAnimator屬性動(dòng)畫怖喻,理解了這一點(diǎn),就相當(dāng)于把窗口動(dòng)畫簡單化了岁诉,最終的實(shí)現(xiàn)就類比于我們普通app的屬性動(dòng)畫的實(shí)現(xiàn)(app屬性動(dòng)畫的對(duì)象是view锚沸,窗口屬性動(dòng)畫的對(duì)象是window),只不過整個(gè)流程比較復(fù)雜而已唉侄,但是最終的實(shí)現(xiàn)原理是一樣的咒吐,殊途同歸,這個(gè)才是android窗口動(dòng)畫機(jī)制的精髓所在属划。
/*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窗口動(dòng)畫之本地窗口動(dòng)畫的原生實(shí)現(xiàn)流程恬叹,主要是WindowManagerService的子類比較多,所以我們從動(dòng)畫的源頭handleAppTransitionReady一步一步分析了它的整個(gè)流程同眯,從整個(gè)流程來看绽昼,本地窗口動(dòng)畫的邏輯比較清晰,線路也比較單一须蜗,比較容易學(xué)習(xí)和理解硅确,最終我們看到,窗口動(dòng)畫的原理就是一個(gè)屬性動(dòng)畫明肮,在動(dòng)畫update方法里面操控了窗口surface的屬性變化菱农,從而實(shí)現(xiàn)了窗口動(dòng)畫的逐幀播放。另外還有一個(gè)重點(diǎn)需要關(guān)注到柿估,那就是wms的窗口動(dòng)畫不需要占用wms主鎖循未,而且是單獨(dú)線程,這樣的設(shè)計(jì)也能在一定程度上優(yōu)化系統(tǒng)卡頓的問題秫舌。