wmsandroid
❶ Android 重學系列 WMS在Activity啟動中的職責 計算窗體的大小(四)
通過啟動窗口為例子,大致上明白了WMS是如何添加,更新,移除窗口的工作原理。本文將會重點聊一聊窗口的大小計算邏輯。
下面的源碼都是來自Android 9.0
計算窗口的大小和Android 4.4相比變化很大。花了一點心思去重新學習了。在Android 4.4中,窗體的計算在onResume中調用了ViewRootImpl調用relayoutWindow對整個Window重新測量窗口大小以及邊距。
relayoutWindow這個方法是做什麼的呢?當我們在Activity的生命周期到達了onResume的階段,此時ViewRootImpl的setView,開始走渲染的View的流程,並且調用requestLayout開始測量渲染。其中有一個核心的邏輯就是調用WMS的relayoutWindow,重新測量Window。
在Android 9.0中把這個流程和DisplayContent綁定起來。讓我們稍微解剖一下這個方法。
relayout大致上要做了以下的事情:
relayout的方法有點長,本次我們將關注這一部分核心的邏輯。分別是兩個方法:
能看到在這裡面對performSurfacePlacementLoop做最多為6次的循環,這六次循環做什麼呢?
能看到這裡面的核心邏輯,首先會檢查WMS下mForceRemoves集合中是否還有對象。有則調用removeImmediately清空WindowState的中SurfaceControl和WindowContainer之間的綁定和Surface對象,以及銷毀WindowAnimator中的Surface。
做這個得到目的很簡單,因為下一個步驟將會申請一個Surface對象,而此時如果Android系統內存過大了(OOM),mForceRemoves就存在對象,就可以銷毀不需要的Surface。這一點的設計和Davlik虛擬機申請對象時候的思路倒是一致的。
銷毀需要一點時間,因此就需要做一個250毫秒的的等待。接著會調用RootWindowContainer的performSurfacePlacement做真正的執行。最後會通過handler通過ViewServer通知事件給DebugBridge調試類中。
每一次loop的最後,如果發現RootWindowContainer需要重新測量皮空,就會把當前這個方法,放入Handler中,等待下次的調用,也是調用6次。這樣就能最大限度的保證在這段時間內Window能夠測量每一次的窗體參數。
下面這個方法十分長,我們只看核心;
我在上面劃分了9個部分:
這里只給總覽,之後有機會再進去裡面抓細節。
我們能夠看到無論是在哪裡,如果窗口發生了變化,都會調用updateFocusedWindowLocked方法。實際上這個方法才是真正的核心測量窗口大小邏輯。
這里注意一下isWindowChange是判斷輸入法焦點是否一致,而窗體焦點則是通過不同的WindowState來判斷。
實際上核心測量的真正動作是DisplayContent.performLayout。我們仔細一想也就知道,在Android 9.0的時候,DisplayContent象徵著邏輯屏幕,我們討論無分屏的情況,實際上就是指我們當前窗體鋪滿邏輯顯示屏各個邊距的大小。
在正式開始聊窗體大小的測量之前,實際上,在Android系統中,為了把Window各個邊界標記出來,實際上隨著時代和慧握顫審美潮流的演進,誕生越來越多的邊距類型,我們往往可以通過這些邊距來測定窗體的大小。
在DisplayFrame中有了大致的分區,如下:
可以看到,這些窗體的邊距實際上是跟著這些年潮流走的。如Android 7.0的自由窗體模式,嵌套窗體模式,劉海屏等等,這些邊距的共同作用,才會誕生一個真正的Window大小。有了這些基礎知識之後,我們去看看測量大小的邏輯。
我前敗們這里把這個方法拆成如下幾個部分:
能看到,此時會設置當前顯示屏幕的大小,以及獲取過掃描區域,還會判斷當前手機屏幕是否支持劉海屏。這一切實際上都是由硬體回饋到DisplayService,我們再從中獲取的信息。
實際上如果有讀者注意到我寫的WMS第一篇就會看到實際上WMS初始化的時候,我們能夠看到WMS會初始化一個WindowManagerPolicy的策略,而這個策略就是PhoneWindowManager。實際上這也支持了系統開發自定義策略,從而辦到自己想要的窗體計算結果。
首先初始化幾個參數,父窗體,屏幕,過掃描,可見區域,輸入法區域為當前邏輯顯示屏的大小,等到後面做裁剪。
能看到所有的事情實際上是關注的是系統UI上的判斷,檢測NavBar,StatusBar大小。最後再判斷當前劉海屏的不允許交叉的區域頂部和顯示屏頂部哪個大。如果mDisplayCutoutSafe的top大於mUnrestricted的top,說明mDisplayCutoutSafe在mUnrestricted下面,也就是我上面那個包含一段黑色的區域。此時會拿穩定的應用區域和劉海區域頂部的最大值,作為劉海屏幕的區域。這樣就能保證劉海屏的頂部就是狀態欄。
提一句如果NavigationBar隱藏,則會創建一個虛假的區域把輸入事件都捕捉起來。
裡面有四個關鍵函數:
可以看到所有的所有的間距將會設置為mUnrestricted的初始寬高,也就是不包含OverScan區域。如果是遇到劉海屏,則會根據設置的SafeInset區域來設置mDisplayCutoutSafe的安全區域。也就是我上面那種情況。比如設置了LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT這種情況,顯示區域將不會超過劉海屏的底部。
我們關注到mTmpNavigationFrame這個對象的賦值,在正常的情況下的范圍是如下:
此時mStable和mStableFullscreen區域的底部都是對應著top,也就是對應著Navigation頂部。System系統元素的底部也是Navigation頂部。
最後經過computeFrameLw重新計算這個區域的值。這個方法稍後會聊到,但是在正常手機開發中,其實是沒有變化的。也就說,實際上對於mNavigationBar來說:
同理對於statusBar來說:
注意,此時如果statusBar可見,則做如下計算:
這種情況挺常見的,我們從一個隱藏狀態欄的頁面跳轉到有狀態欄的頁面,國有有個PopupWindow,你能看到這個popwindow會明顯向下移動。
在這個方法中mScreenDecorWindows這個集合實際上是在adjustWindowParamsLw以及prepareAddWindowLw這兩個方法中加入。加入的條件是,每當有新的Window加入(WMS的addView)或者Window需要重新調整(WMS的relayoutWindow),當前新增得到Window或者需要重新relayout的Window有StatusBar有許可權,且顯示則會添加到mScreenDecorWindows集合。
mScreenDecorWindows從上面的描述,能得知實際上這個步驟還沒有根據層級作區分。但是沒關系,此時僅僅只是初步的測量。
明白了mScreenDecorWindows之後,我們閱讀上面這個方法就很簡單了。
layoutScreenDecorWindows做的事情就如名字一樣,要測量Window上裝飾部分,如StatusBar,如輸入法。此時經過循環,自尾部往頭部調用所有的WindowState的computeFrameLw計算每一個WindowState的對應Window的窗體大小。
當計算出每一個窗體大小之後,將會把事件分成兩個情況,當計算出來的當前的Window的left和top都小於等於0,也就是說,當前的Window的頂部邊緣並且左邊緣超過了當前的屏幕。
說明了有什麼東西在右下側把整個Window定上去了。因此dockFrame的計算就很簡單了:
如果計算出來的bottom大於等於屏幕高度且right大於等於屏幕寬度。說明有什麼東西在左上方把整個Window頂下去了。
最後再設置這個把displayFrames的可見等區域都設置為dockFrame。聯合上下文,實際上這里就是把整個區域的頂部移動到了statusBar之下。
❷ 智能座艙——Android的WMS/AMS/PMS是什麼
智能座艙中的Android系統內部,有三個關鍵組件負責不同的功能:
首先,WindowManagerService (WMS) 是Android系統的核心組件,它負責窗口的創建和顯示順序管理。作為WindowManager的實際執行者,它介於View和底層的SurfaceFlinger之間,確保了界面的流暢和協調。
其次,Activity Manager Service (AMS) 是活動管理器,其核心職責是管理應用程序的生命周期和交互。它掌控著Activity的啟動、暫停、停止等操作,同時調度系統服務,確保應用程序許可權的正確管理,確保系統的穩定運行。
最後,Package Manager Service (PMS) 是Android系統中的關鍵組件,它扮演著超級管理員的角色,主要負責應用程序的安裝、卸載、更新等管理任務。無論是新應用的引入還是現有應用的維護,PMS都在後台默默進行,確保用戶界面的完整性和系統的安全性。
❸ Android WMS動畫系統初探(一)
Android WMS動畫系統初探(一)
Android WMS動畫系統初探(二)
Android WMS動畫系統初探(三)
Android中動畫的工作過程:在某一個時間點,調用getTransformation(),根據mStartTime和mDuration,計算出當前的進度,在根據mInterpolator計算出轉換的進度,然後計算出屬性的當前值,保存在matrix中。
再調用Matrix.getValues將屬性值取出,運用在動畫目標上。
[圖片上傳失敗...(image-8c5ae5-1636101404926)]
Animation
在給定了初始狀態、結束狀態、啟動時間與持續時間後,可以為使用者計算其動畫目標在任意時刻的變換(Transformation)
Transformation
描述了一個變換,包含兩個分量:透明度和一個二維變換矩陣
無論APP或者系統,都是可以直接向Choreographer注冊FrameCallback來實現動畫驅動的。
Choreographer 類似 Handler,處理回調的時機為屏幕的垂直同步(VSync)事件到來之時,其處理回調的過程被當作渲染下一幀的工作的一部分
postCallback(int callbackType, Runnable action, Object token)
postCallbackDelayed(int callbackType, Runnable action, Object token, delayMillis)
postFrameCallback(FrameCallback callback)
對於View動畫,動畫的目標就是View,而對於窗口來說,動畫的目標其實都是Surface,對 不同層級的SurfaceControl 進行操縱,會產生不同的動畫效果。
[圖片上傳失敗...(image-b25ef9-1636101404926)]
如上圖 WMS的結構層次可以簡單概括為:
RootWindowContainer -> DisplayContent -> DisplayArea -> Task -> WindowToken -> WindowState
[圖片上傳失敗...(image-2bdd8a-1636101404926)]
根據操縱層級的不同我把動畫分類為:窗口動畫、過渡動畫、Task動畫、全屏動畫等等
[圖片上傳失敗...(image-d5ed20-1636101404926)]
在窗口布局(relayout)階段調用到
WindowStateAnimator#commitFinishDrawingLocked ->
WindowState#performShowLocked ->
WindowStateAnimator#applyEnterAnimationLocked
開啟窗口動畫流程
frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java中定義了一個mSurfaceAnimator成員變數
SurfaceAnimator的startAnimation方法中創建Leash,可以通過SurfaceAnimator的類注釋了解 Leash
為什麼引入Leash可以參考此文: Android P——LockFreeAnimation
mAnimation.startAnimation這一步最終會通過LocalAnimationAdapter找到WMS里的SurfaceAnimationRunner進行執行。
這是 WindowContainer與SurfaceAnimtor、SurfaceAnimationRunner的持有關系 :
[圖片上傳失敗...(image-f741c0-1636101404926)]
往編舞者上拋的runnable是執行startAnimations方法
SurfaceAnimationRunner#startAnimations ->
SurfaceAnimationRunner#startPendingAnimationsLocked
會從mPendingAnimations遍歷RunningAnimation並執行startAnimationLocked
這一步構建了一個SfValueAnimator來真正的驅動動畫,每一幀的處理是通過WindowAnimationSpec構建真正要執行的動畫事務,然後使用mChoreographer.postCallback在下一個vsync信號到來時提交動畫事務。
ValueAnimator驅動動畫的原理本文就不做深入了。
下一篇文章我將進一步分析Activiy的過渡動畫和屏幕旋轉動畫的相關流程。
Android WMS動畫系統初探(二)
Android WMS動畫系統初探(三)