Activity 系列博客
- 《 Activity 的組成》
- 《Android Activity——啟動(dòng)過程探索(一)》
- 《Android Activity——啟動(dòng)過程探索(二)》
- 《Android Activity——啟動(dòng)過程探索(三)》
- 《Activity 啟動(dòng)模式及任務(wù)棧探究》
- 《 Activity常見問題》
Activity 的組成
Activity
代表一個(gè)窗口粮彤,實(shí)際上這個(gè)窗口并不是 Activity
,而是由 一個(gè) 繼承至Window
抽象類的 PhoneWindow
對(duì)象mWindow
來表示的叹俏,mWindow
是Activity
的成員變量恋拍,主要作用就是負(fù)責(zé)窗口的管理蠢挡。之所以說是管理窗口的嫩与,是因?yàn)樗⒉荒軐⒔缑嬲故境鰜恚宫F(xiàn)界面是由它管理的 DecorView
對(duì)象來完成留特, 在Activity
中我們可以通過getWindow().getDecorView()
方法來獲取DecorView
對(duì)象堂鲤,DecorView
類是 FrameLayout
的子類咙咽, 也是整個(gè) View
樹的根布局(這里也能說明Android事件的分發(fā)是從ViewGroup
開始的老玛,如果需要了解更多關(guān)于Android事件分發(fā)問題,可以瀏覽《 Android中的事件分發(fā)機(jī)制》這篇博客)钧敞。DecorView
由三部分構(gòu)成: ActionBar蜡豹、標(biāo)題區(qū)(標(biāo)題區(qū)根據(jù)加載布局的不同,可能會(huì)沒有)和內(nèi)容區(qū)犁享。
對(duì)上圖進(jìn)行說明:
- 首先是最外層的
Activity
余素,一般我們是繼承自AppCompatActivity
,但是AppCompatActivity
最終也是繼承自Activity
的炊昆,所以我們的最外框?qū)樱褪?Activity
-
Activity
相當(dāng)于一個(gè)框威根,它包含了Window
(實(shí)際上是Window
的子類PhoneWindow
)來管理窗口凤巨,Activity
其他功能這里不做說明 -
PhoneWindo
w管理窗口,它包含了DecorView
用來渲染和顯示內(nèi)容(包括ActionBar洛搀、TitleBar和ContentParent部分) -
DecorView
是FrameLayout
的子類敢茁,也是整個(gè)視圖的根節(jié)點(diǎn),開發(fā)者寫的布局layout.xml文件最終是被添加到DecorView
的ContentParent部分 - 當(dāng)在討論Android事件的分發(fā)是從
View
還是ViewGroup
開始時(shí)留美,從DecorView extends FrameLayout extends ViewGroup
就能得到Android事件是從ViewGroup
開始的(如果需要了解更多關(guān)于Android事件分發(fā)問題彰檬,可以瀏覽《 Android中的事件分發(fā)機(jī)制》這篇博客)
源碼解析
注意:源碼版本為 Android 10(Api 29),不同Android版本可能有一些差別
setContentView()
執(zhí)行過程
// Activity 中的 setContentView() 方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()
方法返回的是 Window
類型的成員變量 mWindow
谎砾,實(shí)際是 PhoneWindow
對(duì)象逢倍。(mWindow
的創(chuàng)建過程請(qǐng)繼續(xù)往下看,在后面【Activity中PhoneWindow在什么時(shí)候創(chuàng)建的】中會(huì)說到)景图,我們現(xiàn)在繼續(xù)看 PhoneWindow
中的 setContentView()
方法:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor(); // 內(nèi)容根布局為null時(shí)较雕,調(diào)用 installDecor() 方法創(chuàng)建 DecorView
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 將傳遞的 布局文件id 通過 inflate 添加到 mContentParent 中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
通過以上我們知道了,Activity
中調(diào)用 setContentView()
方法實(shí)際上就是調(diào)用了 PhoneWindow
中的 setContentView()
方法挚币,然后創(chuàng)建 DecorView
以及將布局內(nèi)容增加到 DecorView
中過程亮蒋。
DecorView
的創(chuàng)建和 布局內(nèi)容填充的過程
在上面方法中,在初次的時(shí)候妆毕,mContentParent
肯定為 null
慎玖,所以會(huì)走 installDecor()
方法創(chuàng)建 DecorView
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1); // 關(guān)鍵點(diǎn)1:創(chuàng)建 DecorView
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor); // 關(guān)鍵點(diǎn)2:初始化內(nèi)容區(qū)域 mContentParent
mDecor.makeOptionalFitsSystemWindows();
// 下面的代碼是設(shè)置標(biāo)題(如果需要并且存在TitleView)和Activity樣式、效果等笛粘,忽略
}
}
-
關(guān)鍵點(diǎn)1
generateDecor()
方法創(chuàng)建DecorView
protected DecorView generateDecor(int featureId) { Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { context = new DecorContext(applicationContext, getContext()); if (mTheme != -1) { context.setTheme(mTheme); } } } else { context = getContext(); } return new DecorView(context, featureId, this, getAttributes()); }
通過 new DecorView()
直接創(chuàng)建 DecorView
對(duì)象趁怔,看看 DecorView
的定義远舅,繼承至 FrameLayout
:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
-
關(guān)鍵點(diǎn)2
generateLayout()
方法初始化內(nèi)容區(qū)域protected ViewGroup generateLayout(DecorView decor) { // 獲取和設(shè)置Activity各種屬性、樣式痕钢、效果等图柏,忽略 // Inflate the window decor. int layoutResource; // 根據(jù)Activity不同的特征加載不同的布局(這個(gè)布局是系統(tǒng)中的,也是DecorView唯一的子孩子控件) int features = getLocalFeatures(); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; setCloseOnSwipeEnabled(true); } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_title_icons; } removeFeature(FEATURE_ACTION_BAR); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { layoutResource = R.layout.screen_progress; } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_custom_title; } removeFeature(FEATURE_ACTION_BAR); } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { layoutResource = a.getResourceId( R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar); } else { layoutResource = R.layout.screen_title; } } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = R.layout.screen_simple_overlay_action_mode; } else { layoutResource = R.layout.screen_simple; } mDecor.startChanging(); // 關(guān)鍵點(diǎn)任连,解析資源文件并將解析后的View添加到 DecorView 中 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 布局添加到 DecorView 中之后蚤吹,通過 findViewById() 找到用于填充內(nèi)容的 contentParent (FrameLayout 布局) ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } // 設(shè)置窗體背景等 return contentParent; }
以上代碼中的資源文件位于 ...\sdk\platforms\android-29\data\res\layout
目錄下(android-29表示對(duì)應(yīng)的android版本),以下是 screen_title.xml 文件中的內(nèi)容:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
其他不同的資源文件具體內(nèi)容可以自行查看随抠,最外層都是 LinearLayout
包裹的裁着,也就就是為什么在上方圖中,DecorView
中為什么先包含了LinearLayout
之后拱她,在 LinearLayout
中包含 ActionView二驰、TitleView、ContentParent 的原因秉沼。
-
關(guān)鍵點(diǎn)桶雀,解析資源文件并將解析后的View添加到。查看
DecorView
中的onResourcesLoaded()
方法:void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { if (mBackdropFrameRenderer != null) { loadBackgroundDrawablesIfNeeded(); mBackdropFrameRenderer.onResourcesLoaded( this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), getCurrentColor(mNavigationColorViewState)); } mDecorCaptionView = createDecorCaptionView(inflater); final View root = inflater.inflate(layoutResource, null); if (mDecorCaptionView != null) { if (mDecorCaptionView.getParent() == null) { addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mDecorCaptionView.addView(root, new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); } else { // Put it below the color views. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mContentRoot = (ViewGroup) root; initializeElevation(); }
當(dāng)完成后唬复,我們回頭繼續(xù)看 PhoneWindow
中的 setContentView()
方法中的 mLayoutInflater.inflate(layoutResID, mContentParent)
這行代碼矗积,這行代碼的作用就是將自定義的界面布局加載到 mContentParent
中。具體的實(shí)現(xiàn)過程敞咧,可以通過《Android inflate解析》和《Android inflate實(shí)例解析》 進(jìn)行了解棘捣。
通過以上過程,我們就從源碼方面知道了 Activity
的組成以及 setContentView()
方法的具體執(zhí)行過程了休建。下面做一個(gè)簡(jiǎn)單的總結(jié):
-
setContentView()
方法過程總結(jié):在
Activity
中調(diào)用setContentView()
方法乍恐, 實(shí)際上是調(diào)用的PhoneWindow
中的setContentView()
方法,在PhoneWindow
中的setContentView()
方法中會(huì)調(diào)用installDecor()
方法去創(chuàng)建一個(gè)DecorView
對(duì)象测砂,然后根據(jù)不同情況加載進(jìn)不同的布局(最外層都是 LinearLayout 包裹的,包含的具體控件一定有表示內(nèi)容的 Content茵烈,根據(jù)情況包含 ActionView 和 TitleView)到DecorView
中;最后通過LayoutInflater.inflate()
方法將自定義的界面布局加載到mContentParent
中邑彪。 -
Activity
的組成總結(jié):Activity
包含了一個(gè)管理窗口的繼承至Window
的PhoneWindow
對(duì)象瞧毙,而在PhoneWindow
中創(chuàng)建了一個(gè)繼承至FrameLayout
類的DecorView
對(duì)象,并且把包含了ActionBar寄症、TitleBar和Content的布局文件通過addView()
方法添加到了DecorView
中宙彪,當(dāng)開發(fā)者定義了layout.xml布局并調(diào)用了setContentView()
方法時(shí),就是將自定義的布局加載到了DecorView
中表示 content 的部分(setContentView()
部分具體過程同 【setContentView()
方法過程總結(jié)】)有巧。
Activity中PhoneWindow在什么時(shí)候創(chuàng)建的
通過查看Activity
的源碼可以找到PhoneWindow
的創(chuàng)建是在Activity
的attach()
方法中(attach()
方法在Activity
中是onCreate()
方法調(diào)用之前系統(tǒng)自動(dòng)調(diào)用的一個(gè)方法)释漆,同時(shí)也獲取到了WindowManager
對(duì)象
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
...
// 創(chuàng)建PhoneWindow對(duì)象
mWindow = new PhoneWindow(this);
... // 將各種參數(shù)賦給Activity的成員變量
// 通過PhoneWindow對(duì)象獲取mWindowManager對(duì)象
mWindowManager = mWindow.getWindowManager();
...
}
Activty
系統(tǒng)回調(diào) attach()
方法的過程可以通過以下博客了解: