《深入理解Android 卷III》第四章 深入理解WindowManagerService
《深入理解Android 卷III》即將公布,作者是張大偉。此書填補(bǔ)了深入理解Android Framework卷中的一個(gè)主要空白。即Android Framework中和UI相關(guān)的部分。在一個(gè)特別講究顏值的時(shí)代,本書分析了Android 4.2中WindowManagerService、ViewRoot、Input系統(tǒng)、StatusBar、Wallpaper等重要“顏值繪制/處理”模塊
第4章 ?深入理解WindowManagerService(節(jié)選)
本章主要內(nèi)容:
·??演示樣例最原始最簡單的窗體創(chuàng)建方法
·??研究WMS的窗體管理結(jié)構(gòu)
·??探討WMS布局系統(tǒng)的工作原理
·??研究WMS動(dòng)畫系統(tǒng)的工作原理
本章涉及的源碼文件名稱及位置:
·??SystemServer.java
frameworks/base/services/java/com/android/server/SystemServer.java
·??WindowManagerService.java
frameworks/base/services/java/com/android/server/wm/WindowManagerService.java
·??ActivityStack.java
frameworks/base/services/java/com/android/server/am/ActivityStack.java
·??WindowState.java
frameworks/base/services/java/com/android/server/wm/WindowState.java
·??PhoneWindowManager.java
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
·??AccelerateDecelerateInterpolator.java
frameworks/base/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
·??Animation.java
frameworks/base/core/java/android/view/animation/Animation.java
·??AlphaAnimation.java
frameworks/base/core/java/android/view/animation/AlphaAnimation.java
·??WindowAnimator.java
frameworks/base/services/java/com/android/server/wm/WindowAnimator.java
·??WindowStateAnimator.java
frameworks/base/services/java/com/android/server/wm/WindowStateAnimator.java
4.1 初識(shí)WindowManagerService
WindowManagerService(下面簡稱WMS)是繼ActivityManagerService與PackageManagerService之后又一個(gè)復(fù)雜卻十分重要的系統(tǒng)服務(wù)。
在介紹WMS之前。首先要了解窗體(Window)是什么。
Android系統(tǒng)中的窗體是屏幕上的一塊用于繪制各種UI元素并能夠響應(yīng)應(yīng)用戶輸入的一個(gè)矩形區(qū)域。
從原理上來講,窗體的概念是獨(dú)自占有一個(gè)Surface實(shí)例的顯示區(qū)域。
比如Dialog、Activity的界面、壁紙、狀態(tài)欄以及Toast等都是窗體。
《卷I》第8章曾具體介紹了一個(gè)Activity通過Surface來顯示自己的過程:
·??Surface是一塊畫布,應(yīng)用能夠隨心所欲地通過Canvas或者OpenGL在其上作畫。
·??然后通過SurfaceFlinger將多塊Surface的內(nèi)容依照特定的順序(Z-order)進(jìn)行混合并輸出到FrameBuffer,從而將Android“美麗的臉蛋”顯示給用戶。
既然每一個(gè)窗體都有一塊Surface供自己涂鴉。必定須要一個(gè)角色對全部窗體的Surface進(jìn)行協(xié)調(diào)管理。于是,WMS便應(yīng)運(yùn)而生。WMS為全部窗體分配Surface,掌管Surface的顯示順序(Z-order)以及位置尺寸,控制窗體動(dòng)畫,而且還是輸入系統(tǒng)的一重要的中轉(zhuǎn)站。
說明一個(gè)窗體擁有顯示和響應(yīng)用戶輸入這兩層含義,本章將側(cè)重于分析窗體的顯示,而響應(yīng)用戶輸入的過程則在第5章進(jìn)行具體的介紹。
本章將深入分析WMS的兩個(gè)基礎(chǔ)子系統(tǒng)的工作原理:
·??布局系統(tǒng)(Layout System)。計(jì)算與管理窗體的位置、層次。
·??動(dòng)畫系統(tǒng)(Animation System),依據(jù)布局系統(tǒng)計(jì)算的窗體位置與層次渲染窗體動(dòng)畫。
為了讓讀者對WMS的功能以及工作方式有一個(gè)初步地認(rèn)識(shí),并見識(shí)一下WMS的強(qiáng)大,本節(jié)將從一個(gè)簡單而奇妙的樣例開始WMS的學(xué)習(xí)之旅。
4.1.1 一個(gè)從命令行啟動(dòng)的動(dòng)畫窗體
1.SampleWindow的實(shí)現(xiàn)
在這一節(jié)里將編寫一個(gè)最簡單的Java程序SampleWindow。僅使用WMS的接口創(chuàng)建并渲染一個(gè)動(dòng)畫窗體。
此程序?qū)侀_Activity、Wallpaper等UI架構(gòu)的復(fù)雜性,直接了當(dāng)?shù)亟沂網(wǎng)MS的client怎樣申請、渲染并注銷自己的窗體。
同一時(shí)候這也初步地反應(yīng)了WMS的工作方式。
這個(gè)樣例非常easy。僅僅有三個(gè)文件:
·??SampleWindow.java 主程序源碼。
·??Android.mk 編譯腳本。
·??sw.sh 啟動(dòng)器。
分別看一下這三個(gè)文件的實(shí)現(xiàn):
[-->SampleWindow.java::SampleWindow]
package understanding.wms.samplewindow;
......
public class SampleWindow {
?
??? publicstatic void main(String[] args) {
??? ????try {
??????? ?????//SampleWindow.Run()是這個(gè)程序的主入口
????????????new SampleWindow().Run();
??? ????} catch (Exception e) {
???????????e.printStackTrace();
??? ????}
??? }
??? //IWindowSession 是client向WMS請求窗體操作的中間代理,而且是進(jìn)程唯一的
???IWindowSession mSession = null;
??? //InputChannel 是窗體接收用戶輸入事件的管道。在第5章中將對其進(jìn)行具體的探討
???InputChannel mInputChannel = new InputChannel();
?
??? // 下面的三個(gè)Rect保存了窗體的布局結(jié)果。
當(dāng)中mFrame表示了窗體在屏幕上的位置與尺寸
??? // 在4.4中將具體介紹它們的作用以及計(jì)算原理
??? RectmInsets = new Rect();
??? RectmFrame = new Rect();
??? RectmVisibleInsets = new Rect();
?
???Configuration mConfig = new Configuration();
??? // 窗體的Surface,在此Surface上進(jìn)行的繪制都將在此窗體上顯示出來
??? SurfacemSurface = new Surface();
??? // 用于在窗體上進(jìn)行畫圖的畫刷
??? PaintmPaint = new Paint();
??? // 加入窗體所需的令牌。在4.2節(jié)將會(huì)對其進(jìn)行介紹
??? IBindermToken = new Binder();
?
??? // 一個(gè)窗體對象。本例演示了怎樣將此窗體加入到WMS中,并在其上進(jìn)行繪制操作
??? MyWindowmWindow = new MyWindow();
?
??? //WindowManager.LayoutParams定義了窗體的布局屬性,包括位置、尺寸以及窗體類型等
???LayoutParams mLp = new LayoutParams();
?
???Choreographer mChoreographer = null;
??? //InputHandler 用于從InputChannel接收按鍵事件做出響應(yīng)
???InputHandler mInputHandler = null;
?
??? booleanmContinueAnime = true;
?
??? publicvoid Run() throws Exception{
??????? Looper.prepare();
??????? // 獲取WMS服務(wù)
???????IWindowManager wms = IWindowManager.Stub.asInterface(
????????????????????? ServiceManager.getService(Context.WINDOW_SERVICE));
?
??????? // 通過WindowManagerGlobal獲取進(jìn)程唯一的IWindowSession實(shí)例。它將用于向WMS
??????? // 發(fā)送請求。
注意這個(gè)函數(shù)在較早的Android版本號(hào)(如4.1)位于ViewRootImpl類中
??????? mSession= WindowManagerGlobal.getWindowSession(Looper.myLooper());
?
??????? // 獲取屏幕分辨率
???????IDisplayManager dm = IDisplayManager.Stub.asInterface(
??????????????????????? ServiceManager.getService(Context.DISPLAY_SERVICE));
???????DisplayInfo di = dm.getDisplayInfo(Display.DEFAULT_DISPLAY);
???????Point scrnSize = new Point(di.appWidth, di.appHeight);
??????? // 初始化WindowManager.LayoutParams
?????? ?initLayoutParams(scrnSize);
?
??????? // 將新窗體加入到WMS
??????? installWindow(wms);
?
??????? // 初始化Choreographer的實(shí)例。此實(shí)例為線程唯一。這個(gè)類的使用方法與Handler
??????? // 相似。只是它總是在VSYC同步時(shí)回調(diào)。所以比Handler更適合做動(dòng)畫的循環(huán)器[1]
??????? mChoreographer= Choreographer.getInstance();
?
??????? // 開始處理第一幀的動(dòng)畫
??????? scheduleNextFrame();
?
??????? // 當(dāng)前線程陷入消息循環(huán),直到Looper.quit()
??????? Looper.loop();
?
??????? // 標(biāo)記不要繼續(xù)繪制動(dòng)畫幀
??????? mContinueAnime= false;
?
??????? // 卸載當(dāng)前Window
??????? uninstallWindow(wms);
??? }
?
??? publicvoid initLayoutParams(Point screenSize) {
??????? // 標(biāo)記即將安裝的窗體類型為SYSTEM_ALERT。這將使得窗體的ZOrder順序比較靠前
???????mLp.type = LayoutParams.TYPE_SYSTEM_ALERT;
??????? mLp.setTitle("SampleWindow");
??????? // 設(shè)定窗體的左上角坐標(biāo)以及高度和寬度
???????mLp.gravity = Gravity.LEFT | Gravity.TOP;
???????mLp.x = screenSize.x / 4;
???????mLp.y = screenSize.y / 4;
???????mLp.width = screenSize.x / 2;
???????mLp.height = screenSize.y / 2;
??????? // 和輸入事件相關(guān)的Flag,希望當(dāng)輸入事件發(fā)生在此窗體之外時(shí),其它窗體也能夠接受輸入事件
???????mLp.flags = mLp.flags | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
??? }
?
??? publicvoid installWindow(IWindowManager wms) throws Exception {
??? ????// 首先向WMS聲明一個(gè)Token,不論什么一個(gè)Window都須要隸屬與一個(gè)特定類型的Token
??????? wms.addWindowToken(mToken,WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
??????? // 設(shè)置窗體所隸屬的Token
???????mLp.token = mToken;
??????? // 通過IWindowSession將窗體安裝進(jìn)WMS,注意,此時(shí)僅僅是安裝到WMS。本例的Window
??????? // 眼下仍然沒有有效的Surface。只是,經(jīng)過這個(gè)調(diào)用后。mInputChannel已經(jīng)能夠用來接受
??????? // 輸入事件了
??????? mSession.add(mWindow,0, mLp, View.VISIBLE, mInsets, mInputChannel);
??????? /*通過IWindowSession要求WMS對本窗體進(jìn)行又一次布局。經(jīng)過這個(gè)操作后。WMS將會(huì)為窗體
???????? 創(chuàng)建一塊用于繪制的Surface并保存在參數(shù)mSurface中。同一時(shí)候。這個(gè)Surface被WMS放置在
????????LayoutParams所指定的位置上 */
??????? mSession.relayout(mWindow,0, mLp, mLp.width, mLp.height, View.VISIBLE,
??????????????????????? 0, mFrame, mInsets,mVisibleInsets, mConfig, mSurface);
??????? if(!mSurface.isValid()) {
??????????? thrownew RuntimeException("Failed creating Surface.");
??????? }
??????? // 基于WMS返回的InputChannel創(chuàng)建一個(gè)Handler,用于監(jiān)聽輸入事件
??????? //mInputHandler一旦被創(chuàng)建,就已經(jīng)在監(jiān)聽輸入事件了
??????? mInputHandler= new InputHandler(mInputChannel, Looper.myLooper());
??? }
?
??? publicvoid uninstallWindow(IWindowManager wms) throws Exception {
??????? // 從WMS處卸載窗體
??????? mSession.remove(mWindow);
??????? // 從WMS處移除之前加入的Token
??????? wms.removeWindowToken(mToken);
??? }
?
??? publicvoid scheduleNextFrame() {
??????? // 要求在顯示系統(tǒng)刷新下一幀時(shí)回調(diào)mFrameRender。注意,僅僅回調(diào)一次
??????? mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION
???????????????, mFrameRender, null);
??? }
?
??? // 這個(gè)Runnable對象用以在窗體上描繪一幀
??? publicRunnable mFrameRender = new Runnable() {
???????@Override
??????? publicvoid run() {
??????????? try{
???????????????// 獲取當(dāng)期時(shí)間戳
???????????????long time = mChoreographer.getFrameTime() % 1000;
?
???????????????// 畫圖
???????????????if (mSurface.isValid()) {
???????????????????Canvas canvas = mSurface.lockCanvas(null);
???????????????????canvas.drawColor(Color.DKGRAY);
???????????????????canvas.drawRect(2 * mLp.width * time / 1000
??????????????????????????? - mLp.width, 0, 2 *mLp.width * time
??????????????????????????? / 1000, mLp.height,mPaint);
???????????????????mSurface.unlockCanvasAndPost(canvas);
???????????????????mSession.finishDrawing(mWindow);
???????????????}
?
??????????? if(mContinueAnime)
???????????????scheduleNextFrame();
???????????} catch (Exception e) {
???????????????e.printStackTrace();
???????????}
??????? }
??? };
?
??? // 定義一個(gè)類繼承InputEventReceiver。用以在其onInputEvent()函數(shù)中接收窗體的輸入事件
??? classInputHandler extends InputEventReceiver {
???????Looper mLooper = null;
??????? publicInputHandler(InputChannel inputChannel, Looper looper) {
??????????? super(inputChannel,looper);
??????????? mLooper= looper;
??????? }
???????@Override
??????? publicvoid onInputEvent(InputEvent event) {
??????????? if(event instanceof MotionEvent) {
???????????????MotionEvent me = (MotionEvent)event;
?? ?????????????if (me.getAction() ==MotionEvent.ACTION_UP) {
???????????????????// 退出程序
???????????????????mLooper.quit();
???????????????}
???????????}
??????????? super.onInputEvent(event);
??????? }
??? }
?
??? // 實(shí)現(xiàn)一個(gè)繼承自IWindow.Stub的類MyWindow。
??? classMyWindow extends IWindow.Stub {
??????? // 保持默認(rèn)的實(shí)現(xiàn)就可以
??? }
}
由于此程序使用了大量的隱藏API(即SDK中未定義這些API)。因此須要放在Android源碼環(huán)境中進(jìn)行編譯它。相應(yīng)的Android.mk例如以下:
[-->Android.mk]
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
?
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := samplewindow
include $(BUILD_JAVA_LIBRARY)
將這兩個(gè)文件放在$TOP/frameworks/base/cmds/samplewindow/下,然后用make或mm命令進(jìn)行編譯。終于生成的結(jié)果是samplewindow.jar,文件位置在out/target/<ProductName>/system/framework/下。將該文件通過adb push到手機(jī)的/system/framework/下。
提示讀者可使用Android4.2模擬器來運(yùn)行此程序。
然而,samplewindow.jar不是一個(gè)可運(yùn)行程序,。故。需借助Android的app_process工具來載入并運(yùn)行它。筆者編寫了一個(gè)腳本做為啟動(dòng)器:
[-->sw.sh]
base=/system
export CLASSPATH=$base/framework/samplewindow.jar
?
exec app_process $base/binunderstanding.wms.samplewindow.SampleWindow "$@"
?
注意app_process事實(shí)上就是大名鼎鼎的zygote。只是。僅僅有使用--zygote參數(shù)啟動(dòng)時(shí)它才會(huì)給改名為zygote[2],否則就像java –jar命令一樣,運(yùn)行指定類的main靜態(tài)函數(shù)。
在手機(jī)中運(yùn)行該腳本,其運(yùn)行結(jié)果是一個(gè)灰色的方塊不斷地從屏幕左側(cè)移動(dòng)到右側(cè),如圖4-1所看到的。
圖 4-1 SampleWindow在手機(jī)中的運(yùn)行效果
2.初識(shí)窗體的創(chuàng)建、繪制與銷毀
SampleWindow的這段代碼盡管簡單,可是卻非常好地提煉了一個(gè)窗體的創(chuàng)建、繪制以及銷毀的過程。注意,本例沒有使用不論什么 WMS以外的系統(tǒng)服務(wù)。也沒有使用Android系統(tǒng)四大組件的框架,也就是說。假設(shè)你愿意,能夠利用WMS實(shí)現(xiàn)自己的UI與應(yīng)用程序框架。這樣就能夠衍生出一個(gè)新的平臺(tái)了。
總結(jié)在client創(chuàng)建一個(gè)窗體的步驟:
·??獲取IWindowSession和WMS實(shí)例。client能夠通過IWindowSession向WMS發(fā)送請求。
·??創(chuàng)建并初始化WindowManager.LayoutParams。注意這里是WindowManager下的LayoutParams,它繼承自ViewGroup.LayoutParams類,并擴(kuò)展了一些窗體相關(guān)的屬性。當(dāng)中最重要的是type屬性。這個(gè)屬性描寫敘述了窗體的類型,而窗體類型正是WMS對多個(gè)窗體進(jìn)行ZOrder排序的依據(jù)。
·??向WMS加入一個(gè)窗體令牌(WindowToken)。本章興許將分析窗體令牌的概念,眼下讀者僅僅要知道。窗體令牌描寫敘述了一個(gè)顯示行為,而且WMS要求每一個(gè)窗體必須隸屬于某一個(gè)顯示令牌。
·??向WMS加入一個(gè)窗體。必須在LayoutParams中指明此窗體所隸屬于的窗體令牌,否則在某些情況下加入操作會(huì)失敗。
在SampleWindow中,不設(shè)置令牌也可成功?完畢加入操作。由于窗體的類型被設(shè)為TYPE_SYSTEM_ALERT,它是系統(tǒng)窗體的一種。
而對于系統(tǒng)窗體。WMS會(huì)自己主動(dòng)為其創(chuàng)建顯示令牌,故無需client擔(dān)心。此話題將會(huì)在后文進(jìn)行更具體的討論。
·??向WMS申請對窗體進(jìn)行又一次布局(relayout)。所謂的又一次布局,就是依據(jù)窗體新的屬性去調(diào)整其Surface相關(guān)的屬性。或者又一次創(chuàng)建一個(gè)Surface(比如窗體尺寸變化導(dǎo)致之前的Surface不滿足要求)。向WMS加入一個(gè)窗體之后,其僅僅是將它在WMS中進(jìn)行了注冊而已。僅僅有經(jīng)過又一次布局之后,窗體才擁有WMS為其分配的畫布。有了畫布,窗體之后就能夠隨時(shí)進(jìn)行繪制工作了。
而窗體的繪制步驟例如以下:
·??通過Surface.lock()函數(shù)獲取能夠在其上作畫的Canvas實(shí)例。
·??使用Canvas實(shí)例進(jìn)行作畫。
·??通過Surface.unlockCanvasAndPost()函數(shù)提交繪制結(jié)果。
提示關(guān)于Surface的原理與使用方法,請參考《卷 I》第8章“深入理解Surface系統(tǒng)”。
這是對Surface作畫的標(biāo)準(zhǔn)方法。
在client也能夠通過OpenGL進(jìn)行作畫。只是這超出了本書的討論范圍。另外,在SampleWindow樣例中使用了Choreographer類進(jìn)行了動(dòng)畫幀的安排。Choreographer意為編舞指導(dǎo),是Jelly Bean新增的一個(gè)工具類。其使用方法與Handler的post()函數(shù)非Z且不會(huì)再顯示新的窗體,則須要從WMS將之前加入的顯示令牌一并刪除。
3.窗體的概念
在SampleWindow樣例中,有一個(gè)名為mWindow(類型為IWindow)的變量。讀者可能會(huì)理所當(dāng)然地覺得它就是窗體了。
事實(shí)上這樣的認(rèn)識(shí)并不全然正確。
IWindow繼承自Binder,而且其Bn端位于應(yīng)用程序一側(cè)(在樣例中IWindow的實(shí)現(xiàn)類MyWindow就繼承自IWindow.Stub),于是其在WMS一側(cè)僅僅能作為一個(gè)回調(diào)。以及起到窗體Id的作用。
那么。窗體的本質(zhì)是什么呢?
是進(jìn)行繪制所使用的畫布:Surface。
當(dāng)一塊Surface顯示在屏幕上時(shí),就是用戶所看到的窗體了。client向WMS加入一個(gè)窗體的過程,事實(shí)上就是WMS為其分配一塊Surface的過程。一塊塊Surface在WMS的管理之下有序地排布在屏幕上,Android才得以呈現(xiàn)出多姿多彩的界面來。所以從這個(gè)意義上來講。WindowManagerService被稱之為SurfaceManagerService也說得通的。
于是,依據(jù)對Surface的操作類型能夠?qū)ndroid的顯示系統(tǒng)分為三個(gè)層次。如圖4-2所看到的。
圖 4-2 Android顯示系統(tǒng)的三個(gè)層次
在圖4-2中:
·??第一個(gè)層次是UI框架層,其工作為在Surface上繪制UI元素以及響應(yīng)輸入事件。
·??第二個(gè)層次為WMS,其主要工作在于管理Surface的分配、層級(jí)順序等。
·??第三層為SurfaceFlinger。負(fù)責(zé)將多個(gè)Surface混合并輸出。
經(jīng)過這個(gè)樣例的介紹,相信大家對WMS的功能有了一個(gè)初步的了解。接下來,我們要進(jìn)入WMS的內(nèi)部,通過其啟動(dòng)過程一窺它的構(gòu)成。
4.1.2WMS的構(gòu)成
俗話說,一個(gè)好漢三個(gè)幫!WMS的強(qiáng)大是由非常多重要的成員互相協(xié)調(diào)工作而實(shí)現(xiàn)的。了解WMS的構(gòu)成將會(huì)為我們深入探索WMS打下良好的基礎(chǔ),進(jìn)而分析它的啟動(dòng)過程。這是再合適只是了。
1.WMS的誕生
和其它的系統(tǒng)服務(wù)一樣,WMS的啟動(dòng)位于SystemServer.java中ServerThread類的run()函數(shù)內(nèi)。
[-->SystemServer.java::ServerThread.run()]
Public void run() {
??? ......
???WindowManagerService wm = null;
??? ......
??? try {
???????......
??????? // ①創(chuàng)建WMS實(shí)例
?????? /* 通過WindowManagerService的靜態(tài)函數(shù)main()創(chuàng)建WindowManagerService的實(shí)例。
?????????? 注意main()函數(shù)的兩個(gè)參數(shù)wmHandler和uiHandler。這兩個(gè)Handler分別運(yùn)行于由
?????????? ServerThread所創(chuàng)建的兩個(gè)名為“WindowManager”和“UI”的兩個(gè)HandlerThread中 */
??????? wm =WindowManagerService.main(context, power, display, inputManager,
??????? uiHandler,wmHandler,
??????? ????????????factoryTest !=SystemServer.FACTORY_TEST_LOW_LEVEL,
???????????????????!firstBoot, onlyCore);
??????? // 加入到ServiceManager中去
??? ????ServiceManager.addService(Context.WINDOW_SERVICE,wm);
???????......
??? catch(RuntimeException e) {
??????? ......
??? }
??? ......
??? try {
??????? //②初始化顯示信息
??????? wm.displayReady();
??? } catch(Throwable e) {......}
??? ......
??? try {
??????? // ③通知WMS,系統(tǒng)的初始化工作完畢
??????? wm.systemReady();
??? } catch(Throwable e) {......}
??? ......
}
由此能夠看出,WMS的創(chuàng)建分為三個(gè)階段:
·??創(chuàng)建WMS的實(shí)例。
·??初始化顯示信息。
·??處理systemReady通知。
接下來,將通過以上三個(gè)階段分析WMS從無到有的過程。
看一下WMS的main()函數(shù)的實(shí)現(xiàn):
[-->WindowManagerService.java::WindowManagerSrevice.main()]
public static WindowManagerService main(finalContext context,
??? finalPowerManagerService pm, final DisplayManagerService dm,
??? finalInputManagerService im,
??? finalHandler uiHandler, final Handler wmHandler,
??? finalboolean haveInputMethods, final boolean showBootMsgs,
??? finalboolean onlyCore) {
??? finalWindowManagerService[] holder = new WindowManagerService[1];
??? // 通過由SystemServer為WMS創(chuàng)建的Handler新建一個(gè)WindowManagerService對象
??? // 此Handler運(yùn)行在一個(gè)名為WindowManager的HandlerThread中
??? wmHandler.runWithScissors(newRunnable() {
???????@Override
??????? publicvoid run() {
??????????? holder[0]= new WindowManagerService(context, pm, dm, im,
??????????????????????????? uiHandler,haveInputMethods, showBootMsgs, onlyCore);
??????? }
??? }, 0);
??? returnholder[0];
}
注意Handler類在Android 4.2中新增了一個(gè)API:runWithScissors()。這個(gè)函數(shù)將會(huì)在Handler所在的線程中運(yùn)行傳入的Runnable對象。同一時(shí)候堵塞調(diào)用線程的運(yùn)行,直到Runnable對象的run()函數(shù)運(yùn)行完畢。
WindowManagerService.main()函數(shù)在ServerThread專為WMS創(chuàng)建的線程“WindowManager”上創(chuàng)建了一個(gè)WindowManagerService的新實(shí)例。WMS中全部須要的Looper對象,比如Handler、Choreographer等。將會(huì)運(yùn)行在“WindowManager”線程中。
接下來看一下其構(gòu)造函數(shù)。看一下WMS定義了哪些重要的組件。
[-->WindowManagerService.java::WindowManagerService.WindowManagerService()]
private WindowManagerService(Context context,PowerManagerService pm,
???????????DisplayManagerService displayManager, InputManagerService inputManager,
???????????Handler uiHandler,
??? booleanhaveInputMethods, boolean showBootMsgs, boolean onlyCore)
??? ......
??? mDisplayManager=
??? ?????????(DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
??? mDisplayManager.registerDisplayListener(this,null);
??? Display[]displays = mDisplayManager.getDisplays();
??? /* 初始化DisplayContent列表。DisplayContent是Android4.2為支持多屏幕輸出所引入的一個(gè)
?????? 概念。一個(gè)DisplayContent指代一塊屏幕,屏幕能夠是手機(jī)自身的屏幕。也能夠是基于Wi-FiDisplay
?????? 技術(shù)的虛擬屏幕[3]*/
??? for(Display display : displays) {
??????? createDisplayContentLocked(display);
??? }
??? .....
??? /* 保存InputManagerService。輸入事件終于要分發(fā)給具有焦點(diǎn)的窗體。而WMS是窗體管理者,
?????? 所以WMS是輸入系統(tǒng)中的重要一環(huán)。關(guān)于輸入系統(tǒng)的內(nèi)容將在第5章中深入探討*/
??? mInputManager= inputManager;
?
??? // 這個(gè)看起來其貌不揚(yáng)的mAnimator,事實(shí)上具有非常關(guān)鍵的數(shù)據(jù)。它管理著全部窗體的動(dòng)畫
??? mAnimator= new WindowAnimator(this, context, mPolicy);
?
??? // 在“UI“線程中將對還有一個(gè)重要成員mPolicy,也就是WindowManagerPolicy進(jìn)行初始化
??? initPolicy(uiHandler);
?
??? // 將自己加入到Watchdog中
??? Watchdog.getInstance().addMonitor(this);
??? ......
}
第二步。displayReady()函數(shù)的調(diào)用主要是初始化顯示尺寸的信息。其內(nèi)容比較瑣碎。這里就先不介紹了。只是值得注意的一點(diǎn)是。再displayReady()完畢后。WMS會(huì)要求ActivityManagerService進(jìn)行第一次Configuration的更新。
第三步。在systemReady()函數(shù)中。WMS本身將不會(huì)再做不論什么操作了,直接調(diào)用mPolicy的systemReady()函數(shù)。
2.WMS的重要成員
總結(jié)一下在WMS的啟動(dòng)過程中所創(chuàng)建的重要成員,參考圖4-3。
圖 4-3 WMS的重要成員
下面是對圖4-3中重要成員的簡介:
·??mInputManager,InputManagerService(輸入系統(tǒng)服務(wù))的實(shí)例。用于管理每一個(gè)窗體的輸入事件通道(InputChannel)以及向通道上派發(fā)事件。關(guān)于輸入系統(tǒng)的具體內(nèi)容將在本書第5章具體探討。
·??mChoreographer,Choreographer的實(shí)例,在SampleWindow的樣例中已經(jīng)見過了。Choreographer的意思是編舞指導(dǎo)。
它擁有從顯示子系統(tǒng)獲取VSYNC同步事件的能力,從而能夠在合適的時(shí)機(jī)通知渲染動(dòng)作,避免在渲染的過程中由于發(fā)生屏幕重繪而導(dǎo)致的畫面撕裂。從這個(gè)意義上來講,Choreographer的確是指導(dǎo)Android翩翩起舞的大師。WMS使用Choreographer負(fù)責(zé)驅(qū)動(dòng)全部的窗體動(dòng)畫、屏幕旋轉(zhuǎn)動(dòng)畫、墻紙動(dòng)畫的渲染。
·??mAnimator。WindowAnimator的實(shí)例。它是全部窗體動(dòng)畫的總管(窗體動(dòng)畫是一個(gè)WindowStateAnimator的對象)。在Choreographer的驅(qū)動(dòng)下。逐個(gè)渲染全部的動(dòng)畫。
·??mPolicy,WindowPolicyManager的一個(gè)實(shí)現(xiàn)。眼下它僅僅有PhoneWindowManager一個(gè)實(shí)現(xiàn)類。mPolicy定義了非常多窗體相關(guān)的策略,能夠說是WMS的首席顧問。每當(dāng)WMS要做什么事情的時(shí)候,都須要向這個(gè)顧問請教應(yīng)當(dāng)怎樣做。比如,告訴WMS某一個(gè)類型的Window的ZOrder的值是多少,幫助WMS矯正不合理的窗體屬性。會(huì)為WMS監(jiān)聽屏幕旋轉(zhuǎn)的狀態(tài),還會(huì)預(yù)處理一些系統(tǒng)按鍵事件(比如HOME、BACK鍵等的默認(rèn)行為就是在這里實(shí)現(xiàn)的)。等等。所以。mPolicy可謂是WMS中最重要的一個(gè)成員了。
·??mDisplayContents,一個(gè)DisplayContent類型的列表。Android4.2支持基于Wi-fi Display的多屏幕輸出,而一個(gè)DisplayContent描寫敘述了一塊能夠繪制窗體的屏幕。
每一個(gè)DisplayContent都用一個(gè)整型變量作為其ID。當(dāng)中手機(jī)默認(rèn)屏幕的ID由Display.DEFAULT_DISPLAY常量指定。DisplayContent的管理是由DisplayManagerService完畢的,在本章不會(huì)去探討DisplayContent的實(shí)現(xiàn)細(xì)節(jié)。而是關(guān)注DisplayContent對窗體管理與布局的影響。
下面的幾個(gè)成員的初始化并沒有出如今構(gòu)造函數(shù)中。只是它們的重要性一點(diǎn)也不亞于上面幾個(gè)。
·??mTokenMap,一個(gè)HashMap,保存了全部的顯示令牌(類型為WindowToken)。用于窗體管理。
在SampleWindow樣例中以前提到過,一個(gè)窗體必須隸屬于某一個(gè)顯示令牌。在那個(gè)樣例中所加入的令牌就被放進(jìn)了這個(gè)HashMap中。從這個(gè)成員中還衍生出幾個(gè)輔助的顯示令牌的子集。比如mAppTokens保存了全部屬于Activity的顯示令牌(WindowToken的子類AppWindowToken),mExitingTokens則保存了正在退出過程中的顯示令牌等。當(dāng)中mAppTokens列表是有序的,它與AMS中的mHistory列表的順序保持一致。反映了系統(tǒng)中Activity的順序。
·??mWindowMap,也是一個(gè)HashMap。保存了全部窗體的狀態(tài)信息(類型為WindowState),用于窗體管理。
在SampleWindow樣例中,使用IWindowSession.add()所加入的窗體的狀態(tài)將會(huì)被保存在mWindowMap中。與mTokenMap一樣,mWindowMap一樣有衍生出的子集。
比如mPendingRemove保存了那些退出動(dòng)畫播放完畢并即將被移除的窗體,mLosingFocus則保存了那些失去了輸入焦點(diǎn)的窗體。在DisplayContent中,也有一個(gè)windows列表,這個(gè)列表存儲(chǔ)了顯示在此DisplayContent中的窗體,而且它是有序的。
窗體在這個(gè)列表中的位置決定了其終于顯示時(shí)的Z序。
·??mSessions。一個(gè)List。元素類型為Session。Session事實(shí)上是SampleWindow樣例中的IWindowSession的Bn端。也就是說,mSessions這個(gè)列表保存了當(dāng)前全部想向WMS尋求窗體管理服務(wù)的client。
注意Session是進(jìn)程唯一的。
·??mRotation,僅僅是一個(gè)int型變量。
它保存了當(dāng)前手機(jī)的旋轉(zhuǎn)狀態(tài)。
WMS定義的成員一定不止這些,可是它們是WMS每一種功能最核心的變量。讀者在這里能夠線對它們有一個(gè)感性的認(rèn)識(shí)。在本章興許的內(nèi)容里將會(huì)具體分析它們在WMS的各種工作中所發(fā)揮的核心作用。
4.1.3 初識(shí)WMS的小結(jié)
這一節(jié)通過SampleWindow的樣例向讀者介紹了WMS的client怎樣使用窗體,然后通過WMS的誕生過程簡單剖析了一下WMS的重要成員組成。以期通過本節(jié)的學(xué)習(xí)能夠?yàn)榕d許的學(xué)習(xí)打下基礎(chǔ)。
從下一節(jié)開始,我們將會(huì)深入探討WMS的工作原理。
4.2 WMS的窗體管理結(jié)構(gòu)
經(jīng)過上一節(jié)的介紹,讀者應(yīng)該對WMS的窗體管理有了一個(gè)感性的認(rèn)識(shí)。從這一節(jié)開將深入WMS的內(nèi)部去剖析其工作流程。
依據(jù)前述內(nèi)容可知,SampleWindow加入窗體的函數(shù)是IWindowSession.add()。
IWindowSession是WMS與client交互的一個(gè)代理,add則直接調(diào)用到了WMS的addWindow()函數(shù)。
我們將從這個(gè)函數(shù)開始WMS之旅。
本小節(jié)僅僅討論它的前半部分。
注意由于篇幅所限,本章不準(zhǔn)備討論removeWindow的實(shí)現(xiàn)。
[-->WindowManagerService.java::WindowManagerService.addWindow()Part1]
public int addWindow(Session session, IWindowclient, int seq,
???????????WindowManager.LayoutParams attrs, int viewVisibility,int displayId
???????????Rect outContentInsets, InputChannel outInputChannel) {
?
??????? // 首先檢查權(quán)限,沒有權(quán)限的client不能加入窗體
??????? intres = mPolicy.checkAddPermission(attrs);
?
???????......
?
??????? // 當(dāng)為某個(gè)窗體加入子窗體時(shí),attachedWindow將用來保存父窗體的實(shí)例
???????WindowState attachedWindow = null;
??????? //win就是即將被加入的窗體了
???????WindowState win = null;
???????......
??????? finalint type = attrs.type;
??????? synchronized(mWindowMap){
???????????......
???????????//①獲取窗體要加入到的DisplayContent
???????????/* 在加入窗體時(shí),必須通過displayId參數(shù)指定加入到哪一個(gè)DisplayContent。
??????????????SampleWindow樣例沒有指定displayId參數(shù)。Session會(huì)替SampleWindow選擇
??????????????DEFAULT_DISPLAY,也就是手機(jī)屏幕 */
??????????? finalDisplayContent displayContent = getDisplayContentLocked(displayId);
??????????? if(displayContent == null) {
???????????????return WindowManagerGlobal.ADD_INVALID_DISPLAY;
???????????}
???????????......
???????????// 假設(shè)要加入的窗體是還有一個(gè)的子窗體,就要求父窗體必須已經(jīng)存在
??????????? // 注意。 attrs.type表示了窗體的類型,attrs.token則表示了窗體所隸屬的對象
???????????// 對于子窗體來說。attrs.token表示了父窗體
??????????? if(type >= FIRST_SUB_WINDOW &&.type <= LAST_SUB_WINDOW) {
???????????????attachedWindow = windowForClientLocked(null, attrs.token, false);
???????????????if (attachedWindow == null) {
???????????????????return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
???????????????}
???????????????//在這里還能夠看出WMS要求窗體的層級(jí)關(guān)系最多為兩層
???????????????if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
??????????????????????????????? &&attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
????????? ??????????return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
???????????????}
???????????}
?
??????????? booleanaddToken = false;
???????????// ②WindowToken出場!
依據(jù)client的attrs.token取出已注冊的WindowToken
???????????WindowToken token = mTokenMap.get(attrs.token);
???????????// 下面的if語句塊初步揭示了WindowToken和窗體之間的關(guān)系
??????????? if(token == null) {
???????????????// 對于下面幾種類型的窗體,必須通過LayoutParams.token成員為其指定一個(gè)已經(jīng)
???????????????// 加入至WMS的WindowToken
???????????????if (type >= FIRST_APPLICATION_WINDOW
??????????????????????????????? && type<= LAST_APPLICATION_WINDOW) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????????if (type == TYPE_INPUT_METHOD) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????????if (type == TYPE_WALLPAPER) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????????if (type == TYPE_DREAM) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????????// 其它類型的窗體則不須要事先向WMS加入WindowToken由于WMS會(huì)在這里隱式地創(chuàng)
???????????????// 建一個(gè)。注意最后一個(gè)參數(shù)false,這表示此WindowToken由WMS隱式創(chuàng)建。
???????????????token = new WindowToken(this, attrs.token, -1, false);
? ??????????????addToken = true;
???????????} else if (type >= FIRST_APPLICATION_WINDOW
???????????????????????????????????????????? &&type <= LAST_APPLICATION_WINDOW) {
???????????????// 對于APPLICATION類型的窗體,要求相應(yīng)的WindowToken的類型也為APPLICATION
???????????????// 而且是WindowToken的子類:AppWindowToken
???????????????AppWindowToken atoken = token.appWindowToken;
???????????????if (atoken == null) {
???????????????????return WindowManagerImpl.ADD_NOT_APP_TOKEN;
???????????????} else if (atoken.removed) {
????????? ??????????returnWindowManagerImpl.ADD_APP_EXITING;
???????????????}
???????????????if (type==TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn){
???????????????????return WindowManagerImpl.ADD_STARTING_NOT_NEEDED;
???????????????}
???????????} else if (type == TYPE_INPUT_METHOD) {
???????????????// 對于其它幾種類型的窗體也有相似的要求:窗體類型必須與WindowToken的類型一致
???????????????if (token.windowType != TYPE_INPUT_METHOD) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????} else if (type == TYPE_WALLPAPER) {
???????????????if (token.windowType != TYPE_WALLPAPER) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????} else if (type == TYPE_DREAM) {
???????????????if (token.windowType != TYPE_DREAM) {
???????????????????return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
???????????????}
???????????}
?
???????????// ③WMS為要加入的窗體創(chuàng)建了一個(gè)WindowState對象
???????????// 這個(gè)對象維護(hù)了一個(gè)窗體的全部狀態(tài)信息
??????????? win= new WindowState(this, session, client, token,
??????????? attachedWindow,seq, attrs, viewVisibility, displayContent);
?
???????????......
???????????// WindowManagerPolicy出場了。這個(gè)函數(shù)的調(diào)用會(huì)調(diào)整LayoutParams的一些成員的取值
??????????? mPolicy.adjustWindowParamsLw(win.mAttrs);
?
??????????? res= mPolicy.prepareAddWindowLw(win, attrs);
??????????? if(res != WindowManagerGlobal.ADD_OKAY) {
???????????????return res;
???????????}
?
???????????// 接下來將剛剛隱式創(chuàng)建的WindowToken加入到mTokenMap中去。通過這行代碼應(yīng)該
???????????//讀者應(yīng)該能想到。全部的WindowToken都被放入這個(gè)HashTable中
??????????? ......
?
??????????? if(addToken) {
???????????????mTokenMap.put(attrs.token, token);
???????????}
??????????? win.attach();
???????????// 然后將WindowState對象加入到mWindowMap中
??????????? mWindowMap.put(client.asBinder(),win);
???????????// 剩下的代碼稍后再做分析
???????????......
??????? }
??? }
addWindow()函數(shù)的前段代碼展示了三個(gè)重要的概念,各自是WindowToken、WindowState以及DisplayContent。而且在函數(shù)開始處對窗體類型的檢查推斷也初步揭示了它們之間的關(guān)系:除子窗體外,加入不論什么一個(gè)窗體都必須指明其所屬的WindowToken;窗體在WMS中通過一個(gè)WindowState實(shí)例進(jìn)行管理和保管。同一時(shí)候必須在窗體中指明其所屬的DisplayContent,以便確定窗體將被顯示到哪一個(gè)屏幕上。
4.2.1 理解WindowToken
1.WindowToken的意義
為了搞清晰WindowToken的作用是什么。看一下其位于WindowToken.java中的定義。盡管它未定義不論什么函數(shù),但其成員變量的意義卻非常重要。
·??WindowToken將屬于同一個(gè)應(yīng)用組件的窗體組織在了一起。所謂的應(yīng)用組件能夠是Activity、InputMethod、Wallpaper以及Dream。在WMS對窗體的管理過程中,用WindowToken指代一個(gè)應(yīng)用組件。比如在進(jìn)行窗體ZOrder排序時(shí)。屬于同一個(gè)WindowToken的窗體會(huì)被安排在一起,而且在當(dāng)中定義的一些屬性將會(huì)影響全部屬于此WindowToken的窗體。這些都表明了屬于同一個(gè)WindowToken的窗體之間的緊密聯(lián)系。
·??WindowToken具有令牌的作用,是相應(yīng)用組件的行為進(jìn)行規(guī)范管理的一個(gè)手段。
WindowToken由應(yīng)用組件或其管理者負(fù)責(zé)向WMS聲明并持有。應(yīng)用組件在須要新的窗體時(shí)。必須提供WindowToken以表明自己的身份,而且窗體的類型必須與所持有的WindowToken的類型一致。
從上面的代碼能夠看到,在創(chuàng)建系統(tǒng)類型的窗體時(shí)不須要提供一個(gè)有效的Token,WMS會(huì)隱式地為其聲明一個(gè)WindowToken,看起來誰都能夠加入個(gè)系統(tǒng)級(jí)的窗體。難道Android為了內(nèi)部使用方便而置安全于不顧嗎?非也。addWindow()函數(shù)一開始的mPolicy.checkAddPermission()的目的就是如此。它要求client必須擁有SYSTEM_ALERT_WINDOW或INTERNAL_SYSTEM_WINDOW權(quán)限才干創(chuàng)建系統(tǒng)類型的窗體。
2.向WMS聲明WindowToken
既然應(yīng)用組件在創(chuàng)建一個(gè)窗體時(shí)必須指定一個(gè)有效的WindowToken才行,那么WindowToken到底該怎樣聲明呢?
在SampleWindow應(yīng)用中,使用wms.addWindowToken()函數(shù)聲明mToken作為它的令牌,所以在加入窗體時(shí),通過設(shè)置lp.token為mToken向WMS進(jìn)行出示。從而獲得WMS加入窗體的許可。
這說明。僅僅要是一個(gè)Binder對象(隨便一個(gè)),都能夠作為Token向WMS進(jìn)行聲明。對于WMS的client來說,Token僅僅是一個(gè)Binder對象而已。
為了驗(yàn)證這一點(diǎn),來看一下addWindowToken的代碼。例如以下所看到的:
[-->WindowManagerService.java::WindowManagerService.addWindowToken()]
???@Override
??? publicvoid addWindowToken(IBinder token, int type) {
??????? // 須要聲明Token的調(diào)用者擁有MANAGE_APP_TOKENS的權(quán)限
??????? if(!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
???????????????"addWindowToken()")) {
??????????? thrownew SecurityException("Requires MANAGE_APP_TOKENS permission");
??????? }
?
??????? synchronized(mWindowMap){
??????????? ......
???????????// 注意其構(gòu)造函數(shù)的參數(shù)與addWindow()中不同。最后一個(gè)參數(shù)為true,表明這個(gè)Token
???????????// 是顯式申明的
??????????? wtoken= new WindowToken(this, token, type, true);
??????????? mTokenMap.put(token,wtoken);
??????????? ......
??????? }
??? }
使用addWindowToken()函數(shù)聲明Token,將會(huì)在WMS中創(chuàng)建一個(gè)WindowToken實(shí)例。并加入到mTokenMap中。鍵值為client用于聲明Token的Binder實(shí)例。與addWindow()函數(shù)中隱式地創(chuàng)建WindowToken不同。這里的WindowToken被聲明為顯式的。隱式與顯式的差別在于,當(dāng)隱式創(chuàng)建的WindowToken的最后一個(gè)窗體被移除后,此WindowToken會(huì)被一并從mTokenMap中移除。顯式創(chuàng)建的WindowToken僅僅能通過removeWindowToken()顯式地移除。
addWindowToken()這個(gè)函數(shù)告訴我們。WindowToken事實(shí)上有兩層含義:
·??對于顯示組件(client)而言的Token。是隨意一個(gè)Binder的實(shí)例,對顯示組件(client)來說僅僅是一個(gè)創(chuàng)建窗體的令牌,沒有其它的含義。
·??對于WMS而言的WindowToken這是一個(gè)WindowToken類的實(shí)例,保存了相應(yīng)于client一側(cè)的Token(Binder實(shí)例)。并以這個(gè)Token為鍵。存儲(chǔ)于mTokenMap中。
client一側(cè)的Token是否已被聲明,取決于其相應(yīng)的WindowToken是否位于mTokenMap中。
注意在普通情況下,稱顯示組件(client)一側(cè)Binder的實(shí)例為Token,而稱WMS一側(cè)的WindowToken對象為WindowToken。可是為了敘述方便,在沒有歧義的前提下不會(huì)過分細(xì)致地區(qū)分這兩個(gè)概念。
接下來,看一下各種顯示組件是怎樣聲明WindowToken的。
(1)??? Wallpaper和InputMethod的Token
Wallpaper的Token聲明在WallpaperManagerService中。
參考下面代碼:
[-->WallpaperManagerService.java::WallpaperManagerService.bindWallpaperComponentLocked()]
BooleanbindWallpaperComponentLocked(......) {
??? ......
???WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
??? ......
??? mIWindowManager.addWindowToken(newConn.mToken,
?????????????????????????WindowManager.LayoutParams.TYPE_WALLPAPER);
??? ......
}
WallpaperManagerService是Wallpaper管理器,它負(fù)責(zé)維護(hù)系統(tǒng)已安裝的全部的Wallpaper并在它們之間進(jìn)行切換。而這個(gè)函數(shù)的目的是準(zhǔn)備顯示一個(gè)Wallpaper。
newConn.mToken與SampleWindow樣例一樣。是一個(gè)簡單的Binder對象。
這個(gè)Token將在即將顯示出來的Wallpaper被連接時(shí)傳遞給它,之后Wallpaper就可以通過這個(gè)Token向WMS申請創(chuàng)建繪制壁紙所需的窗體了。
注意 WallpaperManagerService向WMS聲明的Token類型為TYPE_WALLPAPER,所以,Wallpaper僅能本分地創(chuàng)建TYPE_WALLPAPER類型的窗體。
相應(yīng)的。WallpaperManagerService會(huì)在detachWallpaperLocked()函數(shù)中取消對Token的聲明:
[-->WallpaperManagerService.java::WallpaperManagerService.detachWallpaperLocked()]
booleandetachWallpaperLocked(WallpaperData wallpaper){
??? ......
??? mIWindowManager.removeWindowToken(wallpaper.connection.mToken);
??? ......
}
再此之后。假設(shè)這個(gè)被detach的Wallpaper想再要?jiǎng)?chuàng)建窗體便不再可能了。
WallpaperManagerService使用WindowToken對一個(gè)特定的Wallpaper做出了例如以下限制:
·??Wallpaper僅僅能創(chuàng)建TYPE_WALLPAPER類型的窗體。
·??Wallpaper顯示的生命周期由WallpaperManagerService牢牢地控制著。僅有當(dāng)前的Wallpaper才干創(chuàng)建窗體并顯示內(nèi)容。其它的Wallpaper由于沒有有效的Token,而無法創(chuàng)建窗體。
InputMethod的Token的來源與Wallpaper相似,其聲明位于InputMethodManagerService的startInputInnerLocked()函數(shù)中,取消聲明的位置在InputmethodManagerService的unbindCurrentMethodLocked()函數(shù)。InputMethodManagerService通過Token限制著每一個(gè)InputMethod的窗體類型以及顯示生命周期。
(2)??? Activity的Token
Activity的Token的使用方式與Wallpaper和InputMethod相似。可是其包括很多其它的內(nèi)容。
畢竟,對于Activity。不管是其組成還是操作都比Wallpaper以及InputMethod復(fù)雜得多。
對此。WMS專為Activity實(shí)現(xiàn)了一個(gè)WindowToken的子類:AppWindowToken。
既然AppWindowToken是為Activity服務(wù)的。那么其聲明自然在ActivityManagerService中。具體位置為ActivityStack.startActivityLocked(),也就是啟動(dòng)Activity的時(shí)候。相關(guān)代碼例如以下:
[-->ActivityStack.java::ActivityStack.startActivityLocked()]
private final void startActivityLocked(......) {
??? ......
??? mService.mWindowManager.addAppToken(addPos,r.appToken, r.task.taskId,
???????????????????????????????r.info.screenOrientation, r.fullscreen);
??? ......
}
startActivityLocked()向WMS聲明r.appToken作為此Activity的Token,這個(gè)Token是在ActivityRecord的構(gòu)造函數(shù)中創(chuàng)建的。隨然后在realStartActivityLocked()中將此Token交付給即將啟動(dòng)的Activity。
[-->ActivityStack.java::ActivityStack.realStartActivityLocked()]
final boolean realStartActivityLocked(......) {
??? ......
??? app.thread.scheduleLaunchActivity(newIntent(r.intent), r.appToken,
????? ??????????System.identityHashCode(r), r.info,
?????? ?????????newConfiguration(mService.mConfiguration),
???? ???????????r.compat, r.icicle, results, newIntents,!andResume,
??? mService.isNextTransitionForward(),profileFile, profileFd,
????? ??????????profileAutoStop);
??? ......
}
啟動(dòng)后的Activity就可以使用此Token創(chuàng)建類型為TYPE_APPLICATION的窗體了。
取消Token的聲明則位于ActivityStack.removeActivityFromHistoryLocked()函數(shù)中。
Activity的Token在client是否和Wallpaper一樣,僅僅是一個(gè)主要的Binder實(shí)例呢?事實(shí)上不然。看一下r.appToken的定義能夠發(fā)現(xiàn)。這個(gè)Token的類型是IApplicationToken.Stub。當(dāng)中定義了一系列和窗體相關(guān)的一些通知回調(diào),它們是:
·?? windowsDrawn()。當(dāng)窗體完畢初次繪制后通知AMS。
·?? windowsVisible(),當(dāng)窗體可見時(shí)通知AMS。
·?? windowsGone(),當(dāng)窗體不可見時(shí)通知AMS。
·?? keyDispatchingTimeout(),窗體沒能按時(shí)完畢輸入事件的處理。這個(gè)回調(diào)將會(huì)導(dǎo)致ANR。
·?? getKeyDispatchingTimeout(),從AMS處獲取界定ANR的時(shí)間。
AMS通過ActivityRecord表示一個(gè)Activity。而ActivityRecord的appToken在其構(gòu)造函數(shù)中被創(chuàng)建,所以每一個(gè)ActivityRecord擁有其各自的appToken。
而WMS接受AMS對Token的聲明。并為appToken創(chuàng)建了唯一的一個(gè)AppWindowToken。
因此,這個(gè)類型為IApplicationToken的Binder對象appToken粘結(jié)了AMS的ActivityRecord與WMS的AppWindowToken。僅僅要給定一個(gè)ActivityRecord。都能夠通過appToken在WMS中找到一個(gè)相應(yīng)的AppWindowToken,從而使得AMS擁有了操縱Activity的窗體繪制的能力。比如,當(dāng)AMS覺得一個(gè)Activity須要被隱藏時(shí)。以Activity相應(yīng)的ActivityRecord所擁有的appToken作為參數(shù)調(diào)用WMS的setAppVisibility()函數(shù)。此函數(shù)通過appToken找到其相應(yīng)的AppWindowToken,然后將屬于這個(gè)Token的全部窗體隱藏。
注意每當(dāng)AMS由于某些原因(如啟動(dòng)/結(jié)束一個(gè)Activity,或?qū)ask移到前臺(tái)或后臺(tái))而調(diào)整ActivityRecord在mHistory中的順序時(shí),都會(huì)調(diào)用WMS相關(guān)的接口移動(dòng)AppWindowToken在mAppTokens中的順序。以保證兩者的順序一致。在后面解說窗體排序規(guī)則時(shí)會(huì)介紹到,AppWindowToken的順序?qū)Υ绑w的順序影響非常大。
4.2.2 理解WindowState
從WindowManagerService.addWindow()函數(shù)的實(shí)現(xiàn)中能夠看出,當(dāng)向WMS加入一個(gè)窗體時(shí),WMS會(huì)為其創(chuàng)建一個(gè)WindowState。
WindowState表示一個(gè)窗體的全部屬性,所以它是WMS中事實(shí)上的窗體。
這些屬性將在后面遇到時(shí)再做介紹。
相似于WindowToken。WindowState在顯示組件一側(cè)也有個(gè)相應(yīng)的類型:IWindow.Stub。IWindow.Stub提供了非常多與窗體管理相關(guān)通知的回調(diào)。比如尺寸變化、焦點(diǎn)變化等。
另外,從WindowManagerService.addWindow()函數(shù)中看到新的WindowState被保存到mWindowMap中,鍵值為IWindow的Bp端。mWindowMap是整個(gè)系統(tǒng)全部窗體的一個(gè)全集。
說明對照一下mTokenMap和mWindowMap。
這兩個(gè)HashMap維護(hù)了WMS中最重要的兩類數(shù)據(jù):WindowToken及WindowState。
它們的鍵都是IBinder,差別是: mTokenMap的鍵值可能是IAppWindowToken的Bp端(使用addAppToken()進(jìn)行聲明)。或者是其它隨意一個(gè)Binder的Bp端(使用addWindowToken()進(jìn)行聲明);而mWindowToken的鍵值一定是IWindow的Bp端。
關(guān)于WindowState的很多其它細(xì)節(jié)將在后面的講述中進(jìn)行介紹。只是經(jīng)過上面的分析,不難得到WindowToken和WindowState之間的關(guān)系,參考圖4-4。
圖 4-4 WindowToken與WindowState的關(guān)系
更具體一些,以一個(gè)正在回放視頻并彈出兩個(gè)對話框的Activity為例。WindowToken與WindowState的意義如圖4-5所看到的。
圖 4-5WindowState與WindowToken的從屬關(guān)系
4.2.3理解DisplayContent??????????????
假設(shè)說WindowToken依照窗體之間的邏輯關(guān)系將其分組。那么DisplayContent則依據(jù)窗體的顯示位置將其分組。隸屬于同一個(gè)DisplayContent的窗體將會(huì)被顯示在同一個(gè)屏幕中。每一個(gè)DisplayContent都相應(yīng)這一個(gè)唯一的ID。在加入窗體時(shí)能夠通過指定這個(gè)ID決定其將被顯示在那個(gè)屏幕中。
DisplayContent是一個(gè)非常具有隔離性的一個(gè)概念。
處于不同DisplayContent的兩個(gè)窗體在布局、顯示順序以及動(dòng)畫處理上不會(huì)產(chǎn)生不論什么耦合。因此,就這幾個(gè)方面來說,DisplayContent就像一個(gè)孤島,全部這些操作都能夠在其內(nèi)部獨(dú)立運(yùn)行。因此,這些本來屬于整個(gè)WMS全局性的操作,變成了DisplayContent內(nèi)部的操作了。
4.3 理解窗體的顯示次序
在addWindow()函數(shù)的前半部分中,WMS為窗體創(chuàng)建了用于描寫敘述窗體狀態(tài)的WindowState。接下來便會(huì)為新建的窗體確定顯示次序。
手機(jī)屏幕是以左上角為原點(diǎn),向右為X軸方向。向下為Y軸方向的一個(gè)二維空間。為了方便管理窗體的顯示次序,手機(jī)的屏幕被擴(kuò)展為了一個(gè)三維的空間。即多定義了一個(gè)Z軸,其方向?yàn)榇怪庇谄聊槐砻嬷赶蚱聊煌狻6鄠€(gè)窗體依照其前后順序排布在這個(gè)虛擬的Z軸上,因此窗體的顯示次序又被稱為Z序(Z order)。在這一節(jié)中將深入探討WMS確定窗體顯示次序的過程以及其影響因素。
4.3.1 主序、子序和窗體類型
看一下WindowState的構(gòu)造函數(shù):
[-->WindowState.java::WindowState.WindowState()]
WindowState(WindowManagerService service, Sessions, IWindow c, WindowToken token,
??????WindowState attachedWindow, int seq, WindowManager.LayoutParams a,
??? intviewVisibility, final DisplayContent displayContent) {
??? ......
??? // 為子窗體分配ZOrder
??? if((mAttrs.type >= FIRST_SUB_WINDOW &&
???????????mAttrs.type <= LAST_SUB_WINDOW)) {
??????? // 這里的mPolicy即是WindowManagerPolicy
??????? mBaseLayer= mPolicy.windowTypeToLayerLw(
???????????????attachedWindow.mAttrs.type)
???????????????* WindowManagerService.TYPE_LAYER_MULTIPLIER
???????????????+ WindowManagerService.TYPE_LAYER_OFFSET;
??????? mSubLayer= mPolicy.subWindowTypeToLayerLw(a.type);
???????......
??? } else {// 為普通窗體分配ZOrder
??????? mBaseLayer= mPolicy.windowTypeToLayerLw(a.type)
???????????????* WindowManagerService.TYPE_LAYER_MULTIPLIER
???????????????+ WindowManagerService.TYPE_LAYER_OFFSET;
??????? mSubLayer= 0;
???????......
??? }
??? ......
}
窗體的顯示次序由兩個(gè)成員字段描寫敘述:主序mBaseLayer和子序mSubLayer。
主序用于描寫敘述窗體及其子窗體在全部窗體中的顯示位置。
而子序則描寫敘述了一個(gè)子窗體在其兄弟窗體中的顯示位置。
·??主序越大,則窗體及其子窗體的顯示位置相對于其它窗體的位置越靠前。
·??子序越大,則子窗體相對于其兄弟窗體的位置越靠前。對于父窗體而言,其主序取決于其類型。其子序則保持為0。而子窗體的主序與其父窗體一樣。子序則取決于其類型。從上述代碼能夠看到,主序與子序的分配工作是由WindowManagerPolicy的兩個(gè)成員函數(shù)windowTypeToLayerLw()和subWindowTypeToLayerLw()完畢的。
表4-1與表4-2列出了全部可能的窗體類型以及其主序與子序的值。
表 4-1 窗體的主序
窗體類型 | 主序 | 窗體類型 | 主序 |
TYPE_UNIVERSE_BACKGROUND | 11000 | TYPE_WALLPAPER | 21000 |
TYPE_PHONE | 31000 | TYPE_SEARCH_BAR | 41000 |
TYPE_RECENTS_OVERLAY | 51000 | TYPE_SYSTEM_DIALOG | 51000 |
TYPE_TOAST | 61000 | TYPE_PRIORITY_PHONE | 71000 |
TYPE_DREAM | 81000 | TYPE_SYSTEM_ALERT | 91000 |
TYPE_INPUT_METHOD | 101000 | TYPE_INPUT_METHOD_DIALOG | 111000 |
TYPE_KEYGUARD | 121000 | TYPE_KEYGUARD_DIALOG | 131000 |
TYPE_STATUS_BAR_SUB_PANEL | 141000 | 應(yīng)用窗體與未知類型的窗體 | 21000 |
?
表 4-2 窗體的子序
子窗體類型 | 子序 |
TYPE_APPLICATION_PANEL | 1 |
TYPE_APPLICATION_ATTACHED_DIALOG | 1 |
TYPE_APPLICATION_MEDIA | -2 |
TYPE_APPLICATION_MEDIA_OVERLAY | -1 |
TYPE_APPLICATION_SUB_PANEL | 2 |
?
注意表4-2中的MEDIA和MEDIA_OVERLAY的子序?yàn)樨?fù)值,這表明它們的顯示次序位于其父窗體的后面。這兩個(gè)類型的子窗體是SurfaceView控件創(chuàng)建的。SurfaceView被實(shí)例化后。會(huì)向WMS加入一個(gè)類型為MEDIA的子窗體。它的父窗體就是承載SurfaceView控件的窗體。
這個(gè)子窗體的Surface將被用于視頻回放、相機(jī)預(yù)覽或游戲繪制。為了不讓這個(gè)子窗體覆蓋住全部的父窗體中承載的其它控件(如拍照button,播放器控制button等)。它必須位于父窗體之后。
從表4-1所描寫敘述的主序與窗體類型的相應(yīng)關(guān)系中能夠看出。WALLPAPER類型的窗體的主序竟和APPLICATION類型的窗體主序同樣。這看似有點(diǎn)不合常理。WALLPAPER不是應(yīng)該顯示在全部Acitivity之下嗎?事實(shí)上WALLPAPER類型的窗體是一個(gè)非常不安分的角色,須要在全部的APPLICATION窗體之間跳來跳去。
這是由于,有的Activity指定了android:windowShowWallpaper為true。則表示窗體要求將用戶當(dāng)前壁紙作為其背景。對于WMS來說,最簡單的辦法就是將WALLPAPER窗體放置到緊鄰擁有這個(gè)式樣的窗體的下方。在這樣的需求下。為了保證主序決定窗體順序的原則,WALLPAPER使用了與APPLICATION同樣的主序。另外。輸入法窗體也是一個(gè)非常特殊的情況。輸入法窗體會(huì)選擇輸入目標(biāo)窗體,并將自己放置于其上。在本章中不討論這兩個(gè)特殊的樣例,WALLPAPER的排序規(guī)則將在第7章中進(jìn)行介紹。而輸入法的排序則留給讀者自行研究。
盡管知道了窗體的主序與子序是怎樣分配的,只是我們?nèi)匀淮嬗幸蓡?#xff1a;假設(shè)有兩個(gè)同樣類型的窗體,那么它們的主序與子序豈不是全然同樣?怎樣確定它們的顯示順序呢?事實(shí)上。表4-1和表4-2中所描寫敘述的主序和子序僅僅是排序的依據(jù)之中的一個(gè),WMS須要依據(jù)當(dāng)前全部同類型窗體的數(shù)量為每一個(gè)窗體計(jì)算終于的現(xiàn)實(shí)次序。
4.3.2 通過主序與子序確定窗體的次序
回到WMS的addWindow()函數(shù)中。繼續(xù)往下看:
[-->WindowManagerService.java::WindowManagerService.addWindow()]
public int addWindow(Session session, IWindowclient, int seq,
???WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
??? RectoutContentInsets, InputChannel outInputChannel) {
??? ......
??? synchronized(mWindowMap){
??????? //在前面的代碼中,WMS驗(yàn)證了加入窗體的令牌的有效性。并為新窗體創(chuàng)建了新的WindowState對象
??????? // 新的WindowState對象在其構(gòu)造函數(shù)中依據(jù)窗體類型初始化了其主序mBaseLayer和mSubLayer
???????......
??????? // 接下來,將新的WindowState依照顯示次序插入到當(dāng)前DisplayContent的mWindows列表中
??????? // 為了代碼結(jié)構(gòu)的清晰。不考慮輸入法窗體和壁紙窗體的處理
??? if (type== TYPE_INPUT_METHOD) {
??????? ......
??????? }else if (type == TYPE_INPUT_METHOD_DIALOG) {
??????? }else {
???????????// 將新的WindowState按顯示次序插入到當(dāng)前DisplayContent的mWindows列表中
??????????? addWindowToListInOrderLocked(win,true);
??????????? if(type == TYPE_WALLPAPER) {
???????????????......
???????????}
??????? }
???????......
??????? // 依據(jù)窗體的排序結(jié)果,為DisplayContent的全部窗體分配終于的顯示次序
??????? assignLayersLocked(displayContent.getWindowList());
??????? ......
??? }
??? ......
??? returnres;
}
這里有兩個(gè)關(guān)鍵點(diǎn):
·??addWindowToListInOrderLocked()將新建的WindowState依照一定的順序插入到當(dāng)前DisplayContent的mWindows列表中。
在分析WMS的重要成員時(shí)提到過這個(gè)列表。它嚴(yán)格地依照顯示順序存儲(chǔ)了全部窗體的WindowState。
·??assignLayersLocked()將依據(jù)mWindows的存儲(chǔ)順序?qū)θ康腤indowState的主序和子序進(jìn)行調(diào)整。
接下來分別分析一下這兩個(gè)函數(shù)。
1.addWindowToListInOrderLocked()分析
addWindowToListInOrderLocked()的代碼非常長。只是其排序原則卻比較清晰。這里直接給出其處理原則。感興趣的讀者可依據(jù)這些原則自行深究相關(guān)代碼。
注意再次強(qiáng)調(diào)一下,mWindows列表是依照主序與子序的升序進(jìn)行排序的。所以顯示靠前的窗體放在列表靠后的位置,而顯示靠前的窗體,則位于列表的前面。也就是說,列表順序與顯示順序是相反的。
這點(diǎn)在閱讀代碼時(shí)要牢記。以免混淆。
在后面的敘述中,非特別強(qiáng)調(diào),所謂的前后都是指顯示順序而不是在列表的存儲(chǔ)順序。
子窗體的排序規(guī)則:子窗體的位置計(jì)算是相對父窗體的。并依據(jù)其子序進(jìn)行排序。由于父窗體的子序?yàn)?,所以子序?yàn)樨?fù)數(shù)的窗體會(huì)放置在父窗體的后面,而子序?yàn)檎龜?shù)的窗體會(huì)放置在父窗體的前面。
假設(shè)新窗體與現(xiàn)有窗體子序相等,則正數(shù)子序的新窗體位于現(xiàn)有窗體的前面,負(fù)數(shù)子序的新窗體位于現(xiàn)有窗體的后面。
非子窗體的排序則是依據(jù)主序進(jìn)行的,可是其規(guī)則較為復(fù)雜,分為應(yīng)用窗體和非應(yīng)用窗體兩種情況。之所以要差別處理應(yīng)用窗體是由于全部的應(yīng)用窗體的初始主序都是21000。而且應(yīng)用窗體的位置應(yīng)該與它所屬的應(yīng)用的其它窗體放在一起。比如應(yīng)用A顯示于應(yīng)用B的后方。當(dāng)應(yīng)用A由于某個(gè)動(dòng)作打開一個(gè)新的窗體時(shí),新窗體應(yīng)該位于應(yīng)用A其它窗體的前面,可是不得覆蓋應(yīng)用B的窗體。僅僅依據(jù)主序進(jìn)行排序是無法實(shí)現(xiàn)這個(gè)管理邏輯的,還須要依賴Activity的順序。在WindowToken一節(jié)的解說中。以前簡單分析了mAppTokens列表的性質(zhì)。它所保存的AppWindowToken的順序與AMS中ActivityRecord的順序時(shí)刻保持一致。因此,AppWindowToken在mAppTokens的順序就是Activity的順序。
非應(yīng)用窗體的排序規(guī)則:依照主序進(jìn)行排序,主序高者排在前面,當(dāng)現(xiàn)有窗體的主序與新窗體同樣時(shí),新窗體位于現(xiàn)有窗體的前面。
應(yīng)用窗體的排序規(guī)則:如上所述,同一個(gè)應(yīng)用的窗體的顯示位置必須相鄰。
假設(shè)當(dāng)前應(yīng)用已有窗體在顯示(當(dāng)前應(yīng)用的窗體存儲(chǔ)在其WindowState.appWindowToken.windows中),新窗體將插入到其所屬應(yīng)用其它窗體的前面。可是保證STARTING_WINDOW永遠(yuǎn)位于最前方,BASE_APPLICATION永遠(yuǎn)位于最后方。假設(shè)新窗體是當(dāng)前應(yīng)用的第一個(gè)窗體。則參照其它應(yīng)用的窗體順序,將新窗體插入到位于前面的最后一個(gè)應(yīng)用的最后一個(gè)窗體的后方,或者位于后面的第一個(gè)應(yīng)用的最前一個(gè)窗體的前方。
假設(shè)當(dāng)前沒有其它應(yīng)用的窗體能夠參照。則直接依據(jù)主序?qū)⑿麓绑w插入到列表中。
窗體排序的總結(jié)例如以下:
·??子窗體依據(jù)子序相對于其父窗體進(jìn)行排序。同樣子序的窗體,正子序則越新越靠前,負(fù)子序則越新越靠后。
·??應(yīng)用窗體參照本應(yīng)用其它窗體或相鄰應(yīng)用的窗體進(jìn)行排序。
假設(shè)沒有不論什么窗體能夠參照。則依據(jù)主序進(jìn)行排序。
·??非應(yīng)用窗體依據(jù)主序進(jìn)行排序。
經(jīng)過addWindowToListInOrderLocked()函數(shù)的處理之后。當(dāng)前DisplayContent的窗體列表被插入了一個(gè)新的窗體。
然后等待assignLayersLocked()的進(jìn)一步處理。
2.assignLayersLocked分析
assignLayersLocked()函數(shù)將依據(jù)每一個(gè)窗體的主序以及它們在窗體列表中的位置又一次計(jì)算終于的顯示次序mLayer。
[-->WindowManagerService.java::WindowManagerService.assignLayersLocked()]
privatefinal void assignLayersLocked(WindowList windows) {
??? int N = windows.size();
??? int curBaseLayer = 0;
??? // curLayer表示當(dāng)前分配到的Layer序號(hào)
??? int curLayer = 0;
??? int i;
?
??? // 遍歷列表中的全部的窗體,逐個(gè)分配顯示次序
??? for (i=0; i<N; i++) {
??????? final WindowState w = windows.get(i);
??????? final WindowStateAnimator winAnimator =w.mWinAnimator;
??????? boolean layerChanged = false;
??????? int oldLayer = w.mLayer;
??????? if (w.mBaseLayer == curBaseLayer ||w.mIsImWindow
??????????????? || (i > 0 &&w.mIsWallpaper)) {
??????????? // 為具有同樣主序的窗體在curLayer上添加一個(gè)偏移量,并將curLayer作為終于的顯示次序
??????????? curLayer +=WINDOW_LAYER_MULTIPLIER;
??????????? w.mLayer = curLayer;
??????? } else {
??????????? // 此窗體擁有不同的主序,直接將主序作為其顯示次序并更新curLayer
??????????? curBaseLayer = curLayer =w.mBaseLayer;
??????????? w.mLayer = curLayer;
??????? }
??????? // 假設(shè)現(xiàn)實(shí)次序發(fā)生了變化則進(jìn)行標(biāo)記
??????? if (w.mLayer != oldLayer) {
??????????? layerChanged = true;
??????????? anyLayerChanged = true;
??????? }
??????? ......
??? }
??? ......
??? // 向當(dāng)前DisplayContent的監(jiān)聽者通知顯示次序的更新
??? if (anyLayerChanged) {
??????? scheduleNotifyWindowLayersChangedIfNeededLocked(
??????? getDefaultDisplayContentLocked());
??? }
}
assignLayersLocked()的工作原理比較繞。簡單來說,假設(shè)某個(gè)窗體在整個(gè)列表中擁有唯一的主序。則該主序就是其終于的顯示次序。假設(shè)若干個(gè)窗體擁有同樣的主序(注意經(jīng)過addWindowToListInOrderLocked()函數(shù)的處理后,擁有同樣主序的窗體都是相鄰的),則第i個(gè)同樣主序的窗體的顯示次序?yàn)樵谥餍虻幕A(chǔ)上添加i * WINDOW_LAYER_MULTIPLIER的偏移。
經(jīng)過assignLayersLocked()之后,一個(gè)擁有9個(gè)窗體的系統(tǒng)的現(xiàn)實(shí)次序的信息如表4-3所看到的。
表4- 3 窗體終于的顯示次序信息
? | 窗體1 | 窗體2 | 窗體3 | 窗體4 | 窗體5 | 窗體6 | 窗體7 | 窗體8 | 窗體9 |
主序mBaseLayer | 11000 | 11000 | 21000 | 21000 | 21000 | 21000 | 71000 | 71000 | 101000 |
子序mSubLayer | 0 | 0 | 0 | -1 | 0 | 0 | 0 | 0 | 0 |
顯示次序mLayer | 11000 | 11005 | 21000 | 21005 | 21010 | 21015 | 71000 | 71005 | 101000 |
?
在確定了終于的顯示次序mLayer后。又計(jì)算了WindowStateAnimator還有一個(gè)屬性:mAnimLayer。
例如以下所看到的:
[-->WindowManagerService.java::assignLayersLocked()]
??? finalWindowStateAnimator winAnimator = w.mWinAnimator;
??? ......
? ??if (w.mTargetAppToken != null) {
??????? // 輸入目標(biāo)為Activity的輸入法窗體,其mTargetAppToken是其輸入目標(biāo)所屬的AppToken
???????winAnimator.mAnimLayer =
???????????????w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment;
??? } elseif (w.mAppToken != null) {
??????? // 屬于一個(gè)Activity的窗體
???????winAnimator.mAnimLayer =
???????????????w.mLayer + w.mAppToken.mAppAnimator.animLayerAdjustment;
??? } else {
???????winAnimator.mAnimLayer = w.mLayer;
??? }
?? ?......
對于絕大多數(shù)窗體而言,其相應(yīng)的WindowStateAnimator的mAnimLayer就是mLayer。而當(dāng)窗體附屬為一個(gè)Activity時(shí),mAnimLayer會(huì)加入一個(gè)來自AppWindowAnimator的矯正:animLayerAdjustment。
WindowStateAnimator和AppWindowAnimator是動(dòng)畫系統(tǒng)中的兩員大將,它們負(fù)責(zé)渲染窗體動(dòng)畫以及終于的Surface顯示次序的改動(dòng)。回想一下4.1.2中的WMS的組成結(jié)構(gòu)圖,WindowState屬于窗體管理體系的類。因此其所保存的mLayer的意義偏向于窗體管理。
WindowStateAnimator/AppWindowAnimator則是動(dòng)畫體系的類,其mAnimLayer的意義偏向于動(dòng)畫。而且由于動(dòng)畫系統(tǒng)維護(hù)著窗體的Surface,因此mAnimLayer是Surface的實(shí)際顯示次序。
在沒有動(dòng)畫的情況下,mAnimLayer與mLayer是相等的。而當(dāng)窗體附屬為一個(gè)Activity時(shí),則會(huì)依據(jù)AppTokenAnimator的須要適當(dāng)?shù)靥砑右粋€(gè)矯正值。這個(gè)矯正值來自AppTokenAnimator所使用的Animation。當(dāng)Animation要求動(dòng)畫對象的ZOrder必須位于其它對象之上時(shí)(Animation.getZAdjustment()的返回值為Animation.ZORDER_TOP),這個(gè)矯正是一個(gè)正數(shù)WindowManagerService.TYPE_LAYER_OFFSET(1000),這個(gè)矯正值非常大,于是窗體在動(dòng)畫過程中會(huì)顯示在其它同主序的窗體之上。相反。假設(shè)要求ZOrder必須位于其它對象之下時(shí),矯正為-WindowManagerService.TYPE_LAYER_OFFSET(-1000)。于是窗體會(huì)顯示在其它同主序的窗體之下。在動(dòng)畫完結(jié)后,mAnimLayer會(huì)被又一次賦值為WindowState.mLayer。使得窗體回到其應(yīng)有的位置。
動(dòng)畫系統(tǒng)的工作原理將在4.5節(jié)具體探討。
注意矯正值為常數(shù)1000,也就出現(xiàn)一個(gè)隱藏的bug:當(dāng)同主序的窗體的數(shù)量大于200時(shí)。APPLICATION窗體的mLayer值可能超過22000。此時(shí),在對于mLayer值為21000的窗體應(yīng)用矯正后,仍然無法保證動(dòng)畫窗體位于同主序的窗體之上。只是超過200個(gè)應(yīng)用窗體的情況非常少見,而且僅在動(dòng)畫過程中才會(huì)出現(xiàn)bug,所以google貌似也懶得解決問題。
4.3.3 更新顯示次序到Surface
再回到WMS的addWindow()函數(shù)中。發(fā)現(xiàn)再?zèng)]有可能和顯示次序相關(guān)的代碼了。mAnimLayer是怎樣發(fā)揮自己的作用呢?不要著急,事實(shí)上。新建的窗體眼下尚無Surface。
回想一下SimpleWindow樣例,在運(yùn)行session.relayout()后,WMS才為新窗體分配了一塊Surface。也就是說,僅僅有運(yùn)行relayout()之后才會(huì)為新窗體的Surface設(shè)置新的顯示次序。
為了不中斷對顯示次序的調(diào)查進(jìn)展。就直接開門見山地告訴大家,設(shè)置顯示次序到Surface的代碼位于WindowStateAnimator. prepareSurfaceLocked()函數(shù)中,是通過Surface.setLayer()完畢的。
在4.5節(jié)會(huì)深入為大家揭開WMS動(dòng)畫子系統(tǒng)的面紗。
4.3.4 關(guān)于顯示次序的小結(jié)
這一節(jié)討論了窗體類型對窗體顯示次序的影響。
窗體依據(jù)自己的類型得出其主序及子序,然后addWindowToListInOrderLocked()依據(jù)主序、子序以及其所屬的Activity的順序。依照升序排列在DisplayContent的mWindows列表中。
然后assignLayersLocked()為mWindows中的全部窗體分配終于的顯示次序。之后,WMS的動(dòng)畫系統(tǒng)將終于的顯示次序通過Surface.setLayer()設(shè)置進(jìn)SurfaceFlinger。
[1]關(guān)于Choreographer。請參考鄧凡平的博客《Android Project Butter分析》(http://blog.csdn.net/innost/article/details/8272867)。
[2]讀者可閱讀《深入理解Android 卷I》第4章“深入理解Zygote”來了解和zygote相關(guān)的知識(shí)
[3]關(guān)于Wi-Fi Display的具體信息,請讀者參考http://blog.csdn.net/innost/article/details/8474683的介紹。
轉(zhuǎn)載于:https://www.cnblogs.com/wzjhoutai/p/6873790.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的《深入理解Android 卷III》第四章 深入理解WindowManagerService的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: System.arraycopy用法
- 下一篇: Android反编译 -- 错误代码还原