Transition.onTransactionReady的內容比較長,我們挑重點的部分逐段分析(跳過的地方并非不重要,而是我柿子挑軟的捏)。
1 窗口繪制狀態的流轉以及顯示SurfaceControl
注意我們這里的SurfaceControl特指的是WindowSurfaceController的mSurfaceControl,如果對這個不是很了解的,可以回顧一下之前寫的關于SurfaceControl的文章:
【基礎】2、Surface的創建【Android 12】 - 掘金 (juejin.cn)
接著分析代碼:
先跳過Transition.commitVisibleActivities,看到首先是將Transition.mState置為STATE_PLAYING,這意味著動畫馬上就要執行了。
然后是為Transition的兩個成員變量mStartTransaction以及mFinishTransaction賦值,mFinishTransaction不用多說,看到mStartTransaction被賦值為傳參transaction,傳參即我們上一篇分析中的在SyncGroup.finishNow創建的一個Transaction,局部變量merged:
一個“start transaction”和一個“finish transaction”,我按照個人的理解,舉個例子說明一下,如果我們從ActivityA上啟動了一個ActivityB:
1)、對于ActivityA來說,它相關的SurfaceControl(準確一點說則是WindowSurfaceController.mSurfaceControl)需要在動畫結束的時候再隱藏,如果它在動畫開始前就隱藏,那么就無法看到ActivityA的動畫效果了(向右平移退出或者淡出之類的動畫)。
2)、對于ActivityB來說,它相關的SurfaceControl需要在動畫開始的時候就顯示出來,如果它在動畫開始的時候還沒有顯示,那么同樣也無法看到ActivityB的動畫效果了(向右平移進入或者淡入之類的動畫)。
從以上分析可知,ActivityA和ActivityB相關的SurfaceControl可見性變化的時機是不同的,那么這個行為通過一次Transacton.apply是無法做到的,所以就需要兩個Transaction,即“start transaction”和“finish transaction”。“start transaction”在動畫開始前調用apply,用于在動畫開始執行前提前將ActivityB進行顯示,“finish transaction”則是在動畫結束的時候調用apply,用于在動畫結束的時候再將ActivityA隱藏。
最重要的是要弄清楚“start transaction”和“finish transaction”這兩個Transaction調用apply方法的時機,在以后的Transition流程中會分析到。
再來看Transition.commitVisibleActivities方法的內容:
如該方法的注釋所說,當前Transition已經準備好執行動畫了,這里先讓“start transaction”把相關需要顯示的SurfaceControl顯示出來。
Transition.mParticipants是參與動畫的WindowContainer集合,那么這個方法就是遍歷這個集合:
1)、調用ActivityRecord.commitVisibility設置相關ActivityRecord的為可見。
2)、調用ActivityRecord.commitFinishDrawing進一步設置相關SurfaceControl為可見。
ActivityRecord.commitVisibility方法內容比較多,主要是用來ActivityRecord的可見性,即其成員變量mVisible,除此之外還有很多別的邏輯,但是和我們要分析的Transition內容無關,只需要知道這里設置了ActivityRecord的可見性即可,不去多說。我們主要看下ActivityRecord.commitFinishDrawing:
很簡單,為每一個child,即WindowState調用commitFinishDrawing方法:
1)、調用WindowStateAnimator.commitFinishDrawingLocked方法,繼續將窗口對應的WindowStateAnimator的mDrawState,即繪制狀態進行流轉。
2)、調用WindowStateAnimator.prepareSurfaceLocked,設置SurfaceControl的可見性。
這兩個方法都比較重要,我們接下來分別進行分析。
1.1 窗口繪制狀態的流轉
SurfaceControl最終的顯示和窗口的繪制狀態密切相關,所以我感覺這里有必要看一下WindowStateAnimator.mDrawState這個狀態是如何切換的,并且我自己對這個窗口的繪制狀態也是不求甚解,也希望借著這個機會了解一下。
先分析一下代碼,回頭再試著總結一下。
·1.1.1 WindowStateAnimator.commitFinishDrawingLocked
首先將WindowState.mDrawState設置為READY_TO_SHOW。
然后如果當前WindowStateAnimator相關的WindowState滿足以下條件之一,則繼續調用WindowStateAnimator.performShowLocked:
1)、沒有對應的ActivityRecord,即是一個非Activity窗口:
activity == null
2)、有對應的ActivityRecord,并且此時已經可以顯示窗口了:
activity.canShowWindows()
重要是就是看這個ActivityRecord的mSyncState是不是SYNC_STATE_WAITING_FOR_DRAW,如果是這個值,那么就說明這個ActivityRecord是處于動畫中的。
但是有一個問題是,ActivityRecord的mSyncState是不會被設置為SYNC_STATE_WAITING_FOR_DRAW的,只有WindowState才會,那豈不是每次走到這里判斷ActivityRecord是否drawn,都將一直是true。
3)、是一個TYPE_APPLICATION_STARTING類型的窗口,即SplashScreen或者Snapshot:
mWin.mAttrs.type == TYPE_APPLICATION_STARTING
總而言之,如果這個WindowState滿足了繪制了條件,那么將繼續調用WindowState.performShowLocked。
1.1.2 WindowState.performShowLocked
如果WindowStateAnimator.mDrawState不是READY_TO_SHOW,那么返回false,否則將其置為HAS_DRAWN,并且返回true,這將使得我們可以下一步繼續調用WindowStateAnimator.prepareSurfaces方法。
從WindowStateAnimator.commitFinishDrawingLocked以及WindowState.performShowLocked這兩個方法都能看到,窗口的繪制狀態是循序漸進的,必須是狀態A -> 狀態B -> 狀態C,不存在狀態A直接到狀態C之類的。
1.1.3 窗口繪制狀態小結
首先是mDrawState在WindowStateAnimator的定義,以及幾個取值:
結合著Activity啟動的一般流程,我大致總結一下:
1)、NO_SURFACE:當沒有Surface的就置為這個狀態。
這個很好理解,一般窗口銷毀相關的流程,會將WindowStateAnimator.mDrawState設置為NO_SURFACE,比如:
WindowState.removeImmediately
-> WindowStateAnimator.destroySurfaceLocked
-> WindowStateAnimator.destroySurface
此階段沒有窗口,也沒有Surface。
2)、DRAW_PENDING:當Surface被創建之后,窗口被添加但還沒有開始繪制之前,就會置為這個狀態。在這個時期,Surface是隱藏的。這表明Surface正等待應用程序繪制窗口的內容。
當窗口被添加,接著App側開始走measure、layout以及draw流程,在draw之前,會將窗口在WMS側進行relayout,經過:
WMS.relayoutWindow
-> WMS.createSurfaceControl
-> WindowStateAnimator.createSurfaceLocked
-> WindowStateAnimator.resetDrawState
會將WindowStateAnimator.mDrawState設置為DRAW_PENDING。
這個流程我們也很熟悉,即之前分析創建WindowSurfaceController的SurfaceControl的流程。
此階段窗口被添加但還沒繪制出來,SurfaceControl也是隱藏的。
3)、COMMIT_DRAW_PENDING:當窗口的繪制操作完成,但是這個Surface還沒有顯示出來之前,狀態會設置為此值。這個Surface會在下次layout過程中顯示出來。
當窗口繪制完成,App側調用ViewRootImpl.reportDrawFinished后,就會調用IWindowSession的對端,經過:
Session.finishDrawing
-> WMS.finishDrawingWindow
-> WindowState.finishDrawing
-> WindowStateAnimator.finishDrawingLocked
會將WindowStateAnimator.mDrawState設置為COMMIT_DRAW_PENDING。
此階段窗口已經繪制完成,但是Surface由于一些原因還不能顯示。
4)、READY_TO_SHOW:這個狀態標識窗口的繪制操作已經提交,但Surface還沒有真正顯示。在一組窗口(例如屬于同一個應用的多個窗口)準備顯示時,系統會使用這個狀態來延遲顯示Surface,直到所有相關窗口都準備好一起顯示。
首先我們看到在動畫的流程中,窗口的繪制狀態被設置為READY_TO_SHOW的流程為:
Transition.onTransactionReady
-> Transition.commitVisibleActivities
-> ActivityRecord.commitFinishDrawing
-> WindowState.commitFinishDrawing
-> WindowStateAnimator.commitFinishDrawingLocked
結合注釋,我個人的理解是,繪制狀態被置為READY_TO_SHOW,表明此窗口已經繪制完了,可以準備顯示它的SurfaceControl了,但是它的SurfaceControl需要等待和其它的SurfaceContrl一起顯示,或者說等待動畫走到特定階段才能顯示,因此我們這里推遲其SurfaceControl的顯示時間,將窗口的繪制狀態設置為READY_TO_SHOW。
如果不考慮和其它窗口一起顯示,那么我想在這一步就可以將繪制狀態設置為HAS_DRAWN了,即READY_TO_SHOW這個狀態值是不必要的。
5)、HAS_DRAWN:當窗口首次在屏幕上顯示時,就會設置為此狀態。
這個值在WindowState.performShowLocked方法中被設置,緊跟著WindowStateAnimator.commitFinishDrawingLocked方法。
嚴謹一點的話注釋的說法其實是不準確的,當窗口繪制狀態被設置為HAS_DRAWN的時候,只是說明SurfaceControl接下來可以顯示了,但是SurfaceControl仍然沒有顯示,屏幕上是看不見的。
6)、總結一下,從以上分析可知,這些狀態值不只涉及了窗口的繪制流程,還涉及了SurfaceControl的顯示流程:
- NO_SURFACE:沒窗口,也沒SurfaceControl。
- DRAW_PENDING:有窗口,但沒開始繪制。有SurfaceControl,但不能顯示。
- COMMIT_DRAW_PENDING,窗口剛剛繪制完,SurfaceControl還不能顯示。
- READY_TO_SHOW:窗口已經繪制完了,SurfaceControl可以顯示了,但沒必要,再等等。
- HAS_DRAWN:窗口已經繪制完了,SurfaceControl也可以顯示了。
1.2 顯示SurfaceControl
回到WindowState.commitFinishDrawing,在調用WindowStateAnimator.commitFinishDrawingLocked將窗口的繪制狀態走完后,接下來就是調用WindowStateAnimator.prepareSurfaceLocked來顯示SurfaceControl了。
注意這個方法被調用的地方有兩處:
還有一處調用的地方在WindowState.prepareSurfaces,這個是更通用的流程,但是動畫流程下,則稍微不同,即我們分析的這個流程。
看代碼:
我們只看和顯示SurfaceControl相關的部分:
1)、如果窗口不在屏幕上,則調用WindowStateAnimator.hide -> WindowSurfaceController.hide來隱藏SurfaceControl。
2)、如果窗口在屏幕上,那么進一步判斷窗口的繪制狀態,只有窗口的繪制狀態為HAS_DRAWN,才能繼續調用WindowSurfaceController.showRobustly來顯示SurfaceControl:
關鍵的就那一句,調用Transaction.show來顯示相關SurfaceControl,但是要注意的是這里并沒有調用Transaction.apply,所以這個時候窗口還是沒有顯示。
窗口的最終顯示則是和這個傳參Transaction對象有關,這個Transaction對象則是之前說的”start transaction“,那么這個Transaction的apply方法的調用時機則是跟Transition的流程相關,以后的分析會看到。
2 計算動畫目標
這一節的內容是調用Transition.calculateTargets來計算動畫的目標:
Transition的成員變量mTargets定義為:
之前收集到的動畫參與者提升后的最終的動畫目標,也就是說最終執行動畫的主體并非是之前收集到的動畫參與者,而是這一步用動畫參與者計算得到的動畫目標。
Transition.calculateTargets的內容為:
大致的內容為:
1)、創建一個Transition.Targets類型的局部變量targets,來收集動畫目標。
2)、遍歷Transition.mParticipants,從Transition.mChanges中取出對應的ChangeInfo對象放到Transition.Targets.mArray中,但是跳過WindowState類型的動畫參與者,以及跳過那些根據ChangeInfo.hasChanged得出前后沒有發生變化的動畫參與者。
3)、調用Transition.tryPromote嘗試提升targets中保存的動畫目標的級別。
我們這一節主要來看下這個Transition.tryPromote。
”promote“,提升的動畫目標在WindowContainer層級結構中的級別,這個邏輯之前在AppTransitionController.getAnimationTargets也用到了,思想都是類似的。比如一個Task中有兩個ActivityRecord,并且這兩個ActivityRecord要分別執行一段動畫,也就是動畫執行的主體是ActivityRecord。如果這兩個ActivityRecord剛好都想向左平移同樣的距離,那么我們就不需要為這兩個ActivityRecord分別應用一段平移的動畫,而是直接將這個平移的動畫應用到它們共同的父容器Task上,并且實現的效果是一樣的。這也就是”promote“的含義,動畫的目標主體從ActivityRecord”提升“到了更高一級的Task上。
接著看代碼,Transition.tryPromote。
2.1 Transition.tryPromote
主要邏輯為遍歷Targets.mArray中的每一個ChangeInfo對象,調用Transition.canPromote方法來判斷他們是否能夠提升為父容器。
1)、如果不能,直接跳過該ChangeInfo對象,判斷下一個。
2)、如果能,就說明提升成功。此外還要調用Transition.reportIfNotTop來繼續判斷它是否是organized(我的理解就是這個WindowContainer是否是系統開機后自動創建的,不是需要的時候再去創建的)。如果不是,那么將當前WindowContainer對應的ChangeInfo從局部變量targets中移除,然后把它的父WindowContainer對應的ChangeInfo加如到targets中。如果是,那么在不移除當前WindowContainer對應的ChangeInfo的前提下,把它的父WindowContainer對應的ChangeInfo加如到targets中。這里應該是針對organized的WindowContainer的特殊處理,確保organized的WindowContainer的變化也能夠報告到WMShell那邊。
因此重點其實是Transition.canPromote邏輯。
2.2 Transition.canPromote
感覺這段代碼還是比較重要的,我們逐行分析。
2.2.1 片段1
1)、對應WindowContainer.canCreateRemoteAnimationTarget方法,目前只有TaskDisplayArea、TaskFragment以及ActivityRecord會返回true,其它類型的WindowContainer都會返回false,也就是說父容器不是這幾類的WindowContainer將無法得到提升,那么目前只有這幾種提升:WindowState到ActivityRecord,ActivityRecod到TaskFragment,TaskFragment到TaskFragment(因為TaskFragment存在嵌套,比如Home類型的TaskFragment),以及TaskFragment到TaskDisplayArea。另外從Transition.calculateTargets的邏輯我們看到了執行動畫的target至少是WindowToken這一級的,并且看收集的邏輯,似乎也沒有看到過直接收集WindowState的,因此實際上提升只存在以下幾種情況:
- ActivityRecod到TaskFragment。
- TaskFragment到TaskFragment。
- TaskFragment到TaskDisplayArea。
2)、如果找不到父WindowContainer對應的ChangeInfo,則不提升,返回false。
3)、如果父WindowContainer有ChangeInfo,但是此時的狀態和收集開始時的狀態沒有變化,則不提升,返回false。
2.2.2 片段2
1)、如果當前要提升的WindowContainer是Wallpaper類型的,則不提升,返回false。
2)、如果當前WindowContainer前后的父WindowContainer不一致,即發生reparent了,則不提升,返回false。
2.2.3 片段3
遍歷父WindowContainer的所有子WindowContainer:
1)、如果姊妹WindowContainer在Transition.mChanges中找不到一個對應的ChangeInfo對象,或者有這么一個ChangeInfo對象,但是該ChangeInfo對象不在Targets.mArray中,這種情況一共可以理解為這個姊妹WindowContainer沒有參與到本次動畫,那么還需要繼續判斷:
----1.1)、如果該姊妹WindowContainer可見,那么就不提升,直接返回false,當前WindowContainer無法提升到父WindowContainer。畢竟該姊妹WindowContainer是沒有參與到動畫中的,并且是可見的,如果你提升了,那后續動畫執行的時候用戶不是會看到該姊妹WindowContainer跟著一起動了嘛,這肯定是不對的。
----1.2)、如果該姊妹WindowContainer不可見,那么就跳過對這個WindowContainer的檢查。不可見的姊妹WindowContainer對于本次動畫也沒有太大影響,即使跟著一起進行動畫用戶也看不到,直接跳過檢查下一個姊妹WindowContainer就好了。
2)、如果姊妹WindowContainer從Transition.mChanges中能找到一個對應的ChangeInfo對象,并且該ChangeInfo對象也在局部變量targets中,那么認為該姊妹WindowContainer也參與了本次動畫,那么分別為他們的TransitionMode調用Transition.reduceMode方法來看它們動畫的大方向是否是一致的,首先是根據ChangeInfo.getTransitMode拿到各自的TransitionMode:
TransitionMode定義在TransitionInfo中:
看到Transition模式其實就是定義在WindowManager中的Transition類型的子集。
ChangeInfo.getTransitMode的內容也比較簡單:
TRANSIT_CHANGE:收集階段的可見性和Transition就緒階段的可見性沒有發生變化。
TRANSIT_OPEN:存在發生了變化,且當前可見,即從無到有。
TRANSIT_CLOSE:存在發生了變化,且當前不可見,即從有到無。
TRANSIT_TO_FRONT:存在沒有發生變化,且當前可見,說明從后臺移動到了前臺,從不可見變為了可見。
TRANSIT_TO_BACK:存在沒有發生變化,且當前不可見,說明從前臺移動到了后臺,從可見變為了不可見。
再根據Transition.reduceMode的邏輯:
- TRANSIT_TO_BACK和TRANSIT_CLOSE是一類的。
- TRANSIT_TO_FRONT和TRANSIT_OPEN是一類的。
- TRANSIT_CHANGE單獨一類。
如果動畫的大方向是一致的,那么即使TRANSIT_TO_BACK和TRANSIT_CLOSE的動畫有點差別,但是為了大局考慮,各別同志也不是不能適當調整一下來實現集體上的一致。
如果動畫的大方向都不一致,那么它們中的無論哪個肯定都是不能提升為它們的父容器的。比如TaskA想向左平移,TaskB想向右平移,那么如果擅自提升為父容器TaskDisplayArea,不管TaskDisplayArea向左還是向右平移肯定都不合適,這種矛盾就屬于不可調和了,那父容器TaskDisplayArea就不用管了,也就是別提升了,讓沖突的TaskA和TaskB自己玩去吧。
3)、最后總結一下檢查姊妹WindowContainer的這段邏輯,其實就是檢查所有的姊妹WindowContainer中,有沒有和當前WindowContainer沖突的姊妹WindowContainer,至于是否沖突則看是否滿足了以下條件之一:
- 檢查所有沒有參與動畫的姊妹WindowContainer,看能否找到一個可見的。
- 檢查所有參與了動畫的姊妹WindowContainer,看能否找到一個動畫的大方向和當前WindowContainer不一致。
只要找到了這么一個姊妹WindowContainer,我們就無法提升動畫的主體。
3 構建TransitionInfo對象
這一節我感覺其實沒有什么好說的,大概介紹一下TransitionInfo以及它的內部類Change。
1)、TransitionInfo,實現了Parcelable,結合注釋,用來收集WMCore這邊的Transition信息,用來同步給WMShell的TransitionPlayer。成員變量大概有這些:
2)、TransitionInfo.Change,同樣實現了Parcelable,代表了WindowContainer在一個Transition期間的變化。看其成員變量,保存的信息還是挺多的,還有一個RunningTaskInfo的對象:
再結合Transition.calculateTransitionInfo方法,很明顯就大概能弄懂這兩個類的作用:
1)、TransitionInfo,對應一個Transition對象,用來收集WMShell感興趣的Transition的信息,后續同步給WMShell。
2)、TransitionInfo.Change,對應一個Transition.ChangeInfo對象,用來收集WMShell感興趣的Transition.ChangeInfo的信息,后續同步給WMShell。
順便一提,google為啥不將Transition中ChangeInfo的命名為”Change“,將TransitionInfo中的Change命名為”ChangeInfo“呢,強迫癥犯了。
4 Transition移動到PLAYING狀態
其實在Transition.onTransactionReady方法的開頭已經將Transition.mState狀態置為STATE_PLAYING,這里又調用了一個TransitionController.moveToPlaying方法,看下是干啥的:
其實也非常簡單:
1)、開始動畫了,意味著當前Transition已經不能收集了,所以將TransitionController.mCollectingTransition置空。特別的,如果有其它Transition在排隊,那么就繼續將TransitionController.mCollectingTransition賦值為排隊隊列隊首的那個Transition,我播我的動畫,你收集你的WindowContainer,互不干擾。
2)、將當前Transition添加到TransitionController.mPlayingTransitions:
一個當前處于playing狀態的Transition的隊列,也就是說playing的Transition可以有多個。
5 切換到WMShell:onTransitionReady
在Transition.onTransactionReady方法的最后,調用了ITransitionPlayer.onTransitionReady方法將切換到了WMShell:
切換到WMShell意味著Transition就緒階段已經結束,正式進入Transition的playing階段,Transitions.TransitionPlayerImpl.onTransitionReady就是我們下一篇文章的起點。
最后稍微看一下調用ITransitionPlayer.onTransitionReady方法之前調用的Transition.buildFinishTransaction方法:
傳入的Transaction對象為Transition.mFinishTransaction,如該方法的注釋所說,這里對”finish transaction“的操作保證了動畫結束后,所有的”reparent“操作或者是Layer的變化將會得到重置,特別是Layer的幾何信息(位置、縮放、旋轉這些)。如果你的Layer在動畫結束的時候在Layer的這些信息上的確有變化,那就要注意不要讓這個方法把你對Layer的操作重置了。