???????Android應用程式視窗設計之視窗的添加
Android應用程式視窗設計系列博客:
Android應用程式視窗設計之Window及WindowManager的創建
Android應用程式視窗設計之setContentView布局加載的實作
普法Android的Token前世今生以及在APP,AMS,WMS之間傳遞
Android應用程式視窗設計之視窗的添加
本篇博客撰寫思路總結和關鍵點說明:

為了更加方便的讀者閱讀博客,通過導讀思維圖的形式將本博客的關鍵點列舉出來,從而方便讀者取舍和閱讀!
引言
??通過前面博客Android應用程式視窗設計之Window及WindowManager的創建和Android應用程式視窗設計之setContentView布局加載的實作的原始碼分析以及結合實際開發的相關操作,我們完成了對Android應用程式視窗設計的如下積累:
- 掌握了Android應用程式視窗以及視窗管理器的創建
- 掌握了Android應用程式視窗中的布局檔案是怎么被加載實作的(即Activity怎么通過setContentView加載布局 )
那么Android應用程式視窗的實作就此完了嗎,當然不是!此時距離視窗展示到我們的終端界面還有很長的路要走(其中主要遺留的是Android系統是怎么對視窗進行管理和添加以及繪制等),但是不要灰心當然也不要喪氣,路是一步步走的,飯是一口口吃的不是,今天的博客我將帶領讀者攻破Android應用程式視窗添加機制,從而為全盤掌握Android應用程式視窗的實作又下一城!
注意:本篇的介紹是基于Android 7.xx平臺為基礎的,且視窗的添加是以Activity對應的視窗為例來說明的!其中涉及的代碼路徑如下:
frameworks/base/core/java/android/view/
--- WindowManager.java
--- View.java
--- ViewManager.java
--- ViewRootImpl.java
--- Window.java
--- Display.java
--- WindowManagerImpl.java
--- WindowManager.java
--- WindowManagerGlobal.java
--- IWindowManager.aidl
--- IWindow.aidl
--- IWindowSession.aidl
frameworks/base/services/core/java/com/android/server/wm/
---WindowManagerService.java
---AppWindowToken.java
---WindowState.java
---Session.java
---WindowToken.java
在正式開始本篇博客Android應用程式視窗添加機制及原始碼分析前,這里有必要先將涉及關于Android應用程式視窗相關類以及類圖圖關系先放出來,以便能夠方便后續的相關閱讀,使讀者能夠更加清楚的知道這幾者之間在原始碼中的關系,

一.前期知識準備
??老話說的好,磨刀不誤砍柴工!所以我們也得先磨磨刀,有幾個相關知識點我們在正式開始相關的原始碼分析前,必須先亮出來!
關于下面要亮出的幾個知識點,沒有一定知識積累的可能一時不一定能理解,沒有關系!我在這里提前印出來只是為了讓讀者心里有一個底,提前熟悉下,后面在原始碼分析中會一一講解到的,
1.1 Android視窗的分類
Android的視窗被大致分為三類,分別是:
- Android應用程式視窗,這個是最常見的(擁有自己的WindowToken)譬如:Activity與Dialog
- Android應用程式子視窗(必須依附到其他非子視窗才能存在,通常這個被依附的視窗型別Activity視窗) 例如:PopupWindow
- Android系統視窗,其中我們最最常見的就是Toast視窗了
這里Dialog比較特殊,從表現上來說偏向于子視窗,必須依附到Activity才能存在,而從性質上來說,仍然是應用視窗,有自己的AppWindowToken,
1.2 視窗管理中涉及的幾個重要概念
在接下來要分析的視窗添加以及視窗的管理中,有幾個非常只要的知識點有必要提前點名一下來一個眼前熟,它們分別是:
- IWindow: APP端視窗暴露給WMS的抽象實體,同時也是WMS向APP端發送訊息的Binder通道,它在APP端的實作為W
- IWindowSession:WMS端暴露給視窗端的用于和WMS服務端通信的Binder通道
- WindowState:WMS端視窗的令牌,與IWindow視窗一一對應,是WMS管理視窗的重要依據
- WindowToken:是視窗的令牌,也是視窗分組的依據,在WMS端,和分組對應的資料結構是WindowToken
- Token:是在AMS構建Activity對應的ActivityRecord時里面的IApplicationToken的實體,會在Activity創建程序中傳遞到AMS中,并且Token會在Activity從創建到顯示的程序中會在App行程和AMS,WMS之間進行傳遞,關于Token可以詳見博客普法Android的Token前世今生以及在APP,AMS,WMS之間傳遞,此處很重要!

- IWindowSession:WMS服務用于提供給ViewRootImpl來和其進行跨Binder通信的介面
好了前面的這幾個知識點,讀者先心里大概有個了解即可了,通過后面的原始碼了解翻過頭來看你就會有一種大徹大悟的感覺了,
二.APP應用程式端開始處理視窗的添加流程
??通過前面的博客Android應用程式視窗設計之setContentView布局加載的實作分析我們可知在將視窗布局加載OK以后,會呼叫wm.addView()方法添加,在該方法中的decor引數為Acitity對應的Window中的視圖DecorView,wm為在創建PhoneWindow時創建的WindowManagerImpl物件(對于此處還有疑問的讀者,可以回看博客),該物件的addView方法實際最終呼叫到到是單例物件WindowManagerGlobal的addView方法(前文有提到),其關系如下:

