最近在 CSDN 看了某大神的幾篇源碼解析的文章,自己再回顧整理一遍。
一、從 Activity 的 setContentView 開始
Activity 提供了三個(gè)重載的 setContentView 方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
可以看到 Activity 的 setContentView 內(nèi)部都先調(diào)用了 getWindow 的 setContentView 方法,然后調(diào)用了 Activity 的 initWindowDecorActionBar 方法。
二、Window 類
getWindow 方法返回的是 Activity 的 Window 類的成員變量 mWindow 。
這里簡要介紹一下 Window 類:
- Window 類是一個(gè)抽象類,它的唯一實(shí)現(xiàn)類是 PhoneWindow;
- PhoneWindow 有一個(gè)內(nèi)部類 DecorView,DecorView 是 Activity 的根 View;
- DecorView 繼承自 FramLayout;
三、PhoneWindow 的 setContentView 方法
Window 類的 setContentView 方法都是抽象的,直接看 PhoneWindow 類的 setContentView 方法。
1、setContentView(int layoutResID)
第一個(gè)方法傳入的參數(shù)是布局的資源 ID:
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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
在 setContentView 方法中首先判斷成員變量 mContentParent 是否為 null,如果是第一次調(diào)用,mContentParent 為 null,調(diào)用 PhoneWindow 的 installDecor 方法,如果 mContentParent 不為 null,則判斷是否設(shè)置 FEATURE_CONTENT_TRANSITIONS 的 Window 屬性(默認(rèn)false),如果沒有設(shè)置該屬性就移除 mContentParent 內(nèi)所有的所有子View;
1.1、PhoneWindow 類的 installDecor 方法
那么 installDecor 方法里面做了 什么呢?
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//根據(jù)窗口的風(fēng)格修飾,選擇對應(yīng)的修飾布局文件,并且將id為content的FrameLayout賦值給mContentParent
mContentParent = generateLayout(mDecor);
//......
//初始化一堆屬性值
}
}
在 installDecor 方法里上來并沒有先處理 mContentParent,而是先判斷 mDecor 成員變量是否為 null,如果 mDecor 為 null,就調(diào)用 generateDecor 方法給 mDecor 賦值,generateDecor 的代碼很簡單:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
就是通過 DecorView 的構(gòu)造方法 new 了一個(gè) DecorView 對象返回。 所以說 PhoneWindow 的 mDecor 是 DecorView 類的成員變量,也就是所有內(nèi)容的根 View。
1.2、generateLayout 方法
此時(shí) mDecor 不為 null 了,如果 mContentParent 為 null,則調(diào)用 generateLayout 方法創(chuàng)建 mContentParent :
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
//......
//依據(jù)主題style設(shè)置一堆值進(jìn)行設(shè)置
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
//......
//根據(jù)設(shè)定好的features值選擇不同的窗口修飾布局文件,得到layoutResource值
//把選中的窗口修飾布局文件添加到DecorView對象里,并且指定contentParent值
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//......
//繼續(xù)一堆屬性設(shè)置,完事返回contentParent
return contentParent;
}
在 generateLayout 方法里面首先根據(jù)應(yīng)用主題 style 設(shè)置一堆值進(jìn)行設(shè)置,我們設(shè)置的 android:theme 屬性都是在這里的 getWindowStyle 方法中獲取的,而我們在代碼中通過 requestWindowFeature() 設(shè)置的屬性是在 getLocalFeature 方法中獲取的,這也是為什么 requestWindowFeature() 代碼要在 setContentView() 前面執(zhí)行。
然后根據(jù)設(shè)定好的 features 值選擇不同的窗口修飾布局文件,得到布局文件的 layoutResource 值,LayoutInflater 把布局的資源文件解析成 View 之后,添加到 DecorView 中,這個(gè) View 就是 PhoneWindow 的 mContentRoot 成員變量,而 mContentParent 就是布局文件中 ID 為 @android:id/content 的 FramLayout。
再回到 setContentView 方法中,如果 Window 沒有設(shè)置 FEATURE_CONTENT_TRANSITIONS 的話,就通過 LayoutInflater 把布局文件加載到 mContentParent 中。
2、setContentView(View view) 和 setContentView(View view,ViewGroup.LayoutParams params) 方法
一個(gè)參數(shù)的方法也是調(diào)用了兩個(gè)參數(shù)的方法,只是 params 參數(shù)直接設(shè)置為 MATCH_PARENT。
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
可以看到與參數(shù)為 layoutResID 的方法不同之處在于直接調(diào)用了 ViewGroup 的 addView 方法將布局加載到 mContentParent 上面。
加載完 View 后,兩個(gè)方法最后都調(diào)用了 Callback 的 onContentChanged 方法來通知對應(yīng)的 Activity 視圖內(nèi)容發(fā)生了變化。getCallback 方法返回的是 Window 的 mCallback 成員變量,這個(gè)成員變量是通過 setCallback 方法進(jìn)行賦值的,毫無疑問,Activity 實(shí)現(xiàn)了這個(gè)接口,并且在 attach 方法中通過 mWindow.setCallback(this) 進(jìn)行設(shè)置,Activity 的 onContentChanged 方法是一個(gè)空方法,當(dāng) Activity setContentView 或者 addContentView 時(shí)會調(diào)用該方法。
3、Activity 的 initWindowDecorActionBar
private void initWindowDecorActionBar() {
Window window = getWindow();
// Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView();
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
至于 Window 的 setContentView 方法執(zhí)行完了之后的 initWindowDecorActioonBar 方法就是創(chuàng)建一個(gè) Actionbar 并設(shè)置一些默認(rèn)顯示等。
Activity 調(diào)運(yùn) setContentView 方法自身不會顯示布局的,一個(gè) Activity 的開始實(shí)際是 ActivityThread 的 main 方法,當(dāng)啟動 Activity 調(diào)運(yùn)完 ActivityThread 的 main 方法之后,接著調(diào)用 ActivityThread 類 performLaunchActivity 來創(chuàng)建要啟動的 Activity 組件,在創(chuàng)建 Activity 組件的過程中,還會為該 Activity組件創(chuàng)建窗口對象和視圖對象;接著 Activity 組件創(chuàng)建完成之后,通過調(diào)用 ActivityThread 類的 handleResumeActivity 將它激活。
在 handlerResumeActivity 中調(diào)用 Activity 的 makeVisible 方法顯示我們上面通過 setContentView 創(chuàng)建的 mDecor 視圖族。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
參考:
Android應(yīng)用setContentView與LayoutInflater加載解析機(jī)制源碼分析