本文作者
作者:一隻修仙的猿
連結:
https://blog.csdn.net/weixin_43766753/article/details/108350589
本文由作者授權釋出。
1 前言
你好! 我是一隻修仙的猿,歡迎閱讀我的文章。
Window,讀者可能更多的認識是windows系統的視窗。在windows系統上,我們可以多個視窗同時運作,每個視窗代表着一個應用程式。但在安卓上貌似并沒有這個東西,但讀者可以馬上想到,不是有小視窗模式嗎,像米UI最新的系統,不就是可以随意建立一個小視窗,然後兩個應用同時操作?
是的,那是屬于android中,window的一種表現方式。但是手機螢幕終究不能和電腦相比,因為螢幕太小了,小到隻能操作一款應用,多個視窗就顯得非常不習慣,是以Android上關于視窗方面的知識讀者可能接觸不多。
那window的意思就隻是小米系統中那種小視窗嗎?
當然不是。Android架構層意義上的window和我們認識的window其實是有點不一樣的。我們日常最直覺的,每個應用界面,都有一個應用級的window。再例如popupWindow、Toast、dialog、menu都是需要通過建立window來實作。
是以其實window我們一直都見到,隻是不知道那就是window。
了解window的機制原理,可以更好地了解window,進而更好地了解android是怎麼管理螢幕上的view。這樣,當我們需要使用dialog或者popupWindow的時候,可以懂得他背後究竟做了什麼,才能夠更好的運用dialog、popupWindow等。
當然,到此如果你有很多的疑問,甚至質疑我的理論,那就希望你可以閱讀完這一篇文章。
我會從window是什麼,有什麼用,内部機制是什麼,各種元件是如何建立window等等方面來闡述Android中的window。文章内容非常多,讀者可自選章節閱讀。
強烈建議閱讀【window的概念】【window與PhoneWindow的關系】【從Android架構看window】這三章,會對window的本質有更清晰的認識。
本文大綱:
筆者才疏學淺,有不同觀點歡迎評論區或私信讨論。如需轉載請留言告知。另外歡迎閱讀筆者的個人部落格一隻修仙的猿的個人部落格,更精美的UI,擁有更好的閱讀體驗。
https://qwerhuan.gitee.io/archives/
2 window的概念
先假設如果沒有window,會發生什麼:
我們看到的界面ui是view,如我們的應用布局,更簡單是一個button。假如螢幕上現在有一個Button,如圖1,現在往螢幕中間添加一個TextView,那麼最終的結果是圖2,還是圖3:
在上圖的圖2中,如果我要實作點選textView執行他的監聽事件邏輯,點選不是textView的區域讓textView消失,需要怎麼實作呢?
讀者可能會說,我們可以在Activity中添加這部分的邏輯,那如果我們需要讓一個懸浮窗在所有界面顯示呢,如上文我講到的小米懸浮窗,兩個不用應用的view,怎麼确定他們的顯示次序?又例如我們需要彈出一個dialog來提示使用者,怎麼樣可以讓dialog永遠處于最頂層呢,包括顯示dialog期間應用彈出的如popupWindow必須顯示在dialog的低下,但toast又必須顯示在dialog上面。
很明顯,我們的螢幕可以允許多個應用同時顯示非常多的view,他們的顯示次序或者說顯示高度是不一樣的,如果沒有一個統一的管理者,那麼每一家應用都想要顯示在最頂層,那麼螢幕上的view會非常亂,這時候急需一個管理者來統一管理view的顯示以及接受觸摸事件的邏輯,這個管理者,就是整個Android的window機制。
window機制就是為了解決螢幕上的view的顯示邏輯問題。
那什麼是window,在Android的window機制中,每個view樹都可以看成一個window。
為什麼不是每個view呢?因為view樹中每個view的顯示次序是固定的,例如我們的Activity布局,每一個控件的顯示都是已經安排好的,對于window機制來說,屬于“不可再分割的view”。
什麼是view樹?例如你在布局中給Activity設定了一個布局xml,那麼最頂層的布局如LinearLayout就是view樹的根,他包含的所有view就都是該view樹的節點,是以這個view樹就對應一個window。
舉幾個具體的例子:
- 我們在添加dialog的時候,需要給他設定view,那麼這個view他是不屬于antivity的布局内的,是通過WindowManager添加到螢幕上的,不屬于activity的view樹内,是以這個dialog是一個獨立的view樹,是以他是一個window。
- popupWindow他也對應一個window,因為它也是通過windowManager添加上去的,不屬于Activity的view樹。
- 當我們使用使用windowManager在螢幕上添加的任何view都不屬于Activity的布局view樹,即使是隻添加一個button。
view樹(後面使用view代稱,後面我說的view都是指view樹)是window機制的操作機關,每一個view對應一個window,view是window的存在形式,window是view的載體,我們平時看到的應用界面、dialog、popupWindow以及上面描述的懸浮窗,都是window的表現形式。注意,我們看到的不是window,而是view。window是view的管理者,同時也是view的載體。
他是一個抽象的概念,本身并不存在,view是window的表現形式。這裡的不存在,指的是我們在螢幕上是看不到window的,他不像windows系統,如下圖:
有一個很明顯的标志:看,我就是window。
但在Android中我們是無法感覺的,我們隻能看到view無法看到window,window是控制view需要怎麼顯示的管理者。每個成功的男人背後都有一個女人,每個view背後都有一個window。
window本身并不存在,他隻是一個概念。
舉個栗子:如班集體,就是一個概念,他的存在形式是這整個班的學生,當學生不存在那麼這個班集體也就不存在。但是他的好處是得到了一個新的概念,我們可以以班為機關來安排活動。
因他不存在,是以也很難從源碼中找到他的痕迹,window機制的操作機關都是view,如果要說他在源碼中的存在形式,筆者目前的認知就是在WindowManagerService中每一個view對應一個windowStatus。
WindowManagerService是什麼如果沒了解過可以先忽略後面會講到。讀者可以慢慢思考一下這個抽象的概念,後面會慢慢深入講源碼幫助了解。
- view是window的存在形式,window是view的載體
- window是view的管理者,同時也是view的載體。他是一個抽象的概念,本身并不存在,view是window的表現形式
最後做一個總結:
- window機制是為了解決螢幕上view的顯示混亂問題,讓所有view都按照秩序來顯示,滿足我們的開發需求
- window是window機制中的操作機關,每個window對應一個view。
- window本身并不存在,他是一個抽象的概念,他的存在形式是view,每個view的目前狀态儲存在WindowManagerService中的windowStatus
思考:Android中不是有一個抽象類叫做window還有一個PhoneWindow實作類嗎,他們不就是window的存在形式,為什麼說window是抽象不存在的?讀者可自行思考,後面會講到。
3 Window的相關屬性
在了解window的操作流程之前,先補充一下window的相關屬性。
window的type屬性
前面我們講到window機制解決的一個問題就是view的顯示次序問題,這個屬性就決定了window的顯示次序。
window是有分類的,不同類别的顯示高度範圍不同,例如我把1-1000m高度稱為低空,1001-2000m高度稱為中空,2000以上稱為高空。window也是一樣按照高度範圍進行分類,他也有一個變量Z-Order,決定了window的高度。window一共可分為三類:
- 應用程式視窗:應用程式視窗一般位于最底層,Z-Order在1-99
- 子視窗:子視窗一般是顯示在應用視窗之上,Z-Order在1000-1999
- 系統級視窗:系統級視窗一般位于最頂層,不會被其他的window遮住,如Toast,Z-Order在2000-2999。如果要彈出自定義系統級視窗需要動态申請權限。
Z-Order越大,window越靠近使用者,也就顯示越高,高度高的window會覆寫高度低的window。
window的type屬性就是Z-Order的值,我們可以給window的type屬性指派來決定window的高度。系統為我們三類window都預設了靜态常量,如下(以下常用參數介紹轉自參考文獻第一篇文章):
應用級window
// 應用程式 Window 的開始值
子window
// 子 Window 類型的開始值
系統級window
// 系統Window類型的開始值
Window的flags參數
flag标志控制window的東西比較多,很多資料的描述是“控制window的顯示”,但我覺得不夠準确。
flag控制的範圍包括了:各種情景下的顯示邏輯(鎖屏,遊戲等)還有觸控事件的處理邏輯。控制顯示确實是他的很大部分功能,但是并不是全部。下面看一下一些常用的flag,就知道flag的功能了(以下常用參數介紹轉自參考文獻第一篇文章):
// 當 Window 可見時允許鎖屏
window的其他屬性
上面的三個屬性是window比較重要也是比較複雜 的三個,除此之外還有幾個日常經常使用的屬性:
- x與y屬性:指定window的位置
- alpha:window的透明度
- gravity:window在螢幕中的位置,使用的是Gravity類的常量
- format:window的像素點格式,值定義在PixelFormat中
如何給window屬性指派
window屬性的常量值大部分存儲在WindowManager.LayoutParams類中,我們可以通過這個類來獲得這些常量。當然還有Gravity類和PixelFormat類等。
一般情況下我們會通過以下方式來往螢幕中添加一個window:
// 在Activity中調用
我們可以直接給WindowManager.LayoutParams對象設定屬性。
第二種指派方法是直接給window指派,如
除此之外,window的solfInputMode屬性比較特殊,他可以直接在AndroidManifest中指定,如下:
<activity android:windowSoftInputMode="adjustNothing" />
最後總結一下:
- window的重要屬性有type、flags、solfInputMode、gravity等
- 我們可以通過不同的方式給window屬性指派
- 沒必要去全部記下來,等遇到需求再去尋找對應的常量即可
4 window機制的關鍵類
從這裡開始就要開始講window的内部機制,首先我們需要了解一下window裡面的那些關鍵的類和接口,當然首先我們要了解一下window的内部機制中有哪些角色:
window相關
window的實作類隻有一個:PhoneWindow,他繼承自Window抽象類。後面我們經常見到他。
WindowManager相關
顧名思義,windowManager就是window管理類。這一部分的關鍵類有windowManager,viewManager,windowManagerImpl,windowManagerGlobal。
- windowManager是一個接口,繼承自viewManager。
- viewManager中包含了我們非常熟悉的三個接口:addView,removeView,updateView。
- windowManagerImpl和PhoneWindow是成對出現的,前者負責管理後者。WindowManagerImpl是windowManager的實作類,但是他本身并沒有真正實作邏輯,而是交給了WindowManagerGlobal。
- WindowManagerGlobal是全局單例,windowManagerImpl内部使用橋接模式,他是windowManager接口邏輯的真正實作。
view相關
這裡有個很關鍵的類:ViewRootImpl。每個view樹都會有一個。當我使用windowManager的addView方法時,就會建立一個ViewRootImpl。ViewRootImpl的作用很關鍵:
- 負責連接配接view和window的橋梁事務
- 負責和WindowManagerService的聯系
- 負責管理和繪制view樹
- 輸入事件的中轉站
每個window都會有一個ViewRootImpl,viewRootImpl是負責繪制這個view樹和window與view的橋梁,每個window都會有一個ViewRootImpl。如果這裡對他的這些功能不太了解,沒事,隻要記住這個類就好了,後面會講到。
WindowManagerService
這個是window的真正管理者,類似于AMS(ActivityManagerService)管理四大元件。所有的window建立最終都要經過windowManagerService。整個Android的window機制中,WMS絕對是核心,他決定了螢幕所有的window該如何顯示如何處理點選事件。
小結
好了,看了上面那麼多的類,可能有點感覺眼花缭亂,畫個圖幫助了解一下:(注意裡面綠色的window不是關鍵類,隻是為了友善了解畫進去了)
PhoneWindow是視窗類,繼承自抽象類Window,也是唯一子類。WindowManager是Window管理接口,繼承自ViewManager,他的唯一實作類是WindowManagerImpl。WindowManagerImpl并沒有真正實作windowManager接口邏輯,而是把邏輯轉給了WindowManagerGlobal,WindowManagerGlobal是全局單例。Window和View的聯系通過ViewRootImpl橋梁,同時ViewRootImpl還負責管理view樹、繪制view樹、和WMS通信。WMS即WindowManagerService,是Window的真正管理類。
了解完上面的一些關鍵類,可能讀者對于他們的功能還是一頭霧水,沒關系,下面我将通過源碼來講解,很快你就可以了解了。到時候可以再次回頭來看一下,就更加融會貫通了。
5 Window的添加過程
通過了解源碼之後,可以對之前的理論了解更加的透徹,同時也是對上一小節的講解。window的添加過程,指的是我們通過WindowManagerImpl的addView方法來添加window的過程。window的存在形式是view也可以從這個方法名字看出來,添加window即為添加view。
想要添加一個window,我們知道首先得有view和WindowManager.LayoutParams對象,才能去建立一個window,這是我們常見的代碼:
new Button(
然後接下來我們進入addView方法中看看。我們知道這個windowManager的實作類是WindowManagerImpl,上面講過,進入他的addView方法看一看:
可以發現他把邏輯直接交給mGlobal去處理了。這個mGlobal是啥?
有印象的讀者就會知道他是WindowManagerGlobal,是一個全局單例,是以這裡可以看到WindowManagerGlobal确實是WindowManager接口的具體邏輯實作,這裡運用的是橋接模式。那我們進WindowManagerGlobal的方法看一下:
代碼有點長,盲猜讀者你并沒有看(嘻嘻),一步步看不複雜的。
- 首先對參數的合法性進行檢查
- 然後判斷該視窗是不是子視窗,如果是的話需要對視窗進行調整,這個好了解,子視窗要跟随父視窗的特性。
- 接着建立viewRootImpl對象,并把view、viewRootImpl、params三個對象添加到三個list中進行儲存
- 最後通過viewRootImpl來進行添加
補充一點關于WindowManagerGlobal中的三個list,他們分别是:每一個window所對應的這三個對象都會儲存在這裡,之後對window的一些操作就可以直接來這裡取對象了。當window被删除的時候,這些對象也會被從list中移除。private final ArrayList mViews = new ArrayList(); private final ArrayList mRoots = new ArrayList(); private final ArrayList mParams = new ArrayList();
可以看到添加的window的邏輯就交給ViewRootImpl了。viewRootImpl是window和view之間的橋梁,viewRootImpl可以處理兩邊的對象,然後聯結起來。下面看一下viewRootImpl怎麼處理:
viewRootImpl的邏輯很多,重要的就是調用了mWindowSession的方法調用了WMS的方法。這個mWindowSession很重要重點講一下。
mWindowSession是一個IWindowSession對象,看到這個命名很快地可以像到這裡用了AIDL跨程序通信。IWindowSession是一個IBinder接口,他的具體實作類在WindowManagerService,本地的mWindowSession隻是一個Binder對象,通過這個mWindowSession就可以直接調用WMS的方法進行跨程序通信。
那這個mWindowSession是從哪裡來的呢?我們到viewRootImpl的構造器方法中看一下:
可以看到這個session對象是來自WindowManagerGlobal。再深入看一下:
這熟悉的代碼格式,可以看出來這個session是一個單例,也就是整個應用的所有viewRootImpl的windowSession都是同一個,也就是一個應用隻有一個windowSession。
對于wms而言,他是服務于多個應用的,如果說每個viewRootImpl整一個session,那他的任務就太重了。
WMS的對象機關是應用,他在内部給每個應用session配置設定了一些資料結構如list,用于儲存每個應用的window以及對應的viewRootImpl。當需要操作view的時候,通過session直接找到viewRootImpl就可以操作了。
後面的邏輯就交給WMS去處理了,WMS就會建立window,然後結合參數計算window的高度等等,最後使用viewRootImpl進行繪制。這後面的代碼邏輯就不講了,這是深入到WMS的内容,再講進去就太複雜了(筆者也還沒讀懂WMS)。讀源碼的目的是了解整個系統的本質與工作流程,對系統整體的感覺,而不用太深入代碼細節,Android系統那麼多的代碼,如果深入進去會出不來的,是以點到為止就好了。
我們知道windowManager接口是繼承viewManager接口的,viewManager還有另外兩個接口:removeView、updateView。這裡就不講了,有興趣的讀者可以自己去閱讀源碼。講添加流程主要是為了了解window系統的運作,對内部的流程感覺,以便于更好的了解window。
最後老樣子,做個總結:
window的添加過程是通過PhoneWindow對應的WindowManagerImpl來添加window,内部會調用WindowManagerGlobal來實作。WindowManagerGlobal會使用viewRootImpl來進行跨程序通信讓WMS執行建立window的業務。
每個應用都有一個windowSession,用于負責和WMS的通信,如ApplicationThread與AMS的通信。
window與PhoneWindow的關系
解釋一下标題,window是指window機制中window這個概念,而PhoneWindow是指PhoneWindow這個類。後面我在講的時候,如果是指類,我會在後面加個‘類’字。如window是指window概念,window類是指window這個抽象類。讀者不要混淆。
還記得我在講window的概念的時候留了一個思考嗎?
思考:Android中不是有一個抽象類叫做window還有一個PhoneWindow實作類嗎,他們不就是window的存在形式,為什麼說window是抽象不存在的
這裡我再抛出幾個問題:
- 有一些資料認為PhoneWindow就是window,是view容器,負責管理容器内的view,windowManagerImpl可以往裡面添加view,如上面我們講過的addView方法。但是,同時它又說每個window對應一個viewRootImpl,但卻沒解釋為什麼每次addView都會建立一個viewRootImpl,前後發送沖突。
- 有一些資料也是認為PhoneWindow是window,但是他說addView方法不是添加view而是添加window,同時拿這個方法的名字作為論據證明view就是window,但是他沒解釋為什麼在使用addView方法建立window的過程卻沒有建立PhoneWindow對象。
我們一步步來看。我們首先來看一下源碼中對于window抽象類的注釋:
Abstract base
大概意思就是:這個類是頂級視窗的抽象基類,頂級視窗必須繼承他,他負責視窗的外觀如背景、标題、預設按鍵處理等。這個類的執行個體被添加到windowManager中,讓windowManager對他進行管理。
PhoneWindow是一個top-level window(頂級視窗),他被添加到頂級視窗管理器的頂層視圖,其他的window,都需要添加到這個頂層視圖中,是以更準确的來說,PhoneWindow并不是view容器,而是window容器。
每一個PhoneWindow都有一個WindowManagerImpl對象,他們是成對出現的。當我們調用windowManagerImpl來添加window的時候,其實就是往PhoneWindow中添加window。WindowManagerImpl是利用應用服務中的windowManagerImpl來進行建立的,看似每個PhoneWindow都有一個WindowManagerImpl,但是他的内部其實是同個WindowManagerImpl(後面在講Activity的window建立流程會講到)。
PhoneWindow不是Android的window機制中的window概念,他隻是在應用端的一個window容器。
那PhoneWindow的存在意義是什麼?
每一個view樹,無論你點選哪個控件,都可以追溯到根部,有一個統一管理事件的中心:viewRootImpl,他負責把事件統一發送到合理的地方。
但是如果是不同的view樹,就無法追溯到統一根源,無法統一處理事件。
Activity可能有很多的view樹,例如popupWindow,menu,那這個時候怎麼讓他們有一個統一的處理事件根源呢?沒錯就是PhoneWindow。
所有通過PhoneWindow對應的WindowManagerImpl添加的window都要接受PhoneWindow的管理,我們可以通過WindowManagerImpl往PhoneWindow中添加子window。
這樣所有的點選事件等就可以直接交給PhoneWindow,然後PhoneWindow再把事件交給Activity統一處理。這樣,Activity就可以把屬于自己應用的window當成一棵“view樹”,可以管理到所有屬于他的window。
如下圖:
同時,Activity可以給PhoneWindow設定屬性,他會按照一定的邏輯給PhoneWindow中的window設定屬性。
還記得谷歌的官方給window類注釋的最後一句話嗎:它提供标準的UI政策,如背景、标題區域、預設鍵處理等。。
給PhoneWindow設定的屬性很多,最常見的有背景、軟鍵盤模式、狀态欄區域的延伸等等,他的内部會配合DecorView來實作我們日常的一些開發需求(後面會講到DecorView),封裝成API供我們使用。PhoneWindow的層級結構如下:
PhoneWindow利用DecorView,實作給contentView添加背景,設定标題區域等等功能。而真正的window是沒有背景、标題欄等這一說的。真正的window隻是一個抽象的概念,他本身并不存在,PhoneWindow是利用DecorView才實作了這些功能。讀者需要區分好這兩者的關系。
這裡我就我不深入講PhoneWindow了,我們側重點是講PhoneWindow與window的關系,有興趣的讀者可以自行研究一下。
總結一下:
- PhoneWindow本身不是window,他是一個window容器,統一管理其中的window
- windowManagerImpl并不是管理window的類,而是管理PhoneWindow的類
- PhoneWindow的作用是協作Activity等需要管理多個window的工具類,讓這些window擁有統一的事件處理源,
- PhoneWindow可以配合DecorView可以給其中的window按照一定的邏輯提供标準的UI政策
6 常見元件的window建立流程
上面講的是通過windowManagerImpl建立window的過程,我們通過前面的講解了解到,WindowManagerImpl是管理PhoneWindow的,他們是同時出現的。因而有兩種建立window的方式:
- 已經存在PhoneWindow,直接通過WindowManagerImpl建立window
- PhoneWindow尚未存在,先建立PhoneWindow,再利用windowManagerImpl來建立window
當我們在Activity中使用getWindowManager方法擷取到的就是應用的PhoneWindow對應的WindowManagerImpl。下面來講一下不同的元件是如何建立window的。
Activity
如果有閱讀過Activity的啟動流程的讀者,會知道Activity的啟動最後來到了ActivityThread的handleLaunchActivity這個方法。
關于Activity的啟動流程,我寫過一篇文章,有興趣的讀者可以點選下方連結前往:
Activity啟動流程詳解(基于api28)https://blog.csdn.net/weixin_43766753/article/details/107746968
至于為什麼是這個方法這裡就不講了,有興趣的讀者可以去看上面的文章。我們直接來看這個方法的代碼:
handleLaunchActivity的代碼中首先對WindowManagerGlobal進行初始化,然後調用了performLaunchActivity方法。代碼很多,這裡隻截取了重要部分。
首先會建立Application對象,然後再調用Activity的attach方法,把window作為參數傳進去,最後回調activity的onCreate方法。是以這裡最有可能建立window的方法就是Activity的attach方法了。我們進去看一下:
同樣隻截取了重要代碼,attach方法參數非常多,我隻留下了window相關的參數。在這方法裡首先利用傳進來的window建立了PhoneWindow。
Activity實作window的callBack接口,可以把自己設定給window當觀察者。當window發生變化的時候可以通知activity。然後再建立WindowManager和PhoneWindow綁定在一起,這樣我們就可以通過windowManager操作PhoneWindow了。(這裡不是setWindowManager嗎,windowManager是什麼時候建立的?)我們進去setWindowManager方法看一下:
這個方法裡首先會擷取到應用服務的WindowManager(實作類也是WindowManagerImpl),然後通過這個應用服務的WindowManager建立了新的windowManager。
從這裡可以看到是利用系統服務的windowManager來建立新的windowManagerImpl,因而這個應用所有的WindowManagerImpl都是同個核心windowManager,而建立出來的僅僅是包了個殼。
這樣PhoneWindow和WindowManagerImpl就綁定在一起了。Activity可以通過WindowManagerImpl來操作PhoneWindow。
到這裡Activity的PhoneWindow和WindowManagerImpl對象就建立完成了,接下來是如何把Activity的布局檔案設定給PhoneWindow。在上面我講到調用Activity的attach方法之後,會回調Activity的onCreate方法,在onCreate方法我們會調用setContentView來設定布局,如下:
這裡的getWindow就是擷取到我們上面建立的PhoneWindow對象。我們繼續看下去:
// 注意他有多個重載的方法,要選擇參數對應的方法
同樣我們隻看重點代碼:
- 首先看decorView建立了沒有,沒有的話建立DecorView
- 把布局加載到DecorView中
- 回調Activity的callBack方法
這裡補充一下什麼是DecorView。DecorView是在PhoneWindow中預設好的一個布局,這個布局長這樣:
他是一個垂直排列的布局,上面是ActionBar,下面是ContentView,他是一個FrameLayout。我們的Activity布局就加載到ContentView裡進行顯示。是以Decorview是Activity布局最頂層的viewGroup。
然後我們看一下怎麼初始化DercorView的:
installDecor方法中主要是建立一個DecorView對象,然後加載預設好的布局對DecorView進行初始化,(預設好的布局就是上面講述的布局)并擷取到這個預設布局的ContentView。
好了然後我們再回到window的setContentView方法中,初始化了DecorView之後,把Activity布局加載到DecorView的ContentView中如下代碼:
// 注意他有多個重載的方法,要選擇參數對應的方法
是以可以看到Activitiy的布局确實是添加到DecorView的ContentView中,這也是為什麼onCreate中使用的是setContentView而不是setView。最後會回調Activity的方法告訴Activity,DecorView已經建立并初始化完成了。
到這裡DecorView建立完成了,但還缺少了最重要的一步:把DecorView作為window添加到螢幕上。從前面的介紹我們知道添加window需要用到WindowManagerImpl的addView方法。這一步是在ActivityThread的handleResumeActivity方法中被執行:
這一步方法有兩個重點:回調onResume方法,把decorView添加到螢幕上。我們看一下makeVisible方法做了什麼:
是不是非常熟悉?直接調用WindowManagerImpl的addView方法來吧decorView添加到螢幕上,至此,我們的Activity界面就會顯示在螢幕上了。
好了,這部分很長,最後來總結一下:
- 從Activity的啟動流程可以得到Activity建立Window的過程
- 建立PhoneWindow -> 建立WindowManager -> 建立decorView -> 利用windowManager把DecorView顯示到螢幕上
- 回調onResume方法的時候,DecorView還沒有被添加到螢幕,是以當onResume被回調,指的是螢幕即将到顯示,而不是已經顯示
PopupWindow
popupWindow日常使用的也比較多,最常見的需求是彈一個菜單出來等。popupWindow也是利用windowManager來往螢幕上添加window,但,popupWindow是依附于activity而存在的,當Activity未運作時,是無法彈出popupWindow的,通過源碼可以知道,當調用onResume方法的時候,其實後續還有很多事情在做,這個時候Activity也是尚未完全啟動,是以popupWindow不能在onCreate、onStart、onResume方法中彈出。
彈出popupWindow的過程分為兩個:建立view;通過windowManager添加window。首先看到PopupWindow的構造方法:
他有多個重載方法,但最終都會調用到這個有四個參數的方法。主要是前面的得到context和根據context獲得WindowManager。
然後我們看到他的顯示方法。顯示方法有兩個:showAtLocation和showAsDropDown。主要是處理顯示的位置不同,其他都是相似的。我們看到第一個方法:
邏輯很簡單,父view的根布局存儲了起來,然後調用另外的重載方法:
這個方法的邏輯主要有:
- 判斷contentView是否為空或者是否呀進行顯示
- 做一些準備工作
- 行popupWindow顯示工作
這裡我們看一下他的準備工作做了什麼:
接下來再看他的顯示工作:
到這裡popupWindow就會被添加到螢幕上了。
最後總結一下:
- 根據參數建構popupDecorView
- 把popupDecorView添加到螢幕上
Dialog
dialog和popupWindow最大的差別是:dialog會建立一個PhoneWindow。前面我們了解到,PhoneWindow的作用就是為了統一管理所有屬于自身的window,是以dialog的設計就是一個和Activity獨立開來的window,他會直接顯示在Activity所有view的上面,這也是dialog和popupWindow的本質差別。
dialog的建立過程和popupWindow是大同小異的:建立PhoneWindow,初始化DecorView,添加DecorView。我這裡就簡單講解一下。首先看到他的構造方法:
@NonNull Context context,
這裡和前面的Activity建立過程非常像。接下來看到他的show方法:
幾乎和Activity的添加流程一模一樣,這裡我也不多講解了。
總結一下:
- dialog和popupWindow不同,dialog建立了新的PhoneWindow,他是獨立在Activity之外的window
- popupWindow需要依賴于activity,這是兩者的本質差別
- dialog會直接顯示在Activity上面,而popUpWindow則不會,後來的新添加view會覆寫在popupWindow上
- dialog的建立流程和activity幾乎是一樣的
7 從Android架構角度看Window
前面我們介紹過關于PhoneWindow和window之間的關系,了解到PhoneWindow其實不是Window,隻是一個window容器。不知讀者有沒想過一個問題,為什麼谷歌要建一個不是window但卻名字是window的類?是故意要迷惑我們嗎?要了解這個問題,我們先來回顧一下整個android的window機制結構。
首先從WindowManagerService開始,我們知道WMS是window的最終管理者,在WMS中為每一個應用持有一個session,關于session前面我們講過,每個應用都是全局單例,負責和WMS通信的binder對象。WMS為每個window都建立了一個windowStatus對象,同一個應用的window使用同個session進行跨程序通信,結構大概如下:
而負責與WMS通信的,是viewRootImpl。前面我們講過每個view樹即為一個window,viewRootImpl負責和WMS進行通信,同時也負責view的繪制。如果把上面的圖畫仔細一點就是:
圖中每一個windowStatus對應一個viewRootImpl,WMS通過viewRootImpl來控制view。這也就是window機制的管理結構。當我們需要添加window的時候,最終的邏輯實作是WindowManagerGlobal,他的内部使用自己的session建立一個viewRootImpl,然後向WMS申請添加window,結構圖大概如下:
windowManagerGlobal使用自己的IWindowSession建立viewRootImpl,這個IWindowSession是全局單例。viewRootImpl和WMS申請建立window,然後WMS允許之後,再通知viewRootImpl繪制view,同時WMS通過windowStatus存儲了viewRootImpl的相關資訊,這樣如果WMS需要修改view,直接通過viewRootImpl就可以修改view了。
從上面的描述中可以發現我全程沒有提及到PhoneWindow和WindowManagerImpl。這是因為他們不屬于window機制内的類,而是封裝于window機制之上的架構。
假設如果沒有PhoneWindow和WindowManager我們該如何添加一個window?
首先需要調用WindowGlobal擷取session,再建立viewRootImpl,再通路wms,然後再利用viewRootImpl繪制view,是不是很複雜,而這僅僅隻是整體的步驟。
而WindowManagerImpl正是這個功能。他内部擁有WindowManagerGlobal的單例,然後幫助我們完成了這一系列的步驟。同時,windowManagerImpl也是隻有一個執行個體,其他的windowManagerImpl都是建立在windowManagerImpl單例上。這一點在前面有通過源碼介紹到。
另外,上面我講到PhoneWindow并不是window而是一個window容器,那為什麼他不要命名為windowContainer呢?
PhoneWindow這個類是谷歌給window機制進行更上一層的封裝。PhoneWindow内部擁有一個DecorView,我們的布局view都是添加到decorView中的,因為我們可以通過給decorView設定背景,寬高度,标題欄,按鍵回報等等,來間接給我們的布局view設定。
當我們通過windowManagrImpl添加window的時候,都會把建立的window和PhoneWindow聯系起來,讓PhoneWindow可以統一處理通過windowManagerImpl添加的window,這點上面有講到。這樣一來,PhoneWindow的存在,向開發者屏蔽真正的window,暴露給開發者一個“存在的”window。
我們可以認為PhoneWindow就是一個window,window是view容器。當我們需要在螢幕上添加view的時候,隻需要獲得應用window對應的windowManagerImpl,然後直接調用addView方法添加view即可。
這裡也可以解釋為什麼windowManager的接口方法是addView而不是addWindow,一個是window确實是以view的存在形式沒錯,二是為了向開發者屏蔽真正的window,讓我們以為是在往window中添加view。畫個圖如下:
黃色部分輸于谷歌提供給開發者的window架構,而綠色是真正的window機制結構。通過谷歌的架構我們可以很友善地管理螢幕上的view,而不須了解底層究竟是如何工作的。PhoneWindow的存在,更是讓window的“可見性”得到了實作,讓window變成了一個“view容器”。
好了最後來總結一下:
- Android内部的window機制與谷歌暴露給我們的api是不一樣的,谷歌封裝的目的是為了讓我們更好地使用window。
- dialog、popupWindow等架構更是對具體場景進行更進一步的封裝。
- 我們在了解window機制的時候,需要跳過應用層,看到window的本質,才能更好地幫助我們了解window。
- 在android的其他地方也是一樣,利用封裝向開發者屏蔽底層邏輯,讓我們更好地運用。但如果我們需要了解他的機制的時候,就需要繞過這層封裝,看到本質。
8 總結
全文到這裡,就基本結束了。下面先總結一下我這篇文章說了什麼:
- 詳述了什麼是window
- 對window的各種參數進行講解
- 講解window機制内的關鍵類
- 從源碼講解window的添加流程以及各大元件的window添加流程
- 詳解了PhoneWindow與window的關系,談了關于谷歌的封裝思想
文中最重要的一點就是認識window的本質,區分好window和view之間的關系。為什麼window可以設定背景設定标題欄設定寬高?那是因為有PhoneWindow的存在。讀者需要區分好window與PhoneWindow的關系。
筆者在寫這篇文章的時候,對于各節的安排是比較猶豫的:如果先講概念,沒有源碼流程的講解很難懂;先講源碼流程,沒有概念的認知很難讀懂源碼。最終還是決定了先講window的真正概念,先讓讀者有個整體上的感覺。
文章很長,筆者對于window想要講的都在這篇文章中。
希望文章對你有幫助。
參考文獻
直面底層:探索 Android 中的 Window
直面底層:WindowManager 視圖綁定以及體系結構
奇妙的Window之旅
Android進階必備,Window機制探索
《Android開發藝術探索》
《Android進階解密》
最後推薦一下我做的網站,玩Android: wanandroid.com ,包含詳盡的知識體系、好用的工具,還有本公衆号文章合集,歡迎體驗和收藏!
推薦閱讀:
看我一波,代碼優化到極緻的操作! 這效果炸了,網易雲音樂“宇宙塵埃”特效 三年啦,跳槽成功的Android開發面經總結!
掃一掃 關注我的公衆号
如果你想要跟大家分享你的文章,歡迎投稿~
┏(^0^)┛明天見!