在咱們開發過程當中,咱們有的時候會碰到一些眼熟的單詞,如Window、PhoneWindow、DecorView、ViewGroup等等名詞,雖然不知道它們都包含什麼意思,可是常常會碰到,做爲一個準備進階的Android程序員,咱們有必要了解一下來龍去脈,接下來咱們便一一瞭解這些名詞。首先咱們從最經常使用的部分-setContentView學起。javascript
//開發工具
一、Android Studio
Android Studio 2.2.3
Build #AI-145.3537739, built on December 2, 2016
JRE: 1.8.0_112-release-b05 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
//源碼環境
二、Android API -25
compileSdkVersion 25
buildToolsVersion "25.0.2"複製代碼
首先咱們從最多見的setContentView開始分析源碼。html
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//最多見的開始分析,進入詳細代碼
setContentView(R.layout.activity_set_content_view_learn);
}複製代碼
進入到了詳細代碼中咱們會發現其實咱們進入到了activity類代碼中了。java
//代碼清單Activity.java
/** * Set the activity content from a layout resource. The resource will be * inflated, adding all top-level views to the activity. * * @param layoutResID Resource ID to be inflated. * * @see #setContentView(android.view.View) * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) */
public void setContentView(@LayoutRes int layoutResID) {
//調用Window類中的方法setContentView。
getWindow().setContentView(layoutResID);
//無關代碼,初始化actionBar相關東東...
initWindowDecorActionBar();
}複製代碼
首先咱們會發現,咱們先調用getWindow獲取window對象,而後再調用Window類中的setContentView方法,咱們能夠發現Window類實際上是一個抽象類,因此確定會有它的實現類,在這裏,它的實現類就是PhoneWindow類。在這裏,咱們終於碰到了一個常常會提到的類PhoneWindow。android
//代碼清單 抽象類Window
public abstract class Window {
//省略相關代碼...
}
//代碼清單 Window實現類,PhoneWindow
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//省略相關代碼...
}複製代碼
從上面的類申明當中咱們就能夠發現,PhoneWindow類實際上是抽象類Window的實現類,因此對於setContentView方法,其實咱們就獲得它的實現類PhoneWindow中去查看。git
//代碼清單 PhoneWindow.java
// This is the view in which the window contents are placed. It is either mDecor itself, or a child of mDecor where the contents go.
//變量申明
ViewGroup mContentParent;
@Override
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);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}複製代碼
在PhoneWindow這個具體的實現類中,咱們能夠看到的流程是首先判斷mContentParent是否是等於null,是的話就加在installDecor()方法,不是的話再判斷是否使用了這個FEATURE_CONTENT_TRANSITIONS的flag,若是沒有的話則mContentParent刪除其中全部的view。接下來再判斷是否須要經過LayoutInflater.inflate將咱們傳入的layout放置到mContentParent中。程序員
其實咱們能夠稍微預測下installDecor()的功能,大概就是初始化mContentParent這個變量。而mContentParent這個變量就是包裹咱們設置的整個xml佈局內容的ViewGroup。github
最後就是調用了一個接口Callback裏面的方法。咱們能夠發現其實Activity實現了這個接口,可是onContentChanged這個接口方法在Activity中是一個空實現,這不是重點。windows
接下來,咱們繼續研究installDecor()中的源碼~app
//代碼清單 PhoneWindow中的installDecor方法
//申明變量
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
//省略無關代碼...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//省略無關代碼...
} else {
mTitleView = (TextView) findViewById(R.id.title);
//設置是否須要標題
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
final View titleContainer = findViewById(R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
mContentParent.setForeground(null);
} else {
mTitleView.setText(mTitle);
}
}
}
//省略無關代碼...
}
}複製代碼
從上面代碼咱們能夠看出大概的流程。首先經過generateDecor(-1)來初始化一下mDecor,這個mDecor是DecorView的對象,看吧,這裏面已經出現了這個常見名詞,等等咱們來分析一下DecorView這個類的做用。ide
接下來,咱們用mDecor這個對象經過generateLayout(mDecor)來初始化mContentParent這個對象。而後咱們即可以經過findViewById這個方法來獲取相關的控件了。
//代碼清單 window.java類獲取相關控件
@Nullable
public View findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}複製代碼
在這裏的getDecorView()方法其實就是獲取mDecor這個對象。接下來咱們開始分析DecorView這個類。入口點就是咱們剛剛在installDecor方法中初始化mDecor這個對象的地方mDecor = generateDecor(-1)。
//代碼清單 DecorView.java類
protected DecorView generateDecor(int featureId) {
Context context;
//沒什麼鳥用的無關代碼,省略...
return new DecorView(context, featureId, this, getAttributes());
}複製代碼
從generateDecor中咱們沒發現什麼有用的東西,咱們繼續分析相關代碼。接下來咱們分析mContentParent = generateLayout(mDecor);
從這個方法名便能看出個大概了,它是生成佈局,而後賦值給mContentParent,咱們進入到方法中詳細瞭解一下整個佈局生成過程。
//代碼清單 generateLayout方法流程
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//設置當前activity的theme
TypedArray a = getWindowStyle();
//省略無關代碼...
//首先經過WindowStyle中設置的各類屬性,對Window進行requestFeat
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
//省略無關代碼...
//根據feature來加載對應的xml佈局文件
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} 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;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
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;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
//將上面獲取到的xml佈局加載到mDecor對象中
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
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);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks();
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}複製代碼
上面這個方法流程咱們能看出個大概,首先getWindowStyle在當前的Window的theme中獲取咱們的Window中定義的屬性。而後就根據這些屬性的值,對咱們的Window進行各類requestFeature,setFlags等等。
還記得咱們平時寫應用Activity時設置的theme或者feature嗎(全屏啥的,NoTitle等)?咱們通常是否是經過XML的android:theme屬性或者java的requestFeature()方法來設置的呢?譬如:
經過java文件設置:
requestWindowFeature(Window.FEATURE_NO_TITLE);
經過xml文件設置:
android:theme="@android:style/Theme.NoTitleBar"複製代碼
對的,其實咱們平時requestWindowFeature()設置的值就是在這裏經過getLocalFeature()獲取的;而android:theme屬性也是經過這裏的getWindowStyle()獲取的。
因此這裏就是解析咱們爲Activity設置theme的地方,至於theme通常能夠在AndroidManifest裏面進行設置。接下來,經過對features和mIsFloating的判斷,爲layoutResource進行賦值,至於值能夠爲R.layout.screen_custom_title;R.layout.screen_action_bar;等等。至於features,除了theme中設置的,咱們也能夠在Activity的onCreate的setContentView以前進行requestFeature,也解釋了,爲何須要在setContentView前調用requestFeature設置全屏什麼的。
最後經過咱們獲得了layoutResource,而後將它加載給mDecor對象,這個mDecor對象實際上是一個FrameLayout,它的中心思想就是根據theme或者咱們在setContentView以前設置的Feature來獲取對應的xml佈局,而後經過mLayoutInflater轉化爲view,賦值給mDecor對象,這些佈局文件中都包含一個id爲content的FrameLayout,最後將其引用返回給mContentParent。
//代碼清單 xml佈局文件,包含id爲content的FrameLayout佈局
<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>複製代碼
咱們用word來畫一個簡單的示意圖
到這裏咱們分析了生成佈局後的大概流程,這樣生成了佈局後咱們接着幹嘛呢?請回看當初的代碼:
//代碼清單 PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
//咱們上面分析的生成系統xml佈局的流程
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 {
//將咱們本身寫的xml加載到咱們上面獲取到的裏面包含id爲content的xml佈局中去,並賦值給mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
//省略無關代碼...
}複製代碼
咱們剛剛寫了一個系統生成的xml佈局就是包含id爲content的佈局:
<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" />複製代碼
其實這就是咱們將本身設置的xml佈局裝載進這個佈局中。這就是整個setContentView所作的工做。
最後咱們來總結一下所有流程,用一個圖來表示,更方面直接:
Android中有一個成員叫Window,Window是一個抽象類,提供了繪製窗口的一組通用API,PhoneWindow繼承自Window,是Window的具體實現類,PhoneWindow中有一個私有成員DecorView,這個DecorView對象是全部應用Activity頁面的根View,DecorView繼承自FrameLayout,在內部根據用戶設置的Activity的主題(theme)對FrameLayout進行修飾,爲用戶提供給定Theme下的佈局樣式。通常狀況下,DecorView中包含一個用於顯示Activity標題的TitleView和一個用於顯示內容的ContentView。
能夠看出,DecorView中包含一個Vertical的LinearLayout佈局文件,文件中有兩個FrameLayout,上面一個FrameLayout用於顯示Activity的標題,下面一個FrameLayout用於顯示Activity的具體內容,也就是說,咱們經過setContentView方法加載的佈局文件View將顯示在下面這個FrameLayout中。
首先初始化mDecor,即DecorView爲FrameLayout的子類。就是咱們整個窗口的根視圖了。而後,根據theme中的屬性值,選擇合適的佈局,經過infalter.inflater放入到咱們的mDecor中。在這些佈局中,通常會包含ActionBar,Title,和一個id爲content的FrameLayout。最後,咱們在Activity中設置的佈局,會經過infalter.inflater壓入到咱們的id爲content的FrameLayout中去。
一、http://blog.csdn.net/lmj623565791/article/details/41894125
二、http://blog.csdn.net/yanbober/article/details/45970721/
三、http://www.2cto.com/kf/201409/331824.html
github: github.com/crazyandcod…
博客: crazyandcoder.github.io