一、寫在前面
我們都知道繼承Activity的onCreate()方法然後setContentView(R.layout.xxx)
可以設置我們自己的佈局文件,但是佈局文件生成的View到底添加到哪裏去了呢?首先在Activity這個類中我們可以看到有個mWindow
變量,這個變量是在attach()
方法裏面創建的:mWindow = new PhoneWindow(...)
,然後在我們setContentView()的時候會創建一個mDecor
變量,這就是我們Activity的第一個View,然後再往裏面添加各種View,包括我們自己佈局的View。此文就是分析下這些View都是怎麼添加的。
以前的以前我們自己的Activity都是直接繼承Activity
這個類的,從API21開始,我們一般都繼承AppCompatActivity
了,下面分別分析。
注:以下源碼版本爲API28。
二、繼承Activity的時候
// 這是我們普通的Activity,繼承onCreate,添加我們的佈局
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.xxx);
}
// 類:Activity
public void setContentView(@LayoutRes int layoutResID) {
// 跟蹤getWindow()可以看到mWindow = new PhoneWindow(...),返回的是一個PhoneWindow
// 所以這裏跳到PhoneWindow的setContentView(...)
// IDEA裏面可以直接按ctrl+alt+單擊彈出實現此方法的類,選擇PhoneWindow的進入就行
getWindow().setContentView(layoutResID);
// ...
}
// 類:PhoneWindow
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
}
// ...
// 把我們自己的佈局文件inflate後添加到mContentParent中
// mContentParent就是我們自己的佈局的父View
mLayoutInflater.inflate(layoutResID, mContentParent);
// ...
}
// 類:PhoneWindow
private void installDecor() {
// ...
if (mDecor == null) {
// 1、生成DecorView
// mDecor是PhoneWindow的一個變量,同時PhoneWindow也是mDecor的一個變量
mDecor = generateDecor(-1);
// ...
}
// ...
if (mContentParent == null) {
// 2、生成mContentParent
// 從前面我們知道這就是我們自己的佈局的父類
mContentParent = generateLayout(mDecor);
// ...
}
}
1、生成DecorView
// 類:PhoneWindow
protected DecorView generateDecor(int featureId) {
// ...
// 直接創建了一個DecorView,點擊去查看DecorView是繼承了FrameLayout
return new DecorView(context, featureId, this, getAttributes());
}
2、生成mContentParent
// 類:PhoneWindow
protected ViewGroup generateLayout(DecorView decor) {
// 獲取窗口屬性,也就是AndroidManifest.xml中設置的主題
// View的屬性在layout.xml裏面配置,Window的屬性在主題裏面配置
TypedArray a = getWindowStyle();
// 這裏是一系列的主題設置
// ...
// 根據上面設置的屬性,加載不同的根佈局,所有佈局都一定會有一個View的id是ID_ANDROID_CONTENT,
// 也就是mContentParent,也就是我們setContentView()傳入的View的父View
// 這裏隨便選一個佈局,比如:R.layout.screen_simple
// 該文件在源碼中的路徑:frameworks/base/core/res/res/layout/screen_simple.xml
// 下載源碼方法:https://blog.csdn.net/qiantujava/article/details/102847414
// 在線源碼地址:http://aospxref.com/android-9.0.0_r45/xref/frameworks/base/core/res/res/layout/screen_simple.xml
layoutResource = R.layout.screen_simple;
// 1、解釋上面選定的layout修飾佈局文件
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 這裏findViewById其實調用的是getDecorView().findViewById(id)
// 上面所有佈局都會有ID_ANDROID_CONTENT這個id,也就是:android:id="@android:id/content"
// 我們自己的佈局文件就是添加到這個ViewGroup中的
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// ...
return contentParent;
}
1、解釋修飾佈局文件
// 類:DecorView
// 解釋layout文件
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
// ...
// Caption有標題的意思
mDecorCaptionView = createDecorCaptionView(inflater);
// 解釋剛纔那個佈局screen_simple.xml,裏面會有ID_ANDROID_CONTENT
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
// 沒搞清楚mDecorCaptionView是個什麼東西,先不管這個
} else {
// 沒有mDecorCaptionView的時候,直接把root添加到DecorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
// ...
}
上面有幾個重點關注的變量:
mWindow
是PhoneWindow的對象,是Activity的一個變量,在Activity.attach()方法裏面就創建了;
mDecor
是DecorView的對象,是PhoneWindow的一個變量;
mContentRoot
是修飾佈局文件inflate得到的View,添加到了mDecor中;
mContentParent
是mContentRoot中id爲ID_ANDROID_CONTENT的View;
最後我們setContentView傳入的View是添加到mContentParent的。
所以我們可以得到個大概的佈局關係圖:
三、繼承AppCompatActivity
// 這是我們普通的Activity,繼承onCreate,添加我們的佈局
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.xxx);
}
// 類:AppCompatActivity
public void setContentView(@LayoutRes int layoutResID) {
// 點進去getDelegate()可以看到返回的是AppCompatDelegateImpl
// 所以這裏調用的是AppCompatDelegateImpl.setContentView()
getDelegate().setContentView(layoutResID);
}
// 類:AppCompatDelegateImpl
public void setContentView(int resId) {
// 確保mSubDecor是否已經生成
// 這裏還不知道mSubDecor是個什麼東西,下面再看
ensureSubDecor();
// 把我們自己的佈局添加到contentParent
// 其實從ensureSubDecor()裏面可以知道這個android.R.id.content對應的View已經不是前面分析的mContentParent了
// 而是mSubDecor裏面的一個ContentFrameLayout
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
// ...
}
// 類:AppCompatDelegateImpl
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
// ...
}
}
// 類:AppCompatDelegateImpl
private ViewGroup createSubDecor() {
// 設置各種主題
// ...
// 跟蹤這個mWindow,可以看到是從Activity傳過來的,所以這個也是PhoneWindow
// getDecorView()裏面就一個判斷,如果mDecor==null,就installDecor()
// installDecor()在上面已經分析了,一毛一樣的
// installDecor()後就生成了mDecor和mContentParent了
mWindow.getDecorView();
// 根據不同的主題inflate不同的佈局文件,然後賦值給subDecor
// 所以,subDecor就是一個普通的ViewGroup
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
// ...
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_dialog_title_material, null);
// ...
// 上面所有的佈局都會有個id=R.id.action_bar_activity_content的ContentFrameLayout
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
// 上面mWindow.getDecorView()已經生成mContentParent了,id就是android.R.id.content
// 所以這裏windowContentView其實就是mContentParent
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// 把windowContentView的所有子view都添加到contentView中
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
// 把windowContentView的id設爲了NO_ID,把contentView的id設成了android.R.id.content
// 此後各個view的關係還是:DecorView->mContentRoot->mContentParent(就是windowContentView)
// 但是mContentParent裏面的view已經全部移到contentView中了,同時contentView還沒添加到DecorView
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
// ...
}
// 把contentView添加到DecorView中
mWindow.setContentView(subDecor);
// ...
return subDecor;
}
// 類:PhoneWindow
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 前面createSubDecor()中調了mWindow.getDecorView()已經生成了mContentParent,所以直接跳過
if (mContentParent == null) {
installDecor();
}
// ...
// 這個view是前面傳過來的subDecor
mContentParent.addView(view, params);
// ...
}
由上面的分析,我們同樣可以得到以下總結:
mWindow
是PhoneWindow的對象,是Activity的一個變量,在Activity.attach()方法裏面就創建了;
mDecor
是DecorView的對象,是PhoneWindow的一個變量;
mContentRoot
是修飾佈局文件inflate得到的View,添加到了mDecor中;
mContentParent
是mContentRoot中id爲ID_ANDROID_CONTENT的View,但是被設置爲NO_ID了;
mSubDecor
是含有R.id.action_bar_activity_content的佈局,mSubDecor會被添加到mContentParent中;
contentView
是ContentFrameLayout,是mSubDecor中id=R.id.action_bar_activity_content的view,最後會被設成id=android.R.id.content。
最後我們setContentView()傳入的view是會被添加到id=android.R.id.content的view中的,
所以我們可以得到個大概的佈局關係圖:
四、繼承Activity和繼承AppCompatActivity的區別
從上面的分析可以看出來,繼承AppCompatActivity的時候,佈局中多了個mSubDecor
和contentView
,之前我們的佈局是添加到mContentParent
的,現在是添加到contentView
了,其實換湯不換藥,只是在中間插入了一層view。爲什麼要這樣呢?因爲在API28的時候官方大力推薦MaterialDesign
,在中間插入了一層mSubDecor就可以爲所欲爲的加入MaterialDesign的東西了,應該是醬紫吧。
五、寫在最後
其實看源碼也沒有那麼難嘛,一開始看可能會覺得超複雜,跳來跳去的,谷歌什麼破工程師,寫出來的代碼亂七八糟。但是當你看的多了,這裏的多包括看的次數多和看的內容多,看的次數多了你會對這段源碼印象更深刻,下次再回來看的時候不會再那麼陌生了,而看的內容多的話,你會發現越來越清晰了,各個點都是有關係的,之前那個點可能沒看懂,看到這個點的時候忽然明白了上個點,都是有聯繫的。
PS. 標題裏面的夜夜並沒有說假話,現在是凌晨4點、