所以本章節我們會從此處繼續續寫前緣,接著分析!
//[WindowManagerImpl.java]
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
android.util.SeempLog.record_vg_layout(383,params);
applyDefaultToken(params);//設定默認Token
//這里我們需要注意的是此時的mParentWindow指向了前面創建的Activity對應的視窗PhoneWIndow
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);//詳見章節2.1
}
2.1 WindowManagerGlobal.addView(…)添加視窗處理
在看addView代碼前,我先來看看WindowManagerGlobal物件成員變數(前面類圖關系中也可以看到),如下:
//[WindowManagerGlobal.java]
private static WindowManagerGlobal sDefaultWindowManager;
private static IWindowManager sWindowManagerService;
private static IWindowSession sWindowSession;
private final Object mLock = new Object();
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
這里我們可以看到它有非常重要的三個成員變數mViews、mRoots和mParams分別是型別為View、ViewRootImpl和WindowManager.LayoutParams的陣列(它們之間是什么關系呢,這個后續我們會重點分析),此外還有一個成員變數mDyView,保存的則是已經不需要但還未被系統會收到View,
這里的View與LayoutParams比較好理解(這個是最最基本的Android概念),那么這里的突然冒出的ViewRootImpl物件的作用是什么呢?我們知道WindowManagerGlobal被稱作為視窗管理器,我們也知道管理者嗎一般是管理并不會直接得參與行動,它通常是根據Acitity和Window的呼叫請求,找到合適的做事的人,而真正執行WindowManagerGlobal相關功能的就是ViewRootImpl類了,它可以說是WindowManagerGlobal最得力的干將沒有之一,
這里我們先代為介紹下ViewRootImpl的基本實戰技能有如下幾點(這個在后續的ViewRootImpl中會有所體現):
1.完成了繪制程序,在ViewRootImpl類中,實作了perfromMeasure()、performDraw()、performLayout()等繪制相關的方法,
2.與系統服務進行互動,例如與AMS,WMS,DisplayService、AudioService等進行通信,保證了Acitity相關功能等正常運轉,
3.處理觸屏事件等分發邏輯的實作

好了,前面介紹了這么多是時候直接開擼原始碼了,翠花上原始碼addView如下:
//[WindowManagerGlobal.java]
public void addView(View view, //此處的view指向DecorView
ViewGroup.LayoutParams params,//這里的params為Window對應的默認WindowManager.LayoutParams實體物件mWindowAttributes
Display display, //這里的Display具體指向表示物理顯示設備有關的邏輯顯示的大小(譬如尺寸解析度)和密度的資訊
Window parentWindow) //這里的parentWindow指向了Activity對應的視窗實體物件PhoneWindow
{
//引數有效性的檢查
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
/*
注意此時的parentWindow為不為null,
指向的是Activity對應的PhoneWindow視窗,這個在視窗的創建程序中已經有說明
根據視窗型別調整LayoutParams的相關引數
*/
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
...
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
if (mSystemPropertyUpdater == null) {//監聽Property的變化
...
}
/*
從mViews中查找是否已經有添加過同樣的View
*/
int index = findViewLocked(view, false);
if (index >= 0) {
...
}
...
/*
構建ViewRootImpl
它很重要,它很重要,它很重要,它是WindowManagerGlobal最最得力的干將沒有之一
基本包攬了WindowManagerGlobal絕大部分作業
*/
root = new ViewRootImpl(view.getContext(), display);//詳見章節2.2
view.setLayoutParams(wparams);
mViews.add(view);//這三者之間是一一對應的情況
mRoots.add(root);
mParams.add(wparams);
}
try {
/*
將DecorView添加到ViewRootImpl中,最后添加到WMS中
注意這里傳遞的引數
*/
root.setView(view, wparams, panelParentView);//詳見章節2.3
} catch (RuntimeException e) {
synchronized (mLock) {//發生例外處理情況
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
addView()方法涉及的代碼不是很多,但是它是簡約而不簡單啊,這里我們先來看看它的入參引數,如下:
| 引數型別 | 引數名稱 | 引數功能和含義 |
|---|---|---|
| View | view | 此處的view指向DecorView的實體物件 |
| ViewGroup.LayoutParams | params | 這里的params為Window對應的默認WindowManager.LayoutParams 實體物件mWindowAttributes,并且它的成員變數type默認值為TYPE_APPLICATION(這個很關鍵) |
| Display | display | 這里的Display具體指向表示物理顯示設備有關的邏輯顯示的大小(譬如尺寸解析度)和密度的資訊 |
| Window | parentWindow | 這里的parentWindow指向了Activity對應的視窗實體物件PhoneWindow |
引數分析完畢,正式開始原始碼的分析,可以看到這里會對入參的引數進行相關的合法性檢測(這個不是重點),當我們傳入的引數parentWindow不為null時,呼叫它的方法adjustLayoutParamsForSubWindow()對傳入的引數params進行調整,這個方法我們簡單看下:
//[Window.java]
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
...
/*
判斷視窗型別是不是應用程式子視窗型別
*/
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
if (wp.token == null) {
View decor = peekDecorView();
if (decor != null) {
//LayoutParams的token設定為W本地Binder物件
wp.token = decor.getWindowToken();
}
}
...
} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
...
} else {
/*
如果不是子視窗和系統視窗,同時當前視窗沒有指定容器
則設定token為當前視窗的mAppToken代理物件
否則設定為指定容器的mAppToken代理物件
此時的mAppToken是從AMS傳遞過來的
*/
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
...
}
還記得我們本博客一開始就重點說明了Android的視窗分為三種型別嗎,上述的原始碼就是對不同型別視窗的布局引數進行相應的設定,比如布局引數中的token設定!這里我們先不分析WindowManager.LayoutParams的原始碼,對于它我們現在暫且只需知道如下三點:
-
應用程式視窗:對應的type取值范圍為FIRST_APPLICATION_WINDOW到LAST_APPLICATION_WINDOW之間,譬如Activity和Dialog
-
子視窗:對應的type取值范圍為FIRST_SUB_WINDOW到LAST_SUB_WINDOW之間,譬如PopupWindow
-
系統視窗:對應的type取值范圍為FIRST_SYSTEM_WINDOW到LAST_SYSTEM_WINDOW之間,譬如Toast
好了這里讓我們看看該方法中是怎么對布局引數進行調整的,它遵循了三個原則:
- 如果是應用程式子視窗型別,則設定token為W本地Binder物件(這里的W本地物件也在開始有簡單介紹過了,這里先不指明后續會有分析)
- 對于系統型別視窗token沒有調整
- 如果不是應用程式子視窗和系統視窗,且同時當前視窗沒有指定視窗容器,則設定token為當前視窗的IApplicationToken.Proxy代理物件,否則設定為指定視窗的IApplicationToken.Proxy代理物件
由于我們這里是以Activity的視窗添加為例的,所以會走前面的第三條原則,設定為當前Activity對應的視窗的IApplicationToken.Proxy代理物件,而它通過前面我們博客普法Android的Token前世今生以及在APP,AMS,WMS之間傳遞知道它的物體端是ActivityRecord持有的Token物件,
分析至此我們知道,當應用程式向視窗管理器中添加一個視窗視圖物件時,首先會為該視圖物件創建一個ViewRootImpl物件,并且將視圖物件、ViewRootImpl物件、視圖布局引數分別保存到視窗管理器WindowManagerGlobal的mViews、mRoots、mParams陣列中(并且他們三者在對應的陣列中的索引是一一對應的),如下圖所示:

