想必你們都知道在 Activity 的 onCreate 經過 setContentView(R.layout.xxx) ,而後就能本身編寫界面被加載顯示啦! 卻對它源碼是如何實現的不得而知吧! 這篇文章就是對 setContentView 的分析。android
人狠話很少,直接進入主題。來看咱們的 Activity 的 onCreate 方法,windows
public class MainActivity extends Activity {
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
複製代碼
請注意,我這裏是繼承自 Activity 而沒有繼承 AppCompatActivity。這是谷歌在後續增長新功能引入的,在這裏爲了先分析簡化的版本,分析完了再去分析 AppCompatActivity 額外作了那些工做。bash
點擊 setContentView 進入到 Activity 的 setContentView。app
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
複製代碼
能夠看到第一行代碼 getWindow(), 這個方法返回的什麼呢?跟進去看看。ide
public Window getWindow() {
return mWindow;
}
複製代碼
返回的是一個 mWindow, 這個 mWindow 又是什麼呢?能夠看到它是 Window 對象。Window 又是又是一個抽象類以下:佈局
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
.....
}
複製代碼
從註釋中能夠看到,它有惟一的實現類 PhoneWindow,說明 setContentView 是在 PhoneWindow 實現的。post
@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.
// 當一個 Activity 的 onCreate 方法被調用時,mContentParent 爲空。
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 {
// 這個方法也是很是重要,它是將咱們佈局中的寫的組件,解析並加載到 mContentParent 中, 會分單獨的文章來說,見佈局中的 xml 文件中的加載過程
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
複製代碼
來看 installDecor 作了什麼?ui
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 這個方法用來建立 mDecor
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 生成 mContentParent
mContentParent = generateLayout(mDecor);
..... 省略了部分代碼
}
}
複製代碼
先看 mDecor 的建立,mDecor 是 DecorView 的實例。generateDecor 方法就建立出 mDecor 而已。this
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use // the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
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();
}
// 建立 DecorView 返回
return new DecorView(context, featureId, this, getAttributes());
}
複製代碼
mDecor 以及被實例化了,接下來就是看 generateLayout 方法。前方高能,代碼多到懷疑人生,不過咱們只找咱們關係的部分。分析源碼就是這個過程,若是要每行都看懂。呵呵噠,不存在的,若是追求每一行都看懂,最終就會入門到放棄。spa
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
..... 省略部分代碼, 這部分代碼主要是 window 屬性的設置,好比 FEATURE_NO_TITLE,
FEATURE_ACTION_BAR 咱們比較熟悉的
// 看到這個註釋了麼,告訴咱們開始填充 decor
// Inflate the window decor.
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;
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;
}
// 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!");
}
mDecor.startChanging();
// 看到這個方法,或許就該慶賀了,由於要結束啦。關鍵的是 layoutResource 這是啥?
// 其實從上面一段代碼中能夠看到賦值的地方有好幾處, 這裏咱們以 layoutResource = R.layout.screen_simple; 爲例。其實說白了就是將這個佈局做爲 DecorView 的根視圖。
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 在 R.layout.screen_simple 中有一個 ID 名爲 ID_ANDROID_CONTENT, 其實值爲: com.android.internal.R.id.content, 這個時候就獲得了 contentParent。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...... 省略部分代碼
mDecor.finishChanging();
// 將建立好的 contentParent 返回
return contentParent;
}
複製代碼
咱們能夠舉個例子來看看系統提供的佈局,在源碼中 res/layout/ 找到,是否是發現前面的 content 的 ID, 它是一個 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>
複製代碼
最終返回到 PhoneWindow 的 setContentView 的方法,再看一遍:
@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 {
// 到了這兒,又將咱們在 Activity 調用 setContentView 的佈局填充到 mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
複製代碼
好像很簡單吧!確實不難。經過 mLayoutInflater.inflate(layoutResID, mContentParent) 將咱們的佈局添加進去。給出它的層次圖1-1
經過繼承 Activity 設置 setContentView 分析完畢。接下來就是來看繼承 AppCompatActivity 又作了什麼?首先將以前繼承自 Activity 的改成 AppCompatActivity, 點擊 setContentView 進入源碼,會看到以下的方法:
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
複製代碼
這裏的 getDelegate 是引入代理類來實現 setContentView 邏輯,最終到 AppCompatDelegateImpl 的 setContentView 方法。
public void setContentView(int resId) {
this.ensureSubDecor();
// 因爲下載這部分源碼,若是隻能看到 16908290 這樣的數字,不過沒關係,問題不大
// 只需知道 mSubDecor 幹了什麼便可推理出來。
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}
複製代碼
留意到第一方代碼了嗎? this.ensureSubDecor(); 進去看看。
private void ensureSubDecor() {
if (!this.mSubDecorInstalled) {
// 來看看怎麼建立的 mSubDecor, 這個就是咱們想要知道的。
this.mSubDecor = this.createSubDecor();
CharSequence title = this.getTitle();
if (!TextUtils.isEmpty(title)) {
if (this.mDecorContentParent != null) {
this.mDecorContentParent.setWindowTitle(title);
} else if (this.peekSupportActionBar() != null) {
this.peekSupportActionBar().setWindowTitle(title);
} else if (this.mTitleView != null) {
this.mTitleView.setText(title);
}
}
this.applyFixedSizeWindow();
this.onSubDecorInstalled(this.mSubDecor);
this.mSubDecorInstalled = true;
AppCompatDelegateImpl.PanelFeatureState st = this.getPanelState(0, false);
if (!this.mIsDestroyed && (st == null || st.menu == null)) {
this.invalidatePanelMenu(108);
}
}
}
複製代碼
createSubDecor 這個方也很長啊,怎麼感受咱們在前面是否是看到過,其實和前面看到的原理很像。
private ViewGroup createSubDecor() {
TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
if (!a.hasValue(styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity.");
} else {
if (a.getBoolean(styleable.AppCompatTheme_windowNoTitle, false)) {
this.requestWindowFeature(1);
} else if (a.getBoolean(styleable.AppCompatTheme_windowActionBar, false)) {
this.requestWindowFeature(108);
}
if (a.getBoolean(styleable.AppCompatTheme_windowActionBarOverlay, false)) {
this.requestWindowFeature(109);
}
if (a.getBoolean(styleable.AppCompatTheme_windowActionModeOverlay, false)) {
this.requestWindowFeature(10);
}
this.mIsFloating = a.getBoolean(styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
this.mWindow.getDecorView();
LayoutInflater inflater = LayoutInflater.from(this.mContext);
// subDecor 等着被賦值.
ViewGroup subDecor = null;
// 選擇沒有標題的主題
if (!this.mWindowNoTitle) {
if (this.mIsFloating) {
subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
this.mHasActionBar = this.mOverlayActionBar = false;
} else if (this.mHasActionBar) {
TypedValue outValue = new TypedValue();
this.mContext.getTheme().resolveAttribute(attr.actionBarTheme, outValue, true);
Object themedContext;
if (outValue.resourceId != 0) {
themedContext = new ContextThemeWrapper(this.mContext, outValue.resourceId);
} else {
themedContext = this.mContext;
}
subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null);
this.mDecorContentParent = (DecorContentParent)subDecor.findViewById(id.decor_content_parent);
this.mDecorContentParent.setWindowCallback(this.getWindowCallback());
if (this.mOverlayActionBar) {
this.mDecorContentParent.initFeature(109);
}
if (this.mFeatureProgress) {
this.mDecorContentParent.initFeature(2);
}
if (this.mFeatureIndeterminateProgress) {
this.mDecorContentParent.initFeature(5);
}
}
} else { // 有標題的主題
if (this.mOverlayActionMode) {
subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
} else {
subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
}
if (VERSION.SDK_INT >= 21) {
ViewCompat.setOnApplyWindowInsetsListener(subDecor, new OnApplyWindowInsetsListener() {
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
int top = insets.getSystemWindowInsetTop();
int newTop = AppCompatDelegateImpl.this.updateStatusGuard(top);
if (top != newTop) {
insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), newTop, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
}
return ViewCompat.onApplyWindowInsets(v, insets);
}
});
} else {
((FitWindowsViewGroup)subDecor).setOnFitSystemWindowsListener(new OnFitSystemWindowsListener() {
public void onFitSystemWindows(Rect insets) {
insets.top = AppCompatDelegateImpl.this.updateStatusGuard(insets.top);
}
});
}
}
// 到這兒 subDecor 就應該不爲空,不然 Activity 就會啓動失敗,由於沒有找到相關主題。
if (subDecor == null) {
throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");
} else {
if (this.mDecorContentParent == null) {
this.mTitleView = (TextView)subDecor.findViewById(id.title);
}
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
// 找到 subDecor 的內容 contentView
ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
// 各位,看到這兒明白什麼了嗎? 這個方法找到的是咱們前面的 DecorView, 而後將它添加到 contentView 中,就是在外面套了一層 AppCompatActivity 的主題。
ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
if (windowContentView != null) {
while(windowContentView.getChildCount() > 0) {
View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
windowContentView.setId(-1);
contentView.setId(16908290);
if (windowContentView instanceof FrameLayout) {
((FrameLayout)windowContentView).setForeground((Drawable)null);
}
}
// 偷樑換柱手法,將本身注入到原來的體系中,即多了一層 subDecor.
this.mWindow.setContentView(subDecor);
contentView.setAttachListener(new OnAttachListener() {
public void onAttachedFromWindow() {
}
public void onDetachedFromWindow() {
AppCompatDelegateImpl.this.dismissPopups();
}
});
return subDecor;
}
}
}
複製代碼
然咱們再回到 AppDelegateImpl 的 setContentView 中 contentParent 是不該該知道是什麼了吧! 接下來的就是將咱們在 Activity 設置的佈局文件加載到 contentParent 中。
public void setContentView(int resId) {
this.ensureSubDecor();
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}
複製代碼
總結一下, 其實 AppCompatActivity 只是在 Activity 外面套了直接的一層主題。來個最終層次圖吧!