Android佈局文件的加載過程分析:Activity.setContentView()源碼分析

大家都知道在Activity的onCreate()中調用Activity.setContent()方法可以加載佈局文件以設置該Activity的顯示界面。本文將從setContentView()的源碼談起,分析佈局文件加載所涉及到的調用鏈。本文所用的源碼爲android-19.

Step 1  、Activity.setContentView(intresId)

public void setContentView(int layoutResID) {
	getWindow().setContentView(layoutResID);
	initActionBar();
}

public Window getWindow() {
	return mWindow;
}
該方法調用了該Activity成員的mWindow,mWindow爲Window對象。Windown對象是一個抽象類,提供了標準UI的顯示策略和行爲策略。在SDK中只有PhoneWindow類實現了Window類,而Window中的setContentView()爲空函數,所以最後調用的是PhoneWindow對象的方法。

Step 2  、PhoneWindow.setContentView()

@Override
public void setContentView(int layoutResID) {
	if (mContentParent == null) {
		installDecor();
	} else {
		mContentParent.removeAllViews();
	}
	// 將佈局文件添加到mContentParent中
	mLayoutInflater.inflate(layoutResID, mContentParent);
	final Callback cb = getCallback();
	if (cb != null && !isDestroyed()) {
		cb.onContentChanged();
	}
}
該方法首先根據mContentParent是否爲空對mContentParent進行相應的設置。mContentParent爲ViewGroup類型,若其已經初始化了,則移除所有的子View,否則調用installDecor()初始化。接着將資源文件轉成View樹,並添加到mContentParent視圖中。

Step 3、 PhoneWindow.installDecor() 
這段代碼比較長,下面的僞代碼只介紹邏輯,讀者可自行查看源碼。

private void installDecor() {
	if (mDecor == null) {
		/*創建一個DecorView對象並設置相應的屬性。DecorView是所有View的根View*/
		mDecor = generateDecor();
		mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
		mDecor.setIsRootNamespace(true);
		if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
			mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
		}
	}
	if (mContentParent == null) {
		/*創建mContentParent對象*/
		mContentParent = generateLayout(mDecor);

		// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
		mDecor.makeOptionalFitsSystemWindows();
		
		mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
		if (mTitleView != null) {
	
			mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
			// 根據features值,設置Title的相關屬性
			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);
			}
		} else {
			//若沒有Title,則設置ActionBar的相關屬性,如回調函數、風格屬性
			mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
			if (mActionBar != null) {
				//.......
				
				// 推遲調用invalidate,放置onCreateOptionsMenu在 onCreate的時候被調用
				mDecor.post(new Runnable() {
					public void run() {
						// Invalidate if the panel menu hasn't been created before this.
						PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
						if (!isDestroyed() && (st == null || st.menu == null)) {
							invalidatePanelMenu(FEATURE_ACTION_BAR);
						}
					}
				});
			}
		}
	}
}
創建mContentParent對象的代碼如下:

protected ViewGroup generateLayout(DecorView decor) {
	// Apply data from current theme.
	//獲取當前Window主題的屬性數據,相關字段的文件位置爲:sdk\platforms\android-19\data\res\values\attrs.xml
	//這些屬性值可以在AndroidManifest.xml中設置Activityandroid:theme="",也可以在Activity的onCreate中通過
	//requestWindowFeature()來設置.注意,該方法一定要在setContentView之前被調用
	TypedArray a = getWindowStyle(); 
	//獲取android:theme=""中設置的theme
	//根據主題屬性值來設置PhoneWindow的特徵與佈局,包括Title、ActionBar、ActionBar的模式、Window的尺寸等屬性。
	//........
	

	// Inflate the window decor.
	// 根據上面設置的Window feature來確定佈局文件
	// Android SDK內置佈局文件夾位置爲:sdk\platforms\android-19\data\res\layout
	典型的窗口布局文件有:
		  R.layout.dialog_titile_icons                          R.layout.screen_title_icons
		  R.layout.screen_progress                             R.layout.dialog_custom_title
		  R.layout.dialog_title   
		  R.layout.screen_title         // 最常用的Activity窗口修飾佈局文件
		  R.layout.screen_simple    //全屏的Activity窗口布局文件
	
	int layoutResource;
	int features = getLocalFeatures(); //會調用requesetWindowFeature()
	// ......
	mDecor.startChanging();
	// 將佈局文件轉換成View數,然後添加到DecorView中
	View in = mLayoutInflater.inflate(layoutResource, null);
	decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
	//取出作爲Content的ViewGroup, android:id="@android:id/content",是一個FrameLayout
	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	if (contentParent == null) {
		throw new RuntimeException("Window couldn't find content container view");
	}

	if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
		ProgressBar progress = getCircularProgressBar(false);
		if (progress != null) {
			progress.setIndeterminate(true);
		}
	}

	// 將頂層Window的背景、標題、Frame等屬性設置成以前的
	if (getContainer() == null) {
		//......
	}

	mDecor.finishChanging();

	return contentParent;
}
總結:setContentView將佈局文件加載到程序窗口的過程可概況爲:
1、創建一個DecorView對象,該對象將作爲整個應用窗口的根視圖
2、根據android:theme=""或requestWindowFeature的設定值設置窗口的屬性,根據這些屬性選擇加載系統內置的佈局文件
3、從加載後的佈局文件中取出id爲content的FrameLayout來作爲Content的Parent
4、將setContentView中傳入的佈局文件加載到3中取出的FrameLayout中


最後,當AMS(ActivityManagerService)準備resume一個Activity時,會回調該Activity的handleResumeActivity()方法,
該方法會調用Activity的makeVisible方法 ,顯示我們剛纔創建的mDecor視圖族。

//系統resume一個Activity時,調用此方法  
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {  
    ActivityRecord r = performResumeActivity(token, clearHide);  
    //...  
     if (r.activity.mVisibleFromClient) {  
         r.activity.makeVisible();  
     }  
} 
makeVisible位於ActivityThread類中,代碼如下:

void makeVisible() {  
    if (!mWindowAdded) {  
        ViewManager wm = getWindowManager();   // 獲取WindowManager對象  
        wm.addView(mDecor, getWindow().getAttributes());  
        mWindowAdded = true;  
    }  
    mDecor.setVisibility(View.VISIBLE); //使其處於顯示狀況  
}  

參考文章:http://blog.csdn.net/qinjuning/article/details/7226787




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章