從上一篇文章中,咱們瞭解到了Window的體系機制,也知道了window分爲三種類型,分別是應用窗口(Application Window)、子窗口(Sub Window)、系統窗口(System Window),本文經過源碼以Activity爲例講解一下應用窗口的添加過程,若是沒看過上一篇文章建議先看,對於不一樣類型的窗口的添加,它們在WindowManager中的處理過程會有一點不同,可是對於在WMS的處理過程當中,基本上都是同樣的。因此本文深刻講解一下Activity窗口的添加過程,知道了這個過程,對於其餘類型的窗口添加也就能觸類旁通了。java
本文基於Android8.0, 相關源碼位置以下:
frameworks/base/core/java/android/view/*.java(*表明Window, WindowManager,WindowManagerImpl,WindowManagerGlobal, ViewRootImpl)
frameworks/base/core/java/android/app/*.java(*表明Activity,ActivityThread)
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
frameworks/base/services/core/java/com/android/server/wm/Session.java
複製代碼
熟悉Activity的啓動流程的都知道(不熟悉的能夠查看這篇文章Activity的啓動流程), Window的建立過程是在activity的attach方法中,它在調用Activity的onCreate方法前完成一些重要數據的初始化,以下:android
//Activity.java
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) {
//...
//一、關注這裏,建立PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
//下面都是設置window的一些屬性,如回調、軟鍵盤模式
mWindow.setWindowControllerCallback(this);
//這個設置Window的Callback回調
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
//...
//二、關注這裏,把Window與WindowManager進行關聯
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
//把WindowManager與Activity進行關聯
mWindowManager = mWindow.getWindowManager();
//...
}
複製代碼
在attach裏。跟Window無關的我都省略掉了,咱們看到attach方法裏,在註釋1中,首先new 了一個PhoneWindow賦值mWinow,mWindow是Window類型,它是一個抽象類,因此從這裏能夠看出Activity的Window的具體實現類是PhoneWindow,接下來,給mWindow設置回調,傳入的參數是this,說明Activity實現了這些回調接口,這樣當Window接收到外界的狀態變化或輸入事件時就會回調Activity的方法,其中咱們比較熟悉的接口回調是Window的Callback接口,它裏面有咱們熟悉的回調方法如:dispatchTouchEvent()、onWindowFocusChanged()、onAttachedToWindow()和onDetachedFromWindow()。bash
接着咱們來看註釋2,這裏經過Window的setWindowManager方法把WanagerManger與Window進行關聯,而後經過Window的getWindowManager()把WanagerManger與Activity進行關聯。session
咱們知道Window的添加、更新和刪除都是要經過WanagerManager的,接下來咱們看看Window與WanagerManager是如何關聯的,從上面知道該過程是在Window的setWindowManager方法中,以下:app
//Window.java
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
//token是Window的重要屬性之一,是IBinder類型,它這裏等於Activity中的mToken
mAppToken = appToken;
//應用名
mAppName = appName;
//是否硬件加速
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
//得到系統級服務WMS在本地進程的代理
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//一、關注這裏,調用WindowManagerImpl的createLocalWindowManager方法,建立WindowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
複製代碼
在setWindowManager方法中,是有關window的一些屬性的賦值,其中mAppToken是Activity中的token,它在Activity啓動的過程當中從AMS中傳遞過來的,這裏你只要記住Activity應用窗口的token值是Activity中的token值,接下來若是wm爲空就獲取WMS並轉成WindowManager賦值給wm,wm是WindowManager,它是一個接口,它的具體實現類是WindowManagerImpl,因此接下來的註釋1中wm轉成WindowManagerImpl,並調用WindowManagerImpl的createLocalWindowManager方法,咱們來看看WindowManagerImpl的createLocalWindowManager方法,以下:ide
//WindowManagerImpl.java
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
複製代碼
這個方法很簡單,只是簡單的返回一個WindowManagerImpl對象,注意它傳入了一個parentWindow參數,它是Window類型,說明此時構建的WindowManagerImpl是與具體的Window關聯的,至此,在java層上Window就已經與WindowManager創建起聯繫。佈局
從上一篇文章咱們知道,View是依附在Window上的,在Activity的啓動過程當中的attach方法裏已經完成了Activity的Window的建立和與WindowManager的關聯,那麼Activity的視圖即View是在哪裏建立的呢?答案是在咱們熟悉的setContentView方法中,咱們先來看一張圖:post
如圖所示每個Activity都有一個頂級View叫作DecorView,通常狀況下它會包含一個豎直方向的LinearLayout,在這個LinearLayout中包含兩部分(具體狀況與Android的版本與主題有關),上面是標題欄,下面是內容佈局,內容佈局實際上是一個FrameLayout,咱們平時setContentView指定的佈局實際上是set到了這個FrameLayout中,因此這個方法叫setContentView也是也是很貼和實際的,由於FrameLayout的id就是android.R.id.content,理解了這些知識後,咱們來看Activity中的setContentView方法,以下:動畫
//Activity.java
public void setContentView(@LayoutRes int layoutResID) {
//一、關注這裏,其實調用的是PhoneWindow的setContentView,setContentView裏面會加載內容佈局並添加進DecorView中
getWindow().setContentView(layoutResID);
//若是Activity主題是帶ActionBar的話,這裏面就會建立ActionBar並添加進DecorView中
initWindowDecorActionBar();
}
複製代碼
咱們看註釋1,前面已經講過Activity的Window的建立,因此這裏的getWindow其實返回的是Window,而Window的實現類是PhoneWindow,因此這裏調用的是PhoneWindow的setContentView,並傳入了咱們的內容佈局id,PhoneWindow的setContentView方法的相應源碼以下:ui
//PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
//1,根據mContentParent是否爲空作出不一樣動做,mContentParent就是上面所講的id爲android.R.id.content的佈局,用來set咱們id爲layoutResID的內容佈局
if (mContentParent == null) {
//1.一、mContentParent爲空,建立DecorView,並加載mContentParent
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//1.二、mContentParent不爲空,而且沒有轉場動畫,就把mContentParent中的View視圖清空,下面會從新加載
mContentParent.removeAllViews();
}
//二、根據是否有轉場動畫,作出不一樣的動做
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//2.一、有轉場動畫,建立Scene完成轉場動畫
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext());
transitionTo(newScene);
} else {
//2.二、沒有轉場動畫,直接把咱們的layoutResID的佈局加載進mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//觸發Activity的onContentChanged方法, 由於Activity實現了這些回調接口
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
複製代碼
從註釋中能夠看出這個方法若是忽略轉場動畫的處理的話,能夠分爲兩部分,第一部分是註釋1.1的DecorView的建立和加載mContentParent,第二部分是註釋2.2的把咱們的layoutResID的佈局加載進mContentParent,其中重點是第一部分,下面咱們來分析PhoneWindow的setContentView方法的第一部分。
咱們來看PhoneWindow的installDecor方法,以下:
//PhoneWindow.java
private void installDecor() {
//...
//一、根據mDecor是否爲空,作出不一樣動做,mDecor就是DecorView,它是繼承自FrameLayout
if (mDecor == null) {
//1.一、mDecor爲空,就建立mDecor
mDecor = generateDecor(-1);
//...
} else {
//1.二、、mDecor不爲空,不用重複建立,把Window設置給DecorView
mDecor.setWindow(this);
}
if (mContentParent == null) {
//二、若是mContentParent爲空,就加載mContentParent
mContentParent = generateLayout(mDecor);
//...
}
}
複製代碼
installDecor()有一百多行代碼,可是重點就是上面幾句,由於這裏咱們是第一次建立mDecor,因此mDecor就爲空,那麼上面就分爲兩部分,第一部分是註釋1.1的建立mDecor,第二部分是註釋2的加載加載mContentParent,咱們先看installDecor方法的第一部分。
PhoneWindow的generateDecor()方法以下:
//PhoneWindow.java
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
//...
} else {
context = getContext();
}
//一、關注這裏,new了一個DecorView
return new DecorView(context, featureId, this, getAttributes());
}
複製代碼
能夠看到generateDecor就是簡單的建立了一個DecorView並返回,其中this是Window實例,DecorView的構造方法中會把Window設置給DecorView中的mWindow。咱們看一下DecorView是什麼,以下:
//DecorView.java
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
//...
}
複製代碼
能夠看到DecorView就是一個FrameLayout。
咱們回到installDecor方法中,接下來咱們來看installDecor方法的第二部分。
PhoneWindow的generateLayout()方法以下:
//PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
//這裏獲取到當前的Activity的主題theme的屬性,下面忽略的,都是根據theme的屬性設置Activity的Window
TypedArray a = getWindowStyle();
//...
//這個layoutResource是一個佈局id
int layoutResource;
//得到theme的features
int features = getLocalFeatures();
//下面根據features得到不一樣的layoutResource
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
//一、咱們選取這個 R.layout.screen_simple 佈局做爲例子看一下
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
//二、將上面獲取到的layoutResource對應的佈局加載進DecorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//
//三、由於layoutResource對應的佈局已經加載進DecorView中了,因此這裏能夠經過findViewById獲取android.R.id.content的佈局
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//...
mDecor.finishChanging();
//返回id爲android.R.id.content的佈局,賦值給mContentParent
return contentParent;
}
複製代碼
generateLayout()這個方法很是長,可是它裏面的邏輯很簡單,這個方法的主要做用是根據當前的Activity的theme的屬性設置Activity的Window,並把根據features獲取到的佈局加載進傳進來的DecorView,並從DecorView中獲取android.R.id.content的佈局返回給mContentParent,咱們只要看懂註釋1~3就清楚了。
首先咱們看註釋1,由於if...else...的語句很是多,因此我就選了最後一個else語句的layoutResource對應的佈局文件講解,它的位置在:/frameworks/base/core/res/res/layout/screen_simple.xml,以下:
<!-- 還記得上面那張圖嗎,DecorView通常狀況下它會包含一個豎直方向的LinearLayout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical">
<!-- ViewStub是一個按需加載的View,它在用到時纔會加載,並且只能加載一次,這裏它的layout指向的是一個ActionBar的佈局文件,因此這裏把ViewStub看做一個ActionBar就行 -->
<ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" />
<!-- 這個就是id爲android.R.id.content得佈局,用來放置咱們平時setContentView時set得內存佈局 -->
<FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
複製代碼
screen_simple.xml文件就是一個佈局文件,你們把這個佈局對應一下上面得那張圖,就會有一種恍然大悟得感受了,因此咱們緊接着來看註釋2,它就是把上面這個screen_simple.xml佈局文件加載進DecorView中。
咱們再看註釋3,ID_ANDROID_CONTENT就是android.R.id.content的常量,看一下findViewById方法的源碼,以下:
//PhoneWindow.java
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
//getDecorView()就是得到到Window中的DecorView
return getDecorView().findViewById(id);
}
複製代碼
能夠看到findViewById方法中獲取到DecorView,而後調用DecorView的findViewById方法,由於在註釋2中咱們已經把layoutResource對應的佈局加載進DecorView中了,因此這時就獲取到android.R.id.content的佈局。在generateLayout方法的最後,把android.R.id.content的佈局返回給mContentParent。
咱們再回到installDecor方法中,至此咱們已經建立好DecorView,也經過DecorView獲取到mContentParent, 即android.R.id.content的佈局。
咱們來分析PhoneWindow的setContentView方法的第二部分。
layoutResID就是咱們setContentView傳進來的內容佈局id,因此這裏就把內容佈局加載進mContentParent中了。至此Window的setContentView分析完畢。
這個過程以下圖:
咱們回到Activity的setContentView方法,其實到這裏Activity的視圖,也能夠是說Activity的Window的視圖DecorView就建立好了,接下來就是把這個DecorView顯示到屏幕上。
熟悉Activity的啓動流程的都知道,Activity會在handleResumeActivity方法中把DecorView顯示出來,而添加一個Winow是經過WindowManager的addView方法實現的,可是Window只是View的載體,並非真實存在的,因此addView其實就是添加一個View,這個View是依附在Window上,而且這個View是 View Hierarchy 最頂端的根 View,而Activity的的頂級View是DecorView, 因此添加Activity的Window就是添加DecorView。咱們來看一下handleResumeActivity方法,以下:
//ActivityThread.java
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
//ActivityClientRecord裏面保存了Activity的信息
ActivityClientRecord r = mActivities.get(token);
//...
//這個方法裏面最終會回調Activity的onResume方法
r = performResumeActivity(token, clearHide, reason);
//因此下面都是在執行onResume方法後的行爲
if (r != null) {
//獲得Activity
final Activity a = r.activity;
//...
//面if(r.window == null && !a.mFinished && willBeVisible){}分支裏面的邏輯主要是把Activity的Window的DecorView添加到WMS中
if (r.window == null && !a.mFinished && willBeVisible) {
//獲取前面Activit建立的Window
r.window = r.activity.getWindow();
//獲取前面Window建立的DecorView
View decor = r.window.getDecorView();
//先把DecorView設爲不可見
decor.setVisibility(View.INVISIBLE);
//Activity關聯的WindowManager
ViewManager wm = a.getWindowManager();
//下面設置Window的佈局參數
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//窗口的類型是TYPE_BASE_APPLICATION,應用類型窗口
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
//...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//一、關注這裏,調用WindowManager的addView方法
wm.addView(decor, l);
} else {
//...
}
}
}else if (!willBeVisible) {
//...
}
//...
//面if(r.window == null && !a.mFinished && willBeVisible){}分支裏面的邏輯主要是把DecorView顯示出來
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
//...
if (r.activity.mVisibleFromClient) {
//二、關注這裏,上面已經把Window添加到WMS中了,因此裏面會把DecorView顯示出來, 見下面Activity.java
r.activity.makeVisible();
}
}
//...
}
//...
}
//Activity.java
void makeVisible() {
//...
//把DecorView設爲可見
mDecor.setVisibility(View.VISIBLE);
}
複製代碼
上面的註釋已經寫的很清楚了,重點就是一句話:獲取Activity的Window中的DecorView並調用WindowManager的addView方法添加DecorView,而後把DecorView設置爲可見。到這裏視圖的添加已經轉移到WindowManager中,閱讀過上一篇文章的知道,WindowManager的實現類是WindowManagerImp,WindowManagerImp會把大部分操做轉發給WindowManagerGlobal。
因此咱們直接看方法WindowManagerGlobal的addView()就行,以下:
//WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
//...
//獲取Window的LayoutParams
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//這裏的parentWindow不爲空,由於從上面的Window與WanagerManager的關聯可知,會調用createLocalWindowManager(this)來建立一個WanagerManagerImpl,這個this表明的PhoneWindow實例會傳進WanagerManagerImpl構造中賦值給mParentWindow
//一、調整窗口布局參數
if (parentWindow != null) {
//若是有設置父窗口,會經過adjustLayoutParamsForSubWindow()來調整params
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
//...
}
ViewRootImpl root;
synchronized (mLock) {
//...
//二、構建ViewRootimpl
root = new ViewRootImpl(view.getContext(), display);
//三、把View、ViewRootimpl、LayoutParams保存
//把上面調整好的params設置給待添加的View
view.setLayoutParams(wparams);
//把待添加的View添加到View列表中
mViews.add(view);
//把ViewRootimpl對象root添加到ViewRootimpl列表中
mRoots.add(root);
//把params添加到params列表中
mParams.add(wparams);
try {
//四、調用ViewRootImpl的setView將View顯示到手機窗口上
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
//...
}
}
}
複製代碼
上述方法主要分爲4個部分,咱們先來看WindowManagerGlobal的addView傳進來的4個參數,其中view、params和display三者是必不可少的,view就表明待添加的View,這裏是DecorView,params就表明窗口布局參數,diaplay表明的是表示要輸出的顯示設備,而parentWindow表示父窗口,這裏的父窗口並不必定是真正意義上的父窗口,有可能就是描述一個窗口的對象自己。在上述分析Activity的 WindowManager建立時就提到parentWindow就是PhoneWindow自己。
接下來咱們來看這個方法,這個方法被分爲4部分,其中第一部分是註釋1,重點是Window的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) {
//能夠看到子窗口的token爲頂級View的WindowToken
wp.token = decor.getWindowToken();
}
}
//...
} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW && wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {//若是它是系統窗口
//系統窗口沒有爲token賦值,由於系統窗口的生命週期不依賴於app,當app退出了,系統窗口不會受到影響,它仍是能顯示和接收外界的輸入事件
//...
} else {//若是它是應用窗口
if (wp.token == null) {
//能夠看到應用窗口的token爲Activity的mAppToken
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
//...
}
複製代碼
這個方法主要是爲Window的token賦值,若是是應用窗口且wp.token==null,就會給它賦值mAppToken,而這個mAppToken就是咱們上面在Activity的attach()方法中傳入的mToken,而系統窗口的token爲null,緣由註釋中說了,咱們再分析子窗口的token,接上面的decor.getWindowToken(),該方法以下:
//View.java
public IBinder getWindowToken() {
return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
}
複製代碼
能夠看到子窗口的token就是View中mAttachInfo的mWindowToken,那麼mAttachInfo是什麼?它在哪裏被賦值?咱們先留一個疑問。
咱們回到addView()方法繼續看註釋2,註釋2構建了一個ViewRootimpl,WindowManagerGlobal會爲每個待添加的View建立一個ViewRootImpl,咱們看ViewRootImpl的構造方法,以下:
//ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mContext = context;
//一、記住這個mWindowSession,待會用到
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
//...
//二、建立了一個W對象,繼承自IWindow.Stub,是一個IBinder類型,用來接收WMS的通知
mWindow = new W(this);
//...
//三、建立了一個mAttachInfo,這個mAttachInfo就是上面View中mAttachInfo,它在這裏被建立,見下面View.AttachInfo的構造方法
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
//...
}
//View.java
AttachInfo(IWindowSession session, IWindow window, Display display, ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
mSession = session;
mWindow = window;
//mWindowToken本質就是ViewRootImpl中的W類,只是調用asBinder轉化了一下
mWindowToken = window.asBinder();
mDisplay = display;
mViewRootImpl = viewRootImpl;
//...
}
複製代碼
ViewRootImpl的構造方法中,關鍵的就是上面三個註釋,註釋1下面會解釋,註釋2建立了一個W類對象,它是一個IBinder類型,它在後面會經過Binder IPC傳送到WMS中,WMS就是經過這個W類對象和Activity所在進程交互,註釋3建立了一個AttachInfo類對象,ViewRootImpl爲每個待添加的View建立一個AttachInfo類對象mAttachInfo,當這個待添加的View與ViewRootImpl創建聯繫(mView被賦值)後,ViewRootImpl就會調用performTraversal()方法遍歷這顆View Hierarchy 把其mAttachInfo賦值給這顆View Hierarchy 中的每個View的mAttachInfo,因此上面的**decor.getWindowToken()**中的mAttachInfo就不爲空,這樣子窗口的token就是mAttachInfo中的mWindowToken,從AttachInfo構造能夠看出,傳入的W類經過asBinder轉化了一下賦值給mWindowToken,因此如今能夠得出結論:子窗口的token就是ViewRootImpl中的W類。
咱們回到addView()方法繼續看註釋3,第三部分就是把待添加的View、新建立ViewRootimpl、待添加的View的LayoutParams分別保存到3個列表,這三個列表在WindowManagerGlobal中,這三個列表的含義以下:
//WindowManagerGlobal.java
public final class WindowManagerGlobal {
private final ArrayList<View> mViews = new ArrayList<View>();//mViews存儲的是全部Window所對應的頂級View(即View Hierarchy最頂端的View)
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();//mRoots存儲着全部Window所對應的ViewRootImpl
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();//mParams存儲着全部Window所對應的佈局參數
//...
}
複製代碼
咱們回到addView()方法繼續看註釋4,註釋4就是調用ViewRootImpl的setView方法,它裏面會請求View Hierarchy的繪製,並請求WMS顯示待添加的View,咱們看一下該方法,以下:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//ViewRootImpl與待添加的View創建聯繫
mView = view;
//...
//接收WMS添加後的返回結果
int res;
//一、請求繪製View Hierarchy
requestLayout();
//...
try {
//...
//二、向經過mWindowSession向WMS發起顯示當前Window的請求
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
//...
}
//下面這些異常都是因爲添加Window錯誤而拋出
if (res < WindowManagerGlobal.ADD_OKAY) {
//...
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
//...
}
//...
}
}
}
複製代碼
在setView方法中,咱們先看註釋1,在向WMS發起將View顯示到手機窗口上前,先調用requestLayout繪製整顆View Hierarchy,這個方法裏面會經過Choreographer的postCallback方法註冊對應的繪製回調(CALLBACK_TRAVERSAL),等待vsync信號,而後會觸發整個View樹的繪製操做,也就是performTraversal()方法的執行。咱們來看註釋2,到這裏Activity的Window的添加就交給了mWindowSession,它是一個IWindowSession類型,IWindowSession是一個AIDL接口文件,須要編譯後才生成IWindowSession.java接口,mWindowSession是在上面的ViewRootImpl的構造中被賦值的:mWindowSession = WindowManagerGlobal.getWindowSession();,關於這部分的已經在上一篇文章講解過了,因此註釋2其實最終調用的Session的addToDisplay()方法,在addToDisplay()中返回了WMS的addWindow()的返回結果,因此從這裏開始添加Window的過程轉移到WMS進程中去。
咱們就簡單的過一遍WMS的addWindow()方法,以下:
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) {
int[] appOp = new int[1];
//若是窗口時系統窗口,還要進行權限檢查
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
//...
final int type = attrs.type;
synchronized(mWindowMap) {
//省略的是檢查Display顯示信息,
//...
//若是是子窗口
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
//經過windowForClientLocked()方法還要檢查其父窗口是否存在
parentWindow = windowForClientLocked(null, attrs.token, false);
//若是父窗口不存在,返回錯誤
if (parentWindow == null) {
//...
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
//若是父窗口仍是子窗口,返回錯誤
if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
&& parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
//...
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
}
//...
//檢查token
AppWindowToken atoken = null;
////是否有父窗口
final boolean hasParent = parentWindow != null;
//若是它有父窗口,就使用父窗口的token,若是沒有,就是使用本身的token
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
//若是它有父窗口,就使用父窗口的type,若是沒有,就是使用本身的type
final int rootType = hasParent ? parentWindow.mAttrs.type : type;
if (token == null) {
if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {//若是是應用窗口,可是它的token爲空,返回錯誤
//...
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
//...
} else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {//若是是應用窗口,可是它的token不是mAppToken(mApptoken是從AMS傳過來的),返回錯誤
atoken = token.asAppWindowToken();
if (atoken == null) {
//...
return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
} else if (atoken.removed) {
//...
return WindowManagerGlobal.ADD_APP_EXITING;
}
}
//這裏省略的是,一些系統窗口的token 不能爲空,而且經過token檢索到的WindowToken的類型不能是其自己對應的類型
//...
else if (token.asAppWindowToken() != null) { //某些系統窗口的token應該爲空,可是卻不爲空,因此這裏把token清空
attrs.token = null;
token = new WindowToken(this, client.asBinder(), type, false, displayContent,
session.mCanAddInternalSystemWindow);
}
//...
//通過一系列的檢查後,會建立一個WindowState
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
//...
//走到這裏證實沒有任何錯誤發生,res = ADD_OKAY
res = WindowManagerGlobal.ADD_OKAY;
//...
//WindowState的attach方法建立了一個SurfaceSession對象用於與SurfaceFlinger服務通訊
win.attach();
//client就是Activity進程那邊傳過來的ViewRootImpl中的W類,這裏用asBinder轉化了一下,因此這裏以W類爲Key,WindowState爲Value創建映射存放進mWindowMap中,它是一個WindowHashMap類型
mWindowMap.put(client.asBinder(), win);
}
}
複製代碼
這個方法很長,可是裏面的邏輯仍是頗有規律,建議對照着註釋跟源碼看一遍,這裏總結一下這個方法的過程:
這個添加過程以下圖:
以上就是Activity的Window的添加過程,咱們發現添加一個Window最重要的是View、type和token,至於其餘類型窗口的添加類似的,一圖總結本文,以下:
從圖中能夠看到,添加一個Window,會涉及到兩個進程的交互,一個是Activity所在的應用進程,一個是WMS所在的系統服務進程,因此綠色的那部分就表明着IPC,ViewRootImpl經過WindonManagerGlobal的靜態變量sWindowSession負責與WMS通訊,它是Session類型,在ViewRootImpl構造中被賦值,WMS中的每一個Window的WindowState的mClient負責與Activity所在的應用進程通訊,它是W類型,在建立WindowState構造中被賦值,在Activity所在的應用進程的WindonManagerGlobal中會爲每個添加的Window中的View建立一個ViewRootImpl,因此多個Window就對應多個ViewRootImpl,而在WMS中,Window對應着一個View,它會爲每個Window建立一個WindowState以維護Window的狀態,因此多個Window就多個WindowState。
從應用窗口的添加過程當中,對Window的機制也有了一些瞭解,之後若是遇到有關於Window的添加的異常也懂得去哪裏找緣由。
參考資料: