android setContentView原始碼分析
Activity是android的四大元件之一,其重要性不言而喻,而且在我們的開發過程中打交道最多的也是它。在設定檢視的時候,我們一般都是通過setContentView來載入我們的佈局資源的,看起來很簡單的一行程式碼setContentView(),但是實際上裡面都做了哪些事情你真的知道嗎?
在開始講解setContentView的原始碼之前,你首先要弄懂上面的這張圖,弄不懂也沒關係,但是你需要記住它,這樣有利於後面的原始碼理解。
原始碼解析
@Override public void setContentView(int layoutResID) { // mContentParent即DecorView // 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 { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
private void installDecor() { mForceDecorInstall = false; // mDecor是否為空,第一次進來的時候為空 if (mDecor == null) { // 初始化DecorView mDecor = generateDecor(-1); // 省略部分程式碼 } else { mDecor.setWindow(this); } // mContentParent是否為空,第一次進來的時候為空 if (mContentParent == null) { mContentParent = generateLayout(mDecor); // 省略部分程式碼 } }
// 初始化DecorView
protected DecorView generateDecor(int featureId) {
// 省略部分程式碼
// 建立DecorView
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) { TypedArray a = getWindowStyle(); // 設定 當前Activity配置的主題theme // 視窗是否是浮動的 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); } // 視窗是否有標題欄 if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { requestFeature(FEATURE_NO_TITLE); } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { // Don't allow an action bar if there is no title. requestFeature(FEATURE_ACTION_BAR); } // 視窗中ActionBar是否在佈局空間內 if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) { requestFeature(FEATURE_ACTION_BAR_OVERLAY); } if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) { requestFeature(FEATURE_ACTION_MODE_OVERLAY); } if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) { requestFeature(FEATURE_SWIPE_TO_DISMISS); } // 視窗是否全屏顯示 if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) { setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())); } ... ... //視窗狀態列顏色配置 if (!mForcedStatusBarColor) { mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000); } //視窗導航欄顏色配置 if (!mForcedNavigationBarColor) { mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000); } if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) { if (a.getBoolean( R.styleable.Window_windowCloseOnTouchOutside,false)) { setCloseOnTouchOutsideIfNotSet(true); } } WindowManager.LayoutParams params = getAttributes(); //輸入法配置 if (!hasSoftInputMode()) { params.softInputMode = a.getInt( R.styleable.Window_windowSoftInputMode, params.softInputMode); } if (a.getBoolean(R.styleable.Window_backgroundDimEnabled, mIsFloating)) { /* All dialogs should have the window dimmed */ if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) { params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; } if (!haveDimAmount()) { params.dimAmount = a.getFloat( android.R.styleable.Window_backgroundDimAmount, 0.5f); } } //設定當前Activity的出現動畫效果 if (params.windowAnimations == 0) { params.windowAnimations = a.getResourceId( R.styleable.Window_windowAnimationStyle, 0); } // The rest are only done if this window is not embedded; otherwise, // the values are inherited from our container. if (getContainer() == null) { if (mBackgroundDrawable == null) { if (mBackgroundResource == 0) { mBackgroundResource = a.getResourceId( R.styleable.Window_windowBackground, 0); } if (mFrameResource == 0) { mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0); } mBackgroundFallbackResource = a.getResourceId( R.styleable.Window_windowBackgroundFallback, 0); if (false) { System.out.println("Background: " + Integer.toHexString(mBackgroundResource) + " Frame: " + Integer.toHexString(mFrameResource)); } } mElevation = a.getDimension(R.styleable.Window_windowElevation, 0); mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false); mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT); } } // 為視窗新增 decor根佈局 // Inflate the window decor. int layoutResource; int features = getLocalFeatures(); if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = com.android.internal.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 = com.android.internal.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( com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = com.android.internal.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( com.android.internal.R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) { layoutResource = com.android.internal.R.layout.screen_action_bar_overlay; } else { layoutResource = com.android.internal.R.layout.screen_action_bar; } } else { layoutResource = com.android.internal.R.layout.screen_title; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode; } else { layoutResource = com.android.internal.R.layout.screen_simple; } mDecor.startChanging(); // 通過佈局填充器LayoutInflater將layoutResource填充為佈局,載入到decor上 View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); // 找到ID為com.android.internal.R.id.content的ViewGroup佈局並進行返回 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); } } // Remaining setup -- of background and title -- that only applies // to top-level windows. // 設定視窗的背景標題等 if (getContainer() == null) { Drawable drawable = mBackgroundDrawable; if (mBackgroundResource != 0) { drawable = getContext().getResources().getDrawable(mBackgroundResource); } mDecor.setWindowBackground(drawable); drawable = null; if (mFrameResource != 0) { drawable = getContext().getResources().getDrawable(mFrameResource); } mDecor.setWindowFrame(drawable); if (mTitleColor == 0) { mTitleColor = mTextColor; } if (mTitle != null) { setTitle(mTitle); } setTitleColor(mTitleColor); } mDecor.finishChanging(); return contentParent; }
在這裡先總結一下:
1.我們知道在Activity中設定setContentView()首先會去執行到Activity的setContentView()方法
2.而Activity的setContentView會去執行到Window類裡面,而Window裡面的setContentView()方法是個抽象方法,最終會執行到Window的具體類PhoneWindow裡面的setContentView()方法裡面
3.在PhoneWinow裡面首先會去判斷mContentParent是否為空,那麼mContentParent是什麼呢?其實mContentParent是DecorView類的根佈局,而DecorView是PhoneWindow的一個內部類
4.當mContentParent為空的時候,會去例項化DecorView,同時在例項化DecorView的時候會去設定狀態列的一些屬性並且找到一個ID為com.android.internal.R.id.content的ViewGroup佈局並進行返回
5.最終執行到setContentView()方法的第16行mLayoutInflater.inflate(layoutResID, mContentParent);其中layoutResID為我們傳遞進去的佈局檔案,mContentParent為ID為com.android.internal.R.id.content的ViewGroup
那麼mLayoutInflater.inflate(layoutResID, mContentParent);最終會執行到哪裡呢?
檢視原始碼知道,最終會執行到LayoutInflater的inflate方法裡面
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
// XmlResourceParser是專門用來解析XML檔案的
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
// 省略部分程式碼
// attrs是傳入的佈局layout在xml檔案裡面設定的屬性集合
final AttributeSet attrs = Xml.asAttributeSet(parser);
// 將ViewGroup型別的引數root賦值給result
View result = root;
// temp是傳入的引數resource的根佈局View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
// 例項化temp檢視內的所有子檢視
rInflateChildren(parser, temp, attrs, true);
if (root != null) {
// 根據attrs屬性集,建立佈局引數params
params = root.generateLayoutParams(attrs);
// 如果temp不需要新增到root中,那麼為temp設定佈局引數params
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
if (root != null && attachToRoot) {
// 將temp新增到root中,並使用佈局引數params
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
// 將temp賦值給result(在此之前,result==root)
result = temp;
}
return result;
}
}
到這裡setContentView就分析完了,現在我們回過頭來看看setContentView()方法到底做了什麼?
其實答案很簡單,setContentView()方法只是載入了我們設定的佈局檔案並對裡面的屬性進行了解析,其他的事情什麼都沒做,那麼我們設定的佈局是如何顯示在介面上的呢?
哈哈哈,相信大家都已經猜到了,最終的佈局繪製是通過Activity的生命週期來實現的,那麼下結我們就一起來分析一下View的繪製流程