window的類型
什么是window?通俗的來說察绷,window就是系統(tǒng)劃分的一個窗口干签,其本身是不可見的,是一個用來存儲view的容器拆撼。
在Android系統(tǒng)中容劳,window一共有三種類型,分別是System Window闸度、Application Window以及Sub Window
各個類型的window如圖所示竭贩,其中值得一提的是sub window,他們必須依附于另一種window之上莺禁,我們比較常用的就有dialog以及popwindow
Activity留量、Window、View的關(guān)系
view是我們眼睛具體看到界面;window本身是不可見的肪获,本質(zhì)上它只是系統(tǒng)劃分的一個窗口寝凌,是用來放置view的容器;而activity則是一個管理者孝赫,維護整個環(huán)節(jié)的運行较木,統(tǒng)一的管理所有view的繪制流程。
如果用貼窗花來類比Android系統(tǒng)青柄,那么activity就是剪窗花的人伐债,window是窗子,view是窗花致开。
Application Window創(chuàng)建過程
Window峰锁、WindowManager、WindowManagerService
我們查看Activity的源碼双戳,發(fā)現(xiàn)其中有2個成員變量與window相關(guān)
private Window mWindow;
private WindowManager mWindowManager;
顧名思義虹蒋,WindowManager是Window的管理類,但是它并不是真正的管理類飒货。因為在android中魄衅,存在著很多很多的window,其管理者必然是一個系統(tǒng)級的塘辅、跨進程的服務(wù)晃虫,也就是WindowManagerService。那么WindowManager又做了什么呢扣墩?其實它可以算作一個拉皮條的哲银,是window與wms之間通信的橋梁。
Window的初始化
開始分析源碼呻惕,可以發(fā)現(xiàn)mWindow是在Activity的attach()方法中初始化的
mWindow = new PhoneWindow(this, window);
繼續(xù)往下看荆责,在最后,可以看見wm也是在其中初始化的亚脆。
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
那么attach()方法又是在什么時候被執(zhí)行的呢做院?開動腦筋想一想,當然是在onCreate()之前啦型酥。前面說過window是view的容器山憨,那么在onCreate()將view添加進來之前,window肯定已經(jīng)準備就緒了弥喉。具體這部分代碼的分析就放在下篇activity啟動流程之中郁竟。
View的加載
有道友不知道setContentView()嗎?我們來看看activity中該方法的實現(xiàn)
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getwindow()會返回mWindow的實例由境,也就是調(diào)用了PhoneWindow的setContentView()方法
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
當mContentParent為空時棚亩,首先會調(diào)用installDecor()方法蓖议。查看該方法,可以看到有一個叫mDecor的玩意兒讥蟆,這東西是什么呢勒虾?
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
官方注釋是這么說的,mDecor是window中最頂層的view瘸彤,也就是根view修然,是window中的第一個容器。查看DecorView的源碼质况,也可以知道它是繼承自FrameLayout愕宋。換句說中,我們所寫的layout布局一定是放在DecorView中结榄。
繼續(xù)閱讀installDecor()方法:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
...
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
當mDecor為空時中贝,會調(diào)用generateDecor()方法初始化mDecor。這個方法很簡單臼朗,就是直接new了一個DecorView對象并返回邻寿。
之后當mContentParent為空時,會以剛剛創(chuàng)建的mDecor為參數(shù)视哑,通過generateLayout()方法給mContentParent賦值绣否。
到此為止,mContentParent已經(jīng)有值了黎炉,回到之前的setContentView()看到這行代碼
mLayoutInflater.inflate(layoutResID, mContentParent);
這個layoutResID就是我們通過Layout布局資源枝秤。是不是一切都通暢了醋拧?window中有一個DecorView慷嗜,mContentParent是DecorView的一部分,最終用戶view通過mLayoutInflater的inflate()方法傳遞到DecorView中丹壕。
Window的顯示過程
到現(xiàn)在為止庆械,window已經(jīng)創(chuàng)建并與activity綁定了,view也已經(jīng)加載進window中了菌赖,是時候去看看window是如何顯示在用戶手機屏幕上了缭乘。
通過之前的分析可以知道,window是由系統(tǒng)服務(wù)WindowManagerService統(tǒng)一管理的琉用。在activity的源碼中堕绩,我們找不到它是如何通過wms添加到手機屏幕中的,既然如此邑时,不妨換個思路奴紧,去看看其他類型window的源碼。
PopupWindow
popupwindow是通過showAtLocation()顯示在界面上的晶丘,直接查看該方法源碼:
public void showAtLocation(IBinder token, int gravity, int x, int y) {
...
TransitionManager.endTransitions(mDecorView);
detachFromAnchor();
mIsShowing = true;
mIsDropdown = false;
mGravity = gravity;
final WindowManager.LayoutParams p = createPopupLayoutParams(token);
preparePopup(p);
p.x = x;
p.y = y;
invokePopup(p);
}
代碼中間有一個叫WindowManager.LayoutParams的玩意兒黍氮,它繼承自ViewGroup.LayoutParams唐含,其存放了window在屏幕上顯示時的各種參數(shù)。
在文章最開始時說window有三種類型沫浆,其實就是在WindowManager.LayoutParams中看到的捷枯。在此處我只是將三種大類列出來,每一個類別還有很多子類別专执,其中有一些是我們可以使用的淮捆,另一些是系統(tǒng)級別的。
/**
* Start of window types that represent normal application windows.
*/
public static final int FIRST_APPLICATION_WINDOW = 1;
/**
* Start of types of sub-windows. The {@link #token} of these windows
* must be set to the window they are attached to. These types of
* windows are kept next to their attached window in Z-order, and their
* coordinate space is relative to their attached window.
*/
public static final int FIRST_SUB_WINDOW = 1000;
/**
* Start of system-specific window types. These are not normally
* created by applications.
*/
public static final int FIRST_SYSTEM_WINDOW = 2000;
WindowManager.LayoutParams通過createPopupLayoutParams(token)方法進行初始化本股。這個方法很簡單争剿,就是將LayoutParams中的一系列參數(shù)進行賦值。
p.gravity = computeGravity();
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType;
p.token = token;
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource();
傳進去的參數(shù)token又是什么呢痊末?
private WindowManager.LayoutParams createPopupLayoutParams(IBinder token)
原來token是一個IBinder對象蚕苇,這里就很好理解了,window通過跨進程的windowManagerService進行統(tǒng)一的管理凿叠,那么wms必然需要某個標識來區(qū)分每一個window涩笤,而token顯然就是這個標識。
接著看 preparePopup(p)方法盒件。
if (mBackground != null) {
mBackgroundView = createBackgroundView(mContentView);
mBackgroundView.setBackground(mBackground);
} else {
mBackgroundView = mContentView;
}
最開始就說過蹬碧,sub window必須依附在父window之上,而preparePopup(p)就是用來給popupwindow創(chuàng)建根view的炒刁。到此為止恩沽,window已經(jīng)創(chuàng)建并被賦值了各種參數(shù),根view也創(chuàng)建好了翔始,剩下應(yīng)該就是通過wms進行管理了罗心,我們看最后一個方法invokePopup(p)
private void invokePopup(WindowManager.LayoutParams p) {
...
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
...
mWindowManager.addView(decorView, p);
...
}
顯而易見,最終讓window顯示在屏幕上的城瞎,是mWindowManager.addView(decorView, p)
mWindowManager是一個接口渤闷,找到其被實例化的地方
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
系統(tǒng)在開啟的時候,會初始化很多的SystemService脖镀,此處通過aidl獲取相關(guān)的系統(tǒng)服務(wù)飒箭,即WindowManagerImpl。
接著去找WindowManagerImpl的addView()蜒灰,可以看到其最終調(diào)用了WindowManagerGlobal的addView()弦蹂,這個方法很長,我挑重點展示一下:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
//注釋1
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//注釋2
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
//注釋3
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
}
}
首先看注釋2强窖,其中有三個集合凸椿, mViews、mRoots毕骡、mParams分別用來存放某個window的decorView削饵,ViewRootImpl渲染器以及LayoutParams參數(shù)岩瘦,且這三者的順序是一一對應(yīng)的。
接著看注釋1窿撬,if語句中判斷當前window的類型是否是sub window启昧,如果是的話,就遍歷mRoots集合劈伴,找到其中與當前window參數(shù)中token相同的window密末,并將其設(shè)置為當前window的父window。
之前說過這個token是一個IBinder對象跛璧,是用來在跨進程通信中確定window身份的严里。那么它具體是個什么玩意兒呢?
public void showAtLocation(View parent, int gravity, int x, int y) {
showAtLocation(parent.getWindowToken(), gravity, x, y);
}
從上面的代碼可以知道追城,token就是當前window的父window的標識刹碾。原來如此,所以在windowManagerGlobal.addView()中座柱,只要mRoots中某個window的token與當前window的layoutParams中的token相同迷帜,那么它就是當前window的父window。
最后看注釋3色洞,在前面已經(jīng)獲取了decorview戏锹,layoutParams以及panelParentView,所以現(xiàn)在就可通過ViewRootImpl的setView方法對屏幕進行渲染繪制了火诸!
ViewRootImpl
setView這個方法比較長锦针,選擇其中跟window相關(guān)的最關(guān)鍵的代碼
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(),mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel);
這里有一個叫做mWindowSession的東西。熟悉web的同學一定知道置蜀,session和cookie是服務(wù)器端與客戶端用來進行身份識別的奈搜,其中session存放在服務(wù)器端,cookie存放在客戶端盾碗。那么為啥在android系統(tǒng)里還會出現(xiàn)session呢媚污?
在文章最開始就說過了舀瓢,整個window的管理是一個進程間的管理廷雅,系統(tǒng)的windowManagerService就相當于服務(wù)器端,用戶的每一個window所對應(yīng)的windowManager就相當于客戶端京髓。因此這個IWindowSession就是wms用來識別不同wm的“身份證”航缀。
再來看看IWindowSession初始化的地方,在ViewRootImpl的構(gòu)造方法中堰怨,有這樣一段
mWindowSession = WindowManagerGlobal.getWindowSession();
OK芥玉,回去找WindowManagerGlobal
public static IWindowSession getWindowSession() {
...
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
...
return sWindowSession;
}
}
艾瑪,這不就是典型的AIDL嘛备图,首先看這里的windowManager
IWindowManager windowManager = getWindowManagerService();
不要被它的名字所欺騙灿巧,其實它是WindowManagerService的IBinder引用赶袄,不信就點進去看看getWindowManagerService,其中有這樣一行代碼
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
這兒就不多做解釋了抠藕,對AIDL不太清楚的同學移步Binder恐怖如斯
現(xiàn)在我們已經(jīng)獲取了WindowManagerService的IBinder引用饿肺,接下來就是通過AIDL調(diào)用遠程端的openSession方法。顯而易見盾似,我們要去wms中查看這個方法到底做了什么:
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
IInputContext inputContext) {
...
Session session = new Session(this, callback, client, inputContext);
return session;
}
很簡單敬辣,就是根據(jù)客戶端創(chuàng)建了一個session并返回。順帶一提零院,wms是通過ArraySet來存放客戶端的所有session信息的溉跃,有詩為證:
final ArraySet<Session> mSessions = new ArraySet<>();
回過神,現(xiàn)在我們帶著獲取到的session回到最開始的ViewRootImpl中告抄,也就是res = mWindowSession.addToDisplay()
撰茎。既然mWindowSession是服務(wù)端wms的一個IBinder引用,那么同上打洼,我們要去wms中尋找addToDisplay這個方法乾吻。
遺憾的是,addToDisplay不見了拟蜻!幸好天無絕人之路绎签,雖然沒有了addToDisplay,但是我們可以在wms中找到一個叫addWindow的方法酝锅。來對比一下這兩家伙的參數(shù)诡必。
addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(),mDisplay.getDisplayId(),mAttachInfo.mContentInsets,
mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel);
addWindow(Session session, IWindow client, int seq,
LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel)
巧不巧?你就說巧不巧搔扁!addWindow就比addToDisplay多了一個session參數(shù)爸舒。原來如此,客戶端調(diào)用了IBinder的addToDisplay之后稿蹲,會傳入當前客戶端的session,最終通過AIDL在服務(wù)端調(diào)用了addWindow方法扭勉。
那么這個addWindow又干了些什么呢?這就涉及到具體的繪制流程了苛聘,代碼太復雜沒法細看涂炎,總結(jié)起來就是一句話,判斷各種權(quán)限设哗、window的狀態(tài)以及window的類型唱捣,之后對window進行繪制,最終將window綁定的結(jié)果返回給ViewRootImpl网梢。
要注意的是震缭,到此為止,window繪制完畢了战虏,但是window中的view還是空空如也拣宰。而ViewRootImpl在獲取到window的綁定結(jié)果后党涕,如果成功,就會開始進行一系列的view的繪制流程巡社。這部分的內(nèi)容相當變態(tài)遣鼓,咱們有緣再見吧。
Application Window顯示過程
經(jīng)過前面一大段的嗶嗶重贺,Sub Window的顯示過程已經(jīng)說清楚了骑祟。還記得為什么我們要去看sub window的顯示過程嗎?因為Application Window的顯示過程在activity里面找不到啊气笙。但是在文章的末尾次企,這種坑怎么能不填呢?
遺憾的是潜圃,Application Window的顯示過程和activity的生命周期有關(guān)缸棵,這部分的內(nèi)容也是相當變態(tài),需要有緣再見谭期。所以先不要知道為什么堵第,只要記住在ActivityThread中有一個叫handleResumeActivity()的方法,其中有這樣一段:
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
有沒有一種便秘通暢的感覺隧出?這段代碼將window的類型設(shè)置為TYPE_BASE_APPLICATION踏志,然后通過wm.addView(decor, l)開始和windowManagerService通信。
完結(jié)撒花~