對Activity的setContentView的使用,大家比較熟悉了,當然,對setContentView的原理估計也比較熟悉,網上有不少的文章,不過,還是寫一篇這方面的東西,記錄下,畢竟
很多東西,看別人的是一回事兒,自己寫又是一回事兒,當作是對知識的溫故,再學習吧!
Activity.java
首先,在自己的activity中,調用setContentView(int resID)方法,如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
}
可以看出,該方法先是獲取Window的對象,然後調用該對象的setContentView方法
繼續看,源碼:
private Window mWindow;
public Window getWindow() {
return mWindow;
}
final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,...省略) {
mWindow = new PhoneWindow(this);
//省略部分代碼
}
發現getWindow()方法,是mWindow,屬於成員變量,在attach方法調用後mWindow初始化爲PhoneWindow,是不是着急想知道PhoneWindow是何東東?
別急,在這有點疑問?attach方法在什麼時候調用呢?告訴你,是在ActivityThread類中,有個performLaunchActivity方法,該方法調用activity.attach,然後mWindow初始化
當然,關於performLaunchActivity執行,暫時只需要知道,當我們調用startActivity的時候,系統會去加載一系列的類,調用一系列方法,其中就有performLaunchActivity,來
完成activity的啓動工作
ok,知道了mWindow的初始化,讓我們回到setContentView,來聊聊PhoneWindow這個類,PhoneWindow繼承自Window,而Window是個抽象類
PhoneWindow.java
public class PhoneWindow extends Window
PhoneWindow對setContentView的重寫,如下:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
}
//...
mLayoutInflater.inflate(layoutResID, mContentParent);
//...
}
這裏,知道setContentView執行了兩個方法,
步驟1:installDecor(),
步驟2:將我們自己的xml佈局資源ID,轉換爲view,並填充到mContentParent上,問題來了,mContentParent是什麼呢?留個問題在這
先看步驟1,源碼如下:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
//...
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//...
}
確認DecorView是FrameLayout的子類 private final class DecorView extends FrameLayout
到這裏,知道步驟1是執行了兩步,瞭解到以下內容:
mDecor是個類變量,是DecorView,繼承Framlayout,也就是一個ViewGroup,mDecor是generateDecor創建的
mContentParent是類變量,是一個ViewGroup,mContentParent是generateLayout進行創建的,mContentParent的創建依賴mDecor,
接下來,分別看generateDecor與generateLayout
generateDecor()方法:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
該方法創建了DecorView實例,通過查看DecorView構造方法,發現只是初始化了一些類變量,沒有其他操作
generateLayout()方法(關鍵之處):
PS:這裏,由於源碼比較冗雜,只是保留關鍵部分,簡化代碼如下
protected ViewGroup generateLayout(DecorView decor){
int layoutResource;
//...
layoutResource = R.layout.screen_simple;
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
//...
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//...
return contentParent;
}
layoutResource的值是經過一系列判斷後才被賦值的,這裏,我選擇的其中一個固定值,佈局填充器將系統資源文件Id爲R.layout.screen_simple佈局轉換爲View對象in,
將in作爲DecorView的子view進行添加,調用findViewById,這裏,很奇怪,爲何能直接調用findViewById,原來是Window中定義了該方法,前面說過,PhoneWindow繼承
Window,所以可直接調用父類方法。
Window類
public View findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
public abstract View getDecorView();
那麼,getDecorView()是怎麼執行的呢?PhoneWindow對該抽象方法進行具體實現,返回上面generateDecor()方法的mDecor
PhoneWindow類
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
到這裏,大致清楚了,這句代碼
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
饒來繞去,就是在mDecor中查找ID_ANDROID_CONTENT的對應的View,然後是賦值給contentParent,在generateLayout方法中返回contentParent,最終賦值給
mContentParent,換句話說,mContentParent是mDecor的子view,而我們自己佈局對應的view,是mContentParent的子view
步驟2中
mLayoutInflater.inflate(layoutResID, mContentParent);
//等價於
View ourContentView = mLayoutInflater.inflate(layoutResID, null);
mContentParent.addView(ourContentView );
R.layout.screen_simple的xml源碼:
<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 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" />
<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>
結論:DecorView是Window的最頂級的View,其下有兩個子View,一個標題欄bar,一個是容器content,
自己定義的佈局對應view,是被addView在容器content上
最後,附上一張圖,直觀感受下