轉載請標明出處:
http://blog.csdn.net/xmxkf/article/details/52457893
本文出自:【openXu的博客】
在上一篇文章《 Activtiy完全解析(一、Activity的創建過程)》中,我們探討了Activity
的啓動,從startActivity()
到進程創建,再到activity的創建,最後調用onCreate()
方法。本篇博客我們接着onCreate()
方法繼續研究Activity
加載layout
的過程。我們寫好layout
佈局後,在onCreate()
方法中調用setContentView(layoutID)
就能將我們的佈局加載顯示。這一過程到底做了什麼?佈局中的控件是怎樣被加載並顯示出來的?我們從setContentView()
方法開始一步步分析 。(參考源碼版本爲API23 )
step1.Activity.setContentView()
通常我們的做法是在OnCreate()
方法中設置佈局資源ID,Activity
提供了三個設置視圖的方法,如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
//setContentView(new TextView(this));
//setContentView(new TextView(this),new ViewGroup.LayoutParams(...));
}
再看看Activity
中的調用關係:
public class Activity ...{
private Window mWindow;
//三個設置視圖的方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
//初始化ActionBar
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
final void attach(...) {
...
//初始化mWindow
mWindow = new PhoneWindow(this);
...
}
public Window getWindow() {
return mWindow;
}
}
原來Activity
中調用setContentView()
方法最終調用的是PhoneWindow
對象mWindow
的setContentView(...)
方法。
mWindow
是在Activity
創建的時候在attach()
方法中初始化的,attach()
方法上一篇博客http://blog.csdn.net/xmxkf/article/details/52452218 中有講解。
每一個Activity
組件都有一個關聯的Window
的實現類PhoneWindow
的對象mWindow
,用來描述一個應用程序窗口,它封裝了頂層窗口的外觀和行爲策略,它提供了標準的用戶界面策略,如背景、標題區域、默認鍵處理等。PhoneWindow
管理着整個屏幕的內容,不包括屏幕最頂部的系統狀態條。所以,PhoneWindow
或者Window
是與應用的一個頁面所相關聯。下面我們查看PhoneWindow.setContentView()
方法:
step2. PhoneWindow.setContentView()
@Override
public void setContentView ( int layoutResID){
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//移除該mContentParent內所有的所有子View
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//將我們的資源文件通過LayoutInflater對象轉換爲View樹,並且添加至mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
@Override
public void setContentView (View view){
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView (View view, ViewGroup.LayoutParams params){
//此處判斷mContentParent是否爲null,如果是null,則是第一次調用setContentView
if (mContentParent == null) {
//第一次需要初始化窗口和根佈局(id爲content的)
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//如果不是第一次調用,需要清空根佈局中的內容
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
PhoneWindow
中setContentView(view)
接着調用setContentView(view, layoutParams)
,而setContentView(layoutResID)
和setContentView(view, layoutParams)
方法中的步驟是差不多的,唯一的區別就是setCOntentView(layoutResID)
中是通過mLayoutInflater.inflate(layoutResID, mContentParent)
將佈局填充到mContentParent
上,而setContentView(view, layoutParams)
是將傳過來的view
直接 add到mContentParent
中 。到這一步我們發現,我們爲某個activity
寫的layout
視圖最終是添加到一個叫mContentParent
的ViewGroup
中了。
在加入mContentParent
中之前,首先判斷如果mContentParent==null
時,執行了installDecor()
方法,我們猜想,installDecor()
的作用大概就是初始化mContentParent
,如果mContentParent
已經初始化,而且窗口不是透明的,就清除mContentParent
中的所有視圖。mContentParent
只會實例化一次,所以如果我們在Activity
中多次調用setContentView()
只是改變了mContentParent
的子視圖(也就是我們寫的佈局文件)。接着看看installDecor()
:
step3. PhoneWindow.installDecor()
上一步我們猜測installDecor()
方法的作用是實例化mContentParent
,接下來我們深入PhoneWindow
中驗證這個猜測對不對,下面是PhoneWindow
的關鍵代碼:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//id爲content的容器,這個容器就是用於盛放我們寫的layout視圖的
private ViewGroup mContentParent;
//mContentRoot是整個界面內容,包括title和content等等
private ViewGroup mContentRoot;
//窗口頂層FrameLayout,用於盛放mContentRoot
private DecorView mDecor;
/**
* DecorView是FrameLayout的子類
*/
private final class DecorView extends FrameLayout{
...
}
//實例化了一個DecorView對象
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
/**
* 初始化頂層窗口mDecor和根佈局mContentParent
*/
private void installDecor() {
if (mDecor == null) {
//①.初始化窗口
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
...
}
if (mContentParent == null) {
//②.如果根佈局
mContentParent = generateLayout(mDecor);
//③.初始化title和一些設置
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
...
} else {
mTitleView = (TextView) findViewById(R.id.title);
if (mTitleView != null) {
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
...
} else {
mTitleView.setText(mTitle);
}
}
}
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
...
}
}
}
PhoneWindow
中引用了一個DecorView
的對象,DecorView
是FrameLayout
的子類,相信你們應該多多少少知道Activity
的頂層窗口是一個FramLayout
,也正是這個DevorView
的對象mDecor。在installDecor()
方法中,第①步就是判斷mDecor
是否爲null,如果爲null,將會調用generateDecor()
方法實例化一個DecorView
對象,緊接着第②步判斷mContentParent
是否爲null,如果爲null,將調用generateLayout(mDecor)
方法初始化mContentParent
。現在就有一個疑問了,mDecor
和mContentParent
都是容器,他們是什麼關係?各自代表的是屏幕中那一塊的內容?帶着問題我們看看generateLayout(mDecor)
方法:
step 4. PhoneWindow.generateLayout(mDecor )
/**
* 初始化根佈局mContentParent
*/
protected ViewGroup generateLayout(DecorView decor) {
//①.獲取AndroidManifest.xml中指定的themes主題
TypedArray a = getWindowStyle();
//設置當前窗口是否有標題
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
//請求指定Activity窗口的風格類型
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
requestFeature(FEATURE_ACTION_BAR);
}
...
//設置窗口是否全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
...
//②.根據上面設置的窗口屬性Features, 設置相應的修飾佈局文件layoutResource,這些xml文件位於frameworks/base/core/res/res/layout下
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if
...
mDecor.startChanging();
//③.將第2步選定的佈局文件inflate爲View樹,這也是整個窗口的內容(包括title、content等等)
View in = mLayoutInflater.inflate(layoutResource, null);
//④.將整個窗口內容添加到根mDecor中
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
//⑤.將整個窗口內容賦值給mContentRoot
mContentRoot = (ViewGroup) in;
//⑥.將窗口修飾佈局文件中id="@android:id/content"的View賦值給mContentParent
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
mDecor.finishChanging();
return contentParent;
}
- 在
generateLayout()
中,第①步是初始化了一些樣式屬性值,我們發現有一個很熟悉的類TypedArray
,沒錯,這在之前講解自定義控件屬性的時候用到過,其實此處也是一樣的,之前自定義控件的屬性是用來描述我們自定義的View
,而這裏的樣式屬性是用來描述窗口,而我們知道窗口實質上也是View
,唯一的區別就是,控件的屬性是在layout
中設置,而窗口的屬性是在AndroidManifest.xml
中配置的,通過getWindowStyle()
獲取當前Window
在theme
中定義的屬性,window
支持的屬性可以參考\frameworks\base\core\res\res\values\attrs.xml 中的<declare-styleable name="Window">
。獲取到屬性值之後有與大堆代碼是調用setFlags()
和requestFeature()
給當前window
設置屬性值,這就是爲什麼我們一般在Activity
的onCreate()
中設置全屏等屬性需要在setContentView()
之前設置,因爲setContentView()
之後installDecor()
方法已經執行完畢,所以設置是沒有效的。 - 第①步執行完後,
Window
的相關屬性已經設置完畢,比如是否是全屏?是否有標題等等。然後第②步就是根據Window
的各種屬性,設置相應的佈局文件,如果是全屏layoutResource
(佈局文件ID)是多少,如果有標題…; - 確定
layoutResource
之後,第③步就是mLayoutInflater
解析出layoutResource
對應的視圖in; - 第④步將in這個視圖add到
mDecor
頂層窗口中; - 第⑤步將in賦值給mContentRoot;
- 第⑥步調用
findViewById()
找到id爲content
的View
賦值給mContentParent
。
通過generateLayout ()
方法,我們發現了三個比較重要的視圖對象mDecor
、mContentRoot
、mContentParent
。他們的關係如下:
mDecor
是Activity
的頂層窗體,他是FramLayout
的子類對象;mContentRoot
是根據設置給窗體加載的整個Activity
可見的視圖,這個視圖包含標題欄(如果主題設置有標題),用於容納我們自定義layout
的id爲content
的容器,mContentRoot
被添加到了頂層窗口mDecor
中;mContentParent
是mContentRoot
中id爲content
的容器,這個容器就是用來添加我們寫的layout
佈局文件的,mContentParent
是嵌套在mContentRoot
中,mContentRoot
嵌套在mDecor
。所以在上面第⑥步可以直接調用findViewById()
找到mContentParent
。(跟蹤findViewById()
方法,發現調用的是PhoneWindow
中mDecor
這個頂層窗口的findViewById()
方法 )
關係圖如下:
step5. LayoutInflater.inflate()
經過上面的步驟,Activity
的頂層窗口mDecor
和用於容納我們寫的layout
的容器mContentParent
已經初始化完畢,接下來就是將我們的佈局layout
添加到mContentParent
中,先把step2 中的PhoneWindow.setContentView(int layoutResID)
方法貼出來:
@Override
public void setContentView ( int layoutResID){
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//移除該mContentParent內所有的所有子View
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//將我們的資源文件通過LayoutInflater對象轉換爲View樹,並且添加至mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
在分析PhoneWindow.setContentView()
方法的過程中,我們穿插講解了installDecor()
方法,接着有一個判斷hasFeature(FEATURE_CONTENT_TRANSITIONS)
,這個屬性是Android5.0引入的,這篇博客中參照的是API23的源碼 ,他的意思根據設置的主題屬性,判斷當前窗口內容變化的過程是否需要動畫,如果有動畫標誌,將執行動畫。Scene
是場景,這個對象中引用了mSceneRoot
和mlayout
兩個視圖,mSceneRoot
就是mContentParent
,而mLayout
就是我們的佈局視圖。我們發現另一個setContentView(view)
方法中直接將view
傳遞進去了Scene newScene = new Scene(mContentParent, view);
,而setContentView(layoutResID)
是將layoutResID
傳遞進去,可以想象在Scene
中也會根據此layoutResID
將我們的佈局layout
加載並賦值給mLayout
。由於我們主要研究的是View
的加載過程,所以就不深入講解動畫了。直接看下面的mLayoutInflater.inflate(layoutResID, mContentParent)
,這一步的目的就是解析我們的佈局layout
,並將佈局視圖掛載到mContentParent
上,那下面看看LayoutInflater.inflate()
方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
這個方法有三個參數:
resource
:需要解析的佈局layout
的idroot
:解析layout
之後得到的視圖層級的父視圖attachToRoot
:是否將解析出來的視圖添加到父視圖中,如果傳入true
,並且root
不爲null,這個方法返回的是root
,而且將解析出的視圖添加到root
中。而我們看到inflate(resource)
方法在inflate(resource,root,attachToRoot)
方法時,傳入的attachToRoot
是root!=null
,所以inflate
返回的是已經將佈局layout
視圖添加到mContentParent
後的mContentParent
。
inflate(resource,root,attachToRoot )
方法中通過res.getLayout(resource)
將layout
關聯到一個XmlResourceParser
中(Android內置的pull解析器),然後調用inflate(parser,root,attachToRoot )
方法:
/**
* 將layout解析爲view樹,並附加到root(mContentParent)中
*/
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
//①.將最終返回的View初始化爲root(也就是mContentParent)
View result = root;
try {
int type;
//②.循環直到解析到開始標籤<>或者結尾標籤</>
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//第一次解析到的不是開始標籤<>,說明layout文件沒有<>標籤,xml格式錯誤
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//③.找到第一個開始標籤,這個標籤對應的name就是整個layout最外層的父控件
final String name = parser.getName();
...
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
//★④.根據layout中第一個開始標籤的名稱創建一個View對象temp,temp就是整個xml中的根控件
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
// 根據父控件獲取佈局參數,後面將解析的view樹添加到root中是要使用
params = root.generateLayoutParams(attrs);
//如果不需要附加到root父控件中
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
//爲temp設置佈局參數如果我們不附加。(如果我們是,我們使用addView,下同)
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
//★⑤.調用rInflateChildren遞歸解析temp中的所有子控件,通過這行代碼整個layout就被解析爲view樹了
rInflateChildren(parser, temp, attrs, true);
//★⑥.如果root不爲空,將view樹添加到root中
//此處root爲mContentParent,也就是將layout佈局添加到mContentParent中了
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
//如果不用附加到root中,直接返回解析的view樹
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
這個方法的作用就是將layout
填充到一個view
樹上,然後將view
樹附加到root
(也就是mContentParent
)中,然後返回root
。
方法中有6個重要步驟,上面註釋已經寫得很清楚了,注意簽名帶★的步驟是很重要的。在①-④步是將layout
最外層的控件解析出來,在第④步中調用了createViewFromTag()
方法(請看step6) 根據name
實例化一個View
對象,然後第⑤步調用rInflateChildren()
方法,將剩餘的控件解析出來後填充進最外層控件,這樣就完成了整個layout
的填充,最後第⑥步將解析出來的view
樹添加到root
中。我們發現真正完成inflate
的並不是這個方法,這個方法只是解析了在外層的控件,剩餘的控件是由rInflateChildren()
方法完成的,而rInflateChildren()
中調用的是rInflate()
方法(請看step7)
step6. LayoutInflater.createViewFromTag()
/**
* 根據控件名實例化控件對象
* @param parent 父控件
* @param name 控件名
* @param context
* @param attrs
* @param ignoreThemeAttr
* @return
*/
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//先判斷name中是否有'.'字符,如果沒有,此控件爲android自帶的View,此時會在name的前面加上包名"android.view."
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
//如果有這個'.',則認爲是自定義View,因爲自定義View在使用的時候使用的全名,所以直接創建
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
}
}
step7. LayoutInflater.rInflate()
/**
* 解析layout最外層parent中的所有子控件
* 此方法爲遞歸方法,layout中有多少個ViewGroup就會遞歸調用多少次
* 每一次調用就會完成layout中某一個ViewGroup中所有的子控件的解析
*/
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
//如果遇到結束標籤(</>)就結束,說明此parent中所有的子view已經解析完畢
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
//1.找到開始標籤<>
final String name = parser.getName();
//2.根據name類型分別解析
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
/*
* 如果是<include />,調用parseInclude方法用於解析<include/>標籤:
* ①.根據include標籤的name屬性找到對應的layout的id
* ②.遍歷開始標籤解析layout中的view
* ③.調用rInflateChildren(childParser, view, childAttrs, true)解析view中的子控件
* ④.將view添加(add)進parent中
*/
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
//如果是普通View,調用createViewFromTag創建view對象
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//★遞歸調用rInflateChildren解析view中的子控件
//如果view不是ViewGroup,rInflateChildren()會在while的第一次循環結束
//如果view是ViewGroup,並且裏面有子控件,通過這行代碼view中的所有子控件就被掛到view上了
rInflateChildren(parser, view, attrs, true);
//將view樹添加到viewGroup中,到此爲止完成一個view及其所有子控件的填充
viewGroup.addView(view, params);
}
}
if (finishInflate) {
/*
* ★parent的所有子控件都inflate完畢後調用onFinishInflate方法
* 這個方法在自定義ViewGroup的時候經常用到,自定義ViewGroup中
* 不能在構造方法中find子控件,因爲構造方法中並沒有完成子控件的實例化,
* 只能在onFinishInflate回調方法中findViewById來初始化子控件
*/
parent.onFinishInflate();
}
}
rInflate()
方法無非就是根據剩餘的xml找到開始標籤的name
,然後根據name
的類型分別解析,如果判斷是普通控件,調用createViewFromTag()
創建一個控件view
,接着遞歸調用rInflateChildren()
解析view
中的所有子控件(如果view
是ViewGroup
),最後將view
添加到parent
中。rInflateChildren()
遞歸調用執行完畢後,整個layout
就被填充爲view
樹了,最後在inflate()
中,layout
的view
樹會被add
到root
中,也就是mContentParent
中,整個窗體的view
樹mDecor
就算是填充完畢。
總結一下這篇博客的內容,這篇博客分析的是View
的加載填充原理,也就是從調用setContentView()
方法開始,我們的佈局layout
是怎樣填充爲整個View
樹,並被掛載到Activity
上的。其中有幾個重要的知識點如下:
- 每一個
Activity
組件都有一個關聯的Window
的實現類PhoneWindow
的對象mWindow
,mWindow
管理着整個屏幕的內容,不包括屏幕最頂部的系統狀態條 ,它描述一個應用程序窗口,它封裝了頂層窗口的外觀和行爲策略,它提供了標準的用戶界面策略,如背景、標題區域、默認鍵處理等; Activity
的setContentView()
方法裏面調用的是PhoneWindow
的setContentView()
方法;PhoneWindow
中引用了mDecor
(頂層窗口,FramLayout
的子類)、mContentRoot
(整個Activity
的內容,包括TitleActionBar
等)、mContentParent
(mContentRoot
中id爲content
的容器,用於放置我們的layout
的容器);他們三者的關係是mDecor
嵌套mContentRoot
,mContentRoot
嵌套mContentParent
;- 如果
mContentParent
不爲null,將清空其中的內容,然後重新加載layout
到mContentParent
中 ; 如果mContentParent
爲null說明是第一次調用setContentView
,這時候需要調用installDecor()
爲Activity
加載一個頂層窗口mDecor
,mContentParent
; installDecor()
方法中初始化了mDecor
,然後調用generateLayout(mDecor)
;generateLayout(mDecor)
中首先設置了window
的主題樣式,並根據這些樣式設置爲Activity
加載一個合適的佈局視圖,並將這個視圖賦值給mContentRoot
,然後將此視圖add到mDecor
頂層窗口中;然後通過mDecor.findViewById(R.id.content)
初始化mContentParent
。通過②-⑥步,Activity
中的頂層窗體的View
樹算是搭建完畢了;setContentView(layoutId)
中完成上面步驟後緊接着調用LayoutInflater.inflate()
將我們傳入的layoutId
填充爲View
樹,inflate()
只是解析了layout
佈局的最外層父控件,裏面的子控件是通過rInflateChildren()
方法遞歸解析完成的。在解析的過程中如果遇到開始<>
標籤會調用createViewFromTag()
方法實例化一個View
對象,並解析爲view
設置的屬性attrs
。inflate()
方法執行完畢後,layout
就被映射爲View
樹了,然後將此View
樹add
到mContentParent
中,整個Activity
的view
樹就形成了。
下一篇博客我們講解
Activity
的顯示過程(Activtiy
中的View
是怎樣顯示出來的)。