最后通過ViewRootImpl物件來完成視窗視圖的添加和顯示程序!
2.2 ViewRootImpl得力干將構建程序

還記得在前面章節2.1的一開始我們就將ViewRootImpl的功能夸上了天,是時候展示下真正的實力了,我們先來看看它的構造方法如下:
//[ViewRootImpl.java]
final IWindowSession mWindowSession;
final ViewRootHandler mHandler = new ViewRootHandler();
// These can be accessed by any thread, must be protected with a lock.
// Surface can never be reassigned or cleared (use Surface.clear()).
final Surface mSurface = new Surface();
public ViewRootImpl(Context context, Display display) {
mContext = context;
/*
得到IWindowSession的代理物件,該物件用于和WMS通信
看到這里讀者會不會心里有一個疑問就是為什么不是WMS的Binder代理端,而是
來了一個IWindowSession呢,咋和Activity啟動程序中直接使用AMP和AMS有點不同呢
*/
mWindowSession = WindowManagerGlobal.getWindowSession();//詳見章節2.3
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
mWinFrame = new Rect();
/*
創建了一個W本地Binder物件,用于WMS通知應用程式行程,
有點像ActiviytThread中的IApplicationThread實體物件
關于Activity和WMS的互動完全可以參考Activity啟動程序中和AMS的互動
但是ActivityThread中的IApplicationThread是單一實體,而這里的W卻是每一個
ViewRootImpl都擁有一個
*/
mWindow = new W(this);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
/*
構造一個AttachInfo物件
注意傳入的一個引數為Handler型別ViewRootHandler實體mHandler,
用于處理Android應用程式的視窗資訊
*/
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityInteractionConnectionManager =
new AccessibilityInteractionConnectionManager();
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mHighContrastTextManager = new HighContrastTextManager();
mAccessibilityManager.addHighTextContrastStateChangeListener(
mHighContrastTextManager);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
/*
獲取Choreographer實體物件mChoreographer(注意它是單例模式),所以一個Android應用程式只會擁有一個
Choreographer將會被用于統一調度視窗繪圖
江湖外號"編舞者"
*/
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
loadSystemProperties();
}
可以看到在ViewRootImpl的構造方法以及它的方法區中創建并初始化了一些關鍵的成員變數(大哥也要吃飯不是,必須整幾個小弟干活的),我們來看看ViewRootImpl中關鍵的成員變數以及意義:
-
通過WindowManagerGlobal.getWindowSession()獲取IWindowSession的代理物件,用于后續和WMS服務通信,這個后續詳細掰持掰持
-
創建IWindow的Binder物體端類W的實體物件mWindow(注意它是一個匿名Binder類),用于WMS通知APP端進行跨行程通信
-
采用單例模式創建了一個Choreographer物件,用于統一調度視窗繪圖(這個本篇博客不重點關注,這個涉及到視窗繪制的內容)
-
創建ViewRootHandler物件,用于處理當前視圖訊息的處理
-
構造一個AttachInfo物件;
-
創建Surface物件(注意此時它還只是一個空殼子),用于繪制當前視圖,當然該Surface物件的真正創建是由WMS來完成的,只不過是WMS傳遞給應用程式行程的(這個涉及到Binder跨行程傳遞資料了,Binder很重要啊!)
如果對ViewRootImpl以及它的小弟之間的關系,錯了和它成員類之間的關系還是有點迷糊的話,沒有關系來個終極大殺器來張結構圖,這樣應該會清晰明了些!

2.3 getWindowSession()獲取和WMS通信的Binder代理端IWindowSession
在開始分析getWindowSession()的原始碼前,我們先來認識一下IWindowSession,正所謂知己知彼方能百戰百勝嗎!它是一個aidl介面類(肯定和Binder通信有關了),Android原始碼對它的定義如下:
//[IWindowSession.aidl]
/**
中文翻譯一下就是用于每個應用程式和WMS進行通信的介面
* System private per-application interface to the window manager.
*
* {@hide}
*/
interface IWindowSession {
}
其用類圖關系表示如下:

好了,是時候開始我們真正的表演了,不真真的分析了!
//[WindowManagerGlobal.java]
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
//獲取輸入法管理器服務Binder代理端
InputMethodManager imm = InputMethodManager.getInstance();
//獲取視窗管理器服務Binder代理端
IWindowManager windowManager = getWindowManagerService();
//得到IWindowSession代理物件
//這里需要注意的是傳遞的兩個引數都是匿名Binder,所以匿名Binder讀者最好能掌握掌握
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
這里需要注意地是注意這里的IWindowManager不要和WindowManager搞混淆了,IWindowManager也是一個aidl介面類,被WMS服務實作了,我們先來看看它的類圖:

有了以上支持的加持,分析上述的原始碼就水到渠成了,以上通過WMS服務的代理端跨Binder呼叫到的WMS服務的openSession()方法創建應用程式與WMS之間的連接通道,即獲取IWindowSession代理物件,并將該代理物件保存到WindowManagerGlobal的靜態成員變數sWindowSession中,因此在整個應用程式行程中IWindowSession代理物件是唯一的!
這里我們簡單來看看WMS服務是怎么處理openSession()請求的,如下:
//[windowManagerService.java]
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
IInputContext inputContext) {
//例外檢測
if (client == null) throw new IllegalArgumentException("null client");
if (inputContext == null) throw new IllegalArgumentException("null inputContext");
//直接明了,構建一個Session實體
Session session = new Session(this, callback, client, inputContext);
return session;
}
WMS端對openSession()的處理簡單明了,直接通過傳遞過來的引數構建了Session的Binder物體端,然后跨Binder回傳回去,至此構建了如下的一條Binder通信通道:

2.4 IWindow的Binder物體端類W的構造程序
在開始分析W的構造之間,我們先來認識一下IWindow,正所謂知己知彼方能百戰百勝嗎!它是一個aidl介面類(肯定和Binder通信有關了),Android原始碼對它的定義如下:
//[IWindow.aidl]
/**
這里我用我蹩腳的英文簡單翻譯一下就是,WMS服務用于通知客戶端視窗一些事情的通道
* API back to a client window that the Window Manager uses to inform it of
* interesting things happening.
*
* {@hide}
*/
oneway interface IWindow {
}
好了有了上面Android的官方解釋我們就很清楚了,此處的W本地物件用于WMS通知應用程式行程,有點像ActiviytThread中的IApplicationThread實體物件用于AMS和APP行程通信的方法,都是使用匿名Binder!
關于Activity和WMS的互動完全可以參考Activity啟動程序中和AMS的互動但是ActivityThread中的IApplicationThread是單一實體,而這里的W卻是每一個ViewRootImpl都擁有一個
W的構造方法比較簡單,沒有什么過多看的,我們只要了解清楚了它的類圖關系以及被設計的初衷即可了!

2.5 AttachInfo構造程序
AttachInfo的構造程序就比較簡單了,我們簡單過下!
//[View.java]
AttachInfo(IWindowSession session, IWindow window, Display display,
ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
mSession = session;//IWindowSession代理物件,用于和WMS通信
mWindow = window;//W物件
mWindowToken = window.asBinder();//W本地Binder物件
mDisplay = display;
mViewRootImpl = viewRootImpl;//ViewRootImpl實體
mHandler = handler;//ViewRootHandler物件
mRootCallbacks = effectPlayer;//回呼實體物件
}
2.6 ViewRootImpl.setView()開始視窗視圖的添加
前面將ViewRootImpl的得力小弟一一介紹完畢了,氣勢搞起來了,是時候來看看ViewRootImpl是怎么處理視窗視圖的添加了!
/**
這里我們分析的是Activity的DecorView視窗視圖添加的邏輯,所以此時不存在父視圖的概念,
不會走到這里,此時的panelParentView為null
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
/*
初始化成員變數mView、mWindowAttraibutes
mAttachInfo是View類的一個內部類AttachInfo類的實體物件
該類的主要作用就是儲存一組當View attach給它的父Window的時候Activity各種屬性的資訊
將DecorView保存到ViewRootImpl的成員變數mView中
*/
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);//拷貝引數
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
attrs = mWindowAttributes;
...
/*
我們的DecorView實作了RootViewSurfaceTaker介面,所以會進入此分支
*/
if (view instanceof RootViewSurfaceTaker) {
mSurfaceHolderCallback =
((RootViewSurfaceTaker)view).willYouTakeTheSurface();
if (mSurfaceHolderCallback != null) {
mSurfaceHolder = new TakenSurfaceHolder();
mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
}
}
...
/*
繼續相關的初始化
*/
mSoftInputMode = attrs.softInputMode;
mWindowAttributesChanged = true;
mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
mAttachInfo.mRootView = view;
mAttachInfo.mScalingRequired = mTranslator != null;
mAttachInfo.mApplicationScale =
mTranslator == null ? 1.0f : mTranslator.applicationScale;
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
}
//標記DecorView是否已經添加
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
/*
這里呼叫異步重繪請求,最侄訓呼叫performTraversals方法來完成View的繪制
在向WMS添加視窗前進行UI布局,這個本篇博客將不會重點分析,我們先重點關注視窗的添加
*/
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
...
try {
...
/*
將視窗添加到WMS服務中,mWindow為W本地Binder物件,
通過Binder傳輸到WMS服務端后,變為IWindow代理物件
*/
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
...
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
}
...
if (res < WindowManagerGlobal.ADD_OKAY) {
...
//檢查例外情況,并拋出例外給上層處理
}
//建立視窗訊息通道
if (view instanceof RootViewSurfaceTaker) {
mInputQueueCallback =
((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
}
//建立輸入事件通道
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
...
}
}
}
此處的代碼有點多,我這里采取四舍五入的方法將一些細節處理的代碼暫時省略了,在對該原始碼分析前我們先來回憶回憶腦補一下,我們是如何走到現在的階段的:
- 首先我們在Activity啟動的最開始階段為它創建了視窗和視窗管理器
- 然后用戶自定義的UI作為一個子View被添加到DecorView中,然后將頂級視圖DecorView添加到應用程式行程的視窗管理器中,視窗管理器首先為當前添加的View創建一個ViewRootImpl物件、一個布局引數物件ViewGroup.LayoutParams,然后將這三個物件分別保存到當前應用程式行程的視窗管理器WindowManagerImpl中
- 最后就是呼叫ViewRootImpl的setView進行下一步的處理

