我們在開發(fā)android程序城看,如果要寫一個(gè)界面摄杂,很自然就想到幾個(gè)步驟:創(chuàng)建一個(gè)Activity類杈湾,寫一個(gè)xml布局矢门,在回調(diào)函數(shù)onCreate()通過setContentView()把xml布局文件設(shè)置進(jìn)Activity膝迎,然后一個(gè)簡單的界面就出來了粥帚,相當(dāng)簡單!可是限次,在這個(gè)過程中android系統(tǒng)到底經(jīng)歷了什么呢芒涡,作為一名應(yīng)用開發(fā)工程師,我們大部分只關(guān)心上層的實(shí)現(xiàn)卖漫,并不清楚android系統(tǒng)層面的實(shí)現(xiàn)原理费尽,這篇文章我將分析與Activity視圖相關(guān)的對象Window、WindowManager和View的之間關(guān)系和它們創(chuàng)建過程羊始。
Window的初始化過程
Activity有一個(gè)類型為Window的成員變量旱幼,它在Activity成員函數(shù)attach()被初始化,附上源碼:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
......
private Window mWindow;
......
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this);
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);
}
......
mWindow.setWindowManager(null, mToken, mComponent.flattenToString());
......
}
整個(gè)代碼的調(diào)用流程可以用下面的時(shí)序圖來概括:
attach()函數(shù)主要做了以下幾件事情:
- 初始化一個(gè)PhoneWindow對象并賦給mWindow突委,由此可知PhoneWindow是Window的子類柏卤,兩者的關(guān)系可以參考下面的圖2;
- 調(diào)用PhoneWindow的成員函數(shù)setCallback()設(shè)置Activity的回調(diào),該回調(diào)用來通知鍵盤和觸摸回調(diào)匀油;
- 調(diào)用setSoftInputMode()設(shè)置描述窗口的軟鍵盤輸入?yún)^(qū)域的顯示模式缘缚;
- 為PhoneWindow創(chuàng)建一個(gè)WindowManagerImpl對象并保存在成員變量mWindowManager中,WindowManagerImpl用來處理后面會(huì)講到的View對象敌蚜;
Activity的所有視圖相關(guān)的屬性和布局對象都是通過PhoneWindow來維護(hù)的桥滨,我通過一個(gè)類圖來體現(xiàn)Window和PhoneWindow的關(guān)系:
View的創(chuàng)建過程
通過下面的時(shí)序圖我們可以知道整View的創(chuàng)建過程
其中ActivityThread是創(chuàng)建Activity的一個(gè)處理類,通過這個(gè)類來處理Activity的生命周期钝侠,這個(gè)流程不是這篇文章討論的范疇该园,我們只要知道系統(tǒng)經(jīng)過一系列復(fù)雜的過程最終通過performLaunchActivity調(diào)用了Activity的onCreate函數(shù),而我們要開發(fā)應(yīng)用程序一般都會(huì)在onCreate函數(shù)調(diào)用setContentView來設(shè)置布局帅韧,最終調(diào)用了PhoneWindow的setContentView()里初,我們來看源碼:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;
private ViewGroup mContentRoot;
......
@Override
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();
}
}
.......
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
......
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
}
}
}
......
通過代碼分析,PhoneWindow通過以下幾個(gè)步驟來創(chuàng)建View:
- 創(chuàng)建一個(gè)DecorView對象mDecor忽舟,通過以上的圖2我們知道DecorView繼承自FrameLayout双妨,它是Activity的頂級視圖淮阐,Activity顯示的所有界面內(nèi)容都放在里面;
- 根據(jù)Activity的Feature加載不同的布局文件刁品,這些布局文件放在frameworks/base/core/res/res/layout目錄下泣特,它們必須包含有一個(gè)id值為“content”的布局控件,加載完后解析成一個(gè)布局對象mContentRoot挑随,接著把mContentRoot當(dāng)作子布局添加到mDecor中状您;
- 從mContentRoot獲取到id為“content”的子布局mContentParent,將我們自定義的整個(gè)布局添加到mContentParent中兜挨;
最終可以通過下面的圖表示activity的view布局結(jié)構(gòu):
這樣我們就知道和Activity相關(guān)的Window和View的創(chuàng)建過程了膏孟,接下來,如圖3的時(shí)序圖拌汇,ActivityThread會(huì)將創(chuàng)建好的視圖對象Window和布局對象通過addView()方法最終傳遞到WindowManagerGlobal柒桑,后者就會(huì)將每個(gè)Activity對應(yīng)的Window和View關(guān)聯(lián)起來,最終傳遞到WindowManagerService噪舀,后者會(huì)統(tǒng)一協(xié)調(diào)整個(gè)系統(tǒng)的Activity視圖布局魁淳,根據(jù)需要將需要渲染的activity布局內(nèi)容交給另一個(gè)叫SurfaceFlinger的服務(wù)去渲染出來,這個(gè)時(shí)候我們就能在屏幕上看到具體的布局圖形了与倡。關(guān)于WindowManagerService和SurfaceFlinger涉及了另外兩塊更加復(fù)雜的內(nèi)容界逛,此篇文章不作詳細(xì)分析。