好了,來龍我們整清楚了,我們得看看去脈,看看ViewRootImpl是怎么通過setView方法向WMS服務添加一個視窗的:
-
首先呼叫ViewRootImpl內部方法requestLayout,對視窗視圖中的UI進行視窗布局等操作(三件套操作,測量,布局,繪圖),這個先不放在這個博客中分析,后續專門博客來講
-
然后呼叫前面的獲取的代理端IWindowSession的addToDisplay向WMS添加一個視窗物件
-
最后為ViewRootImpl注冊一些列的視窗和輸入事件通道,用于處理一系列事件
2.7 APP應用程式端開始處理視窗的添加流程小結
注意:上述原始碼分析我將關于Android應用程式視窗UI布局繪制的流程沒有分析,因為那個牽涉的東西非常多,不是一下子能分析清楚的,總之UI布局三個老套路測量,布局,繪圖!上述關于視窗UI布局牽涉的知識點也比較多,我們會在后續的專門的博客中進行分析,我們本篇博客的主體是將Android應用程式視窗的添加流程分析清楚!
至此APP應用程式端處理視窗添加的流程就到此結束了,我們先不乘勝追擊先來復盤一下我們前面的整體流程:
- 首先我們在Activity啟動的最開始階段為它創建了視窗和視窗管理器
- 然后用戶自定義的UI作為一個子View被添加到DecorView中,然后將頂級視圖DecorView添加到應用程式行程的視窗管理器中,視窗管理器首先為當前添加的View創建一個ViewRootImpl物件、一個布局引數物件ViewGroup.LayoutParams,然后將這三個物件分別保存到當前應用程式行程的視窗管理器WindowManagerImpl中-
- 最后就是通過ViewRootImpl物件構造程序中和WIMS建立的IWindowSession匿名Binder通道將當前視圖物件注冊到WMS服務中
在前面2.6章節中已經有放出了簡單的流程圖,這里我就先偽代碼來簡單描述下:
WindowManagerImpl.addView(...)--->
WindowManagerGlobal.addView(...)--->
parentWindow.adjustLayoutParamsForSubWindow(...)--->
new ViewRootImpl(...)--->
WindowManagerGlobal.getWindowSession()--->
new W(this)--->
Choreographer.getInstance()--->
mViews.add(view)--->
mRoots.add(root)--->
mParams.add(wparams)--->
ViewRootImpl.setView(...)--->
mWindowSession.addToDisplay(...)--->
三.WMS端開始處理視窗的添加流程
不容易啊,視窗的添加我們Android應用程式端已經處理完畢,現在要正式開始進入WMS服務端進行處理了,激不激動,期不期待,廢話不多說直接開始干了,
3.1 Session.addToDisplay()處理視窗添加
還記得大明湖畔的夏雨荷嗎!錯了,還記得前面章節的我們的呼叫了mWindowSession.addToDisplay向WMS發起了添加視窗的請求,此時IWindowSession跨行程Binder的能力是時候體現出來了,我們原始碼展開分析一下:
//[Sesstion.java'
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
//注意這里的mService是在前面WMS呼叫openSession方法構造Session時傳遞過來的參考
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);//詳見3.2
}
好嗎,其實Session就是一個中間人而已(掮客,不需要留下買路錢的那種),APP應用程式段通過它的跨行程Binder能力發送來的請求,它想都不想直接傳給WMS服務進行處理了,我是干飯人!
3.2 WMS開始真正開始視窗添加
前面的所有所有,都是為了此時的榮譽時刻!我們來看看視窗的真正管理者WMS是怎么處理視窗添加流程的(前方高能,讀者最好能先活動活動腦路,否則在接下來的原始碼分析中很難理清楚某些知識點,牛逼的人不算啊!),但是在這之前WMS的幾個成員變數我們有必要提前重點點名下:
//[WindowManagerService.java]
/**
* All currently active sessions with clients.
*/
final ArraySet<Session> mSessions = new ArraySet<>();
/**
* Mapping from an IWindow IBinder to the server's Window object.
* This is also used as the lock for all of our state.
* NOTE: Never call into methods that lock ActivityManagerService while holding this object.
*/
final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
/**
* Mapping from a token IBinder to a WindowToken object.
*/
final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();

好了,我們對前面的兩個哈希串列先有個了解,我們通過原始碼來分析深入它二者的設計意圖:
//[WindowManagerService.java]
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
//這里的client為IWindow的代理物件,用于WMS和Activity進行通信
...
WindowState attachedWindow = null;
...
synchronized(mWindowMap) {
//判斷我們添加的視窗是否已經存在
if (mWindowMap.containsKey(client.asBinder())) {
Slog.w(TAG_WM, "Window " + client + " is already added");
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
//判斷添加的視窗型別是否為子視窗
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
/*
根據attrs.token從mWindowMap中取出應用程式視窗在WMS服務中的描述符WindowState
這里的前提是父視窗必須在WMS中已經被添加了
*/
attachedWindow = windowForClientLocked(null, attrs.token, false);
if (attachedWindow == null) {
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
&& attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
}
if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display. Aborting.");
return WindowManagerGlobal.ADD_PERMISSION_DENIED;
}
boolean addToken = false;
//根據attrs.token從mTokenMap中取出應用程式視窗在WMS服務中的描述符WindowToken
WindowToken token = mTokenMap.get(attrs.token);
AppWindowToken atoken = null;
boolean addToastWindowRequiresToken = false;
if (token == null) {//第一次add的情況下token怎么會有值呢,為什么呢
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
Slog.w(TAG_WM, "Attempted to add application window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_INPUT_METHOD) {
Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_VOICE_INTERACTION) {
Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_WALLPAPER) {
Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_DREAM) {
Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_QS_DIALOG) {
Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_ACCESSIBILITY_OVERLAY) {
Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_TOAST) {
if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
attachedWindow)) {
Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
//此處什么邏輯會走到這個地方呢
token = new WindowToken(this, attrs.token, -1, false);
addToken = true;
}
//處理應用程式視窗邏輯
else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
atoken = token.appWindowToken;
if (atoken == null) {
Slog.w(TAG_WM, "Attempted to add window with non-application token "
+ token + ". Aborting.");
return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
} else if (atoken.removed) {
Slog.w(TAG_WM, "Attempted to add window with exiting application token "
+ token + ". Aborting.");
return WindowManagerGlobal.ADD_APP_EXITING;
}
if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
// No need for this guy!
if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v(
TAG_WM, "**** NO NEED TO START: " + attrs.getTitle());
return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
}
} //輸入法視窗
else if (type == TYPE_INPUT_METHOD) {
if (token.windowType != TYPE_INPUT_METHOD) {
Slog.w(TAG_WM, "Attempted to add input method window with bad token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (type == TYPE_VOICE_INTERACTION) {
if (token.windowType != TYPE_VOICE_INTERACTION) {
Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}//壁紙視窗
else if (type == TYPE_WALLPAPER) {
if (token.windowType != TYPE_WALLPAPER) {
Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}//Dream視窗
else if (type == TYPE_DREAM) {
if (token.windowType != TYPE_DREAM) {
Slog.w(TAG_WM, "Attempted to add Dream window with bad token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (type == TYPE_ACCESSIBILITY_OVERLAY) {
if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (type == TYPE_TOAST) {
addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
callingUid, attachedWindow);
if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
Slog.w(TAG_WM, "Attempted to add a toast window with bad token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (type == TYPE_QS_DIALOG) {
if (token.windowType != TYPE_QS_DIALOG) {
Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
} else if (token.appWindowToken != null) {//系統視窗
Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type);
// It is not valid to use an app token with other system types; we will
// instead make a new token for it (as if null had been passed in for the token).
attrs.token = null;
token = new WindowToken(this, null, -1, false);
addToken = true;
}
//為Activity視窗(注意這里只是因為分析的是Activity視窗的添加而已)創建WindowState物件
WindowState win = new WindowState(this, session, client, token,
attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
res = WindowManagerGlobal.ADD_OKAY;
if (excludeWindowTypeFromTapOutTask(type)) {
displayContent.mTapExcludedWindows.add(win);
}
origId = Binder.clearCallingIdentity();
//以鍵值對<IWindow.Proxy/Token,WindowToken>形式保存到mTokenMap表中
if (addToken) {
mTokenMap.put(attrs.token, token);
}
win.attach();//詳見章節3.2
//以鍵值對<IWindow的代理物件,WindowState>形式保存到mWindowMap表中
mWindowMap.put(client.asBinder(), win);
...
boolean imMayMove = true;
if (type == TYPE_INPUT_METHOD) {
...
} else if (type == TYPE_INPUT_METHOD_DIALOG) {
...
addWindowToListInOrderLocked(win, true);
} else {
addWindowToListInOrderLocked(win, true);//將WindowState添加到關聯的AppWindowToken中,詳見章節3.3
...
}
...
return res;
}
在我們正式開始上述的原始碼決議前,讀者一定要讀Token要有所了解,如果還不是很清楚強烈建議參見博客普法Android的Token前世今生以及在APP,AMS,WMS之間傳遞,
老規矩,我們先來看看該方法入參的幾個重要引數的含義,如下:
| 引數型別 | 引數名稱 | 引數功能和含義 |
|---|---|---|
| Session | session | 為前面創建的Sesseion物件實體(Binder代理端經過跨行程傳輸后轉變成Binder物體端) |
| WindowManager.LayoutParams attrs | attrs | 這里的attrs為Window對應的默認WindowManager.LayoutParams 實體物件mWindowAttributes經過一系列賦值之后的物件, 其中它的最最重要的兩個引數為token和type |
| IWindow | client | 這里的client為IWindow的代理物件,用于WMS和Activity進行通信 |
前面的引數我們簡單的總結了一下,這個時候得開始真正的表演了(下面的陳述可能有點拗口,不過沒有關系多理解多分析就好了,我們一定要引數理解清楚)!
-
從我們前面的分析可知知道當應用程式行程添加一個DecorView到視窗管理器時,會為當前添加的視窗創建ViewRootImpl物件,同時構造了一個W本地Binder物件,無論是視窗視圖物件DecorView還是ViewRootImpl物件,都只是存在于應用程式行程中,在添加視窗程序中僅僅將該視窗的W物件傳遞給WMS服務,經過Binder傳輸后,到達WMS服務端行程后變為IWindow.Proxy代理物件,因此該函式的引數client的型別為IWindow.Proxy,這個就是我們client引數的終極由來
-
引數attrs的型別為WindowManager.LayoutParams,這里的attrs為Window對應的默認WindowManager.LayoutParams實體物件mWindowAttributes經過一系列賦值之后的物件,其中它的最最重要的兩個引數為token和type,由于WindowManager.LayoutParams實作了Parcelable介面,因此WindowManager.LayoutParams物件可以跨行程傳輸,WMS服務的addWindow函式中的attrs引數就是應用程式行程發送過來的視窗布局引數,
-
在WindowManagerGlobal的addView中我們呼叫了Window方法的adjustLayoutParamsForSubWindow()方法為視窗布局引數設定了相應的token,如果是應用程式視窗,則布局引數的token設為Token傳遞到APP端的代理物件,如果是應用程式子視窗則設定為token設為W本地Binder物件,由于應用程式和WMS分屬于兩個不同的行程空間,因此經過Binder傳輸后,布局引數的令牌attrs.token就轉變為IWindow.Proxy或者Token,
注意這里我們是以Android應用程式視窗為說明的
關于引數的入參的分析我真的盡力了,如果讀者還是沒有弄清楚的話,我只能說臣妾做不到啊!了解了引數的含義,該方法也就比較好理解了,其處理的流程如下:
-
首先判斷視窗是否添加過以及是否合法,如果已經添加則直接回傳
-
然后根據attrs.token從mWindowMap中取出應用程式視窗在WMS服務中的描述符WindowToken
-
我們將上述得到的引數聚合在一起然后在構造一個WindowState物件,并將添加的視窗資訊記錄到mTokenMap和mWindowMap哈希表中

-
接著呼叫WindowState的attach()來進一步完成視窗添加
-
最后將上述的WindowState添加到關聯的WindowToken中的串列中,這個地方需要重點分析下
3.3 WindowState繼續處理視窗的添加
在正式開始WindowState繼續處理視窗的添加前,我們先來看看WindowState的構造方法如下:
//[WindowState.java]
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, final DisplayContent displayContent) {
mService = service;//WMS的參考
mSession = s;//和APP端對應的Session端
mClient = c;//APP端W對應的Binder代理端
mAppOp = appOp;
mToken = token;//此處的Token為AppWindowToken,Token從AMS傳遞到WMS程序中創建的
...
WindowState appWin = this;
while (appWin.isChildWindow()) {
appWin = appWin.mAttachedWindow;
}
WindowToken appToken = appWin.mToken;
mRootToken = appToken;
mAppToken = appToken.appWindowToken;
...
}
它的構造方法主要是將傳入的初始化成員變數!我們接著繼續分析它的attach方法邏輯
//[WindowState.java]
void attach() {
if (WindowManagerService.localLOGV) Slog.v(
TAG, "Attaching " + this + " token=" + mToken
+ ", list=" + mToken.windows);
mSession.windowAddedLocked();
}
無話,接著往下看
//[WindowState.java]
void windowAddedLocked() {
if (mSurfaceSession == null) {
if (WindowManagerService.localLOGV) Slog.v(
TAG_WM, "First window added to " + this + ", creating SurfaceSession");
mSurfaceSession = new SurfaceSession();
if (SHOW_TRANSACTIONS) Slog.i(
TAG_WM, " NEW SURFACE SESSION " + mSurfaceSession);
mService.mSessions.add(this);
if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
mService.dispatchNewAnimatorScaleLocked(this);
}
}
mNumWindow++;//記錄對應的某個應用程式添加的視窗數量
}
3.4 WMS呼叫addWindowToListInOrderLocked方法處理WindowState
//[WindowManagerService.java]
private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) {
if (DEBUG_FOCUS) Slog.d(TAG_WM, "addWindowToListInOrderLocked: win=" + win +
" Callers=" + Debug.getCallers(4));
if (win.mAttachedWindow == null) {
final WindowToken token = win.mToken;
int tokenWindowsPos = 0;
if (token.appWindowToken != null) {
tokenWindowsPos = addAppWindowToListLocked(win);
} else {
addFreeWindowToListLocked(win);
}
if (addToToken) {
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + token);
token.windows.add(tokenWindowsPos, win);
}
} else {
addAttachedWindowToListLocked(win, addToToken);
}
final AppWindowToken appToken = win.mAppToken;
if (appToken != null) {
if (addToToken) {
appToken.addWindow(win);//AppWindowToken進行管理
}
}
}
在創建完視窗對應的WindowState之后,會接著addWindowToListInOrderLocked將WindowState放入AppWindowToken進行管理,(這個管理方式有點類似AMS中對Activity的堆疊管理),經過上述處理以后AppWindowToken和WindowState有如下的對應關系,

對于上述關系我們也可以通過命令dumpsys來證明:
XXX:/ # dumpsys window displays
WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)
Display: mDisplayId=0
init=720x1280 320dpi cur=720x1280 app=720x1184 rng=720x672-1184x1136
deferred=false layoutNeeded=false
Application tokens in top down Z order:
mStackId=1
mDeferDetach=false
mFullscreen=true
mBounds=[0,0][720,1280]
taskId=120
mFullscreen=true
mBounds=[0,0][720,1280]
mdr=false
appTokens=[AppWindowToken{7e5b066 token=Token{977da8 ActivityRecord{6fd71cb u0 com.example.window/.MainActivity t120}}}]
mTempInsetBounds=[0,0][0,0]
Activity #0 AppWindowToken{7e5b066 token=Token{977da8 ActivityRecord{6fd71cb u0 com.example.window/.MainActivity t120}}}
windows=[Window{9eb8f33 u0 com.example.window/com.example.window.MainActivity}, Window{cbaaa6d u0 hello}]
windowType=2 hidden=false hasVisible=true
app=true voiceInteraction=false
allAppWindows=[Window{cbaaa6d u0 hello}, Window{9eb8f33 u0 com.example.window/com.example.window.MainActivity}]
task={taskId=120 appTokens=[AppWindowToken{7e5b066 token=Token{977da8 ActivityRecord{6fd71cb u0 com.example.window/.MainActivity t120}}}] mdr=false}
appFullscreen=true requestedOrientation=-1
hiddenRequested=false clientHidden=false reportedDrawn=true reportedVisible=true
numInterestingWindows=2 numDrawnWindows=2 inPendingTransaction=false allDrawn=true (animator=true)
startingData=null removed=false firstWindowDrawn=true mIsExiting=false
四.小結
??至此Android應用程式視窗設計之視窗的添加就到此告一段落了,這里的告一段落也是對于博主來說而已,對于讀者來說還有很長的路要走,因為視窗添加的概念中涉及了太多的知識點和概念了,那么怎么把這些抽象的概念具體化呢,其實Android為我們提供了不錯的命令工具,dumpsys window,這個命令能夠很好的列印出我們視窗前面所說的一些概念的資訊,這個命令值得大家去使用,我們簡單看下:
WINDOW MANAGER LAST ANR (dumpsys window lastanr)
<no ANR has occurred since boot>
WINDOW MANAGER POLICY STATE (dumpsys window policy)
mSafeMode=false mSystemReady=true mSystemBooted=true
WINDOW MANAGER ANIMATOR STATE (dumpsys window animator)
DisplayContentsAnimator #0:
Window #0: WindowStateAnimator{2fa13aa com.android.systemui.ImageWallpaper}
WINDOW MANAGER SESSIONS (dumpsys window sessions)
Session Session{7bf2bd 5690:u0a10023}:
WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)
Display: mDisplayId=0
init=720x1280 320dpi cur=720x1280 app=720x1184 rng=720x672-1184x1136
deferred=false layoutNeeded=false
WINDOW MANAGER TOKENS (dumpsys window tokens)
All tokens:
AppWindowToken{4513043 token=Token{65e80fd ActivityRecord{cfdc954 u0 com.android.launcher3/.Launcher t118}}}
WINDOW MANAGER WINDOWS (dumpsys window windows)
Window #9 Window{4b8f2da u0 NavigationBar}:
mDisplayId=0 stackId=0 mSession=Session{abf850b 4764:u0a10014} mClient=android.os.BinderProxy@be79485
好了這里限于篇幅就不一一展開了,總之經過前面的一系列分析我們應該知道了:
- 應用程式端的每個視窗對應的ViewRootImpl對持有一個W物件,而WMS端持有它的代理端用于和APP的資訊回呼
- 然后每個應用程式行程都持有一個與WMS服務會話通道IWindowSession,系統中創建的所有IWindowSession都被記錄到WMS服務的mSessions成員變數中,這樣WMS就可以知道自己正在處理那些應用程式的請求,到此我們來梳理一下在WMS服務端都創建了那些物件:
- 然后為每個視窗在我們是WMS端創建一個WindowState物件,該物件是應用程式視窗在WMS服務端的描述符,有點類似于Activity在AMS端的描述符ActivityRecord

四.寫在最后
??到這里本篇博客Android應用程式視窗設計之視窗的添加真的就宣告結束了,相信分析至此讀者應該對Android應用程式視窗的添加有了一定的理解了,但是想要說深入那還很遠,很遠,但是沒有關系萬事開頭難嗎,只有我們有信心放肆的干,我想關于Android應用程式視窗設計的整體流程一定會被我們攻破的,在接下來的博客中我們會接著分析Android系統視窗和子視窗的添加流程,歡迎讀者繼續關注!好了,青山不改綠水長流先到這里了,如果本博客對你有所幫助,麻煩關注或者點個贊,如果覺得很爛也可以踩一腳!謝謝各位了!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/233900.html
標籤:其他
