# 1. AppCompatDelegate 的 setContentView()
分析 Android 中的 View蛾派,我們先從進(jìn)入應(yīng)用的看到的的一個 View 入手个少,第一個 View 就是 通過 setContentView() 這個方法進(jìn)行加載的夜焦。我們來看 setContentView() 的源碼:
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
AppCompatActivity 中的 setContentView() 又調(diào)用了 AppCompatDelegate
中的 setContentView() 方法,那 AppCompatDelegate
是做什么的呢巷波?
AppCompat 出現(xiàn)在 v7 包,它的作用是讓 API 等級在 7 之上的設(shè)備也能使用 ActionBar锉屈,在 v7:21 之后垮耳,AppCompat 可以讓 API 在 7 之上的設(shè)備使用 MD、ToolBar 等效果俊嗽。之前的 ActionBarActivity 也被取代為 AppCompatActivity绍豁。但 AppCompatActivity 的內(nèi)部回調(diào)是由 AppCompatDelegate 來實(shí)現(xiàn)的豌研。
AppCompatDelegate 是一個抽象類,它的實(shí)現(xiàn)類是 AppCompatDelegateImpl鬼佣,現(xiàn)在看 AppCompatDelegateImpl 中的 setContentView() 方法:
public void setContentView(int resId) {
// 創(chuàng)建 DecorView晶衷,DecorView 是視圖中的頂級 View
this.ensureSubDecor();
// 獲取 DecorView 中的 content 部分
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
// 將我們編寫的界面填充到 content 中
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}
# 2. DecorView
在 AppCompatDelegateImpl 的 setContentView() 中阴孟,通過 ensureSubDecor()
方法為視圖創(chuàng)建 DecorView永丝,
private void ensureSubDecor() {
if (!this.mSubDecorInstalled) {
// DecorView 不存在,調(diào)用 createSubDecor() 創(chuàng)建 DecorView
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);
}
}
}
private ViewGroup createSubDecor() {
// 獲取設(shè)置的主題屬性
TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
// 如果使用的主題不是 Theme.AppCompat哥牍,或者沒又繼承自 Theme.AppCompat嗅辣,拋出異常澡谭。
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 {
// 根據(jù)主題的屬性進(jìn)行設(shè)置
if (a.getBoolean(styleable.AppCompatTheme_windowNoTitle, false)) {
// 在 requestWindowFeature() 方法中
// 設(shè)置 this.mWindowNoTitle = true
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);
ViewGroup subDecor = null;
// 根據(jù)不同的設(shè)置蛙奖,給 subDecor 填充內(nèi)容
if (!this.mWindowNoTitle) {
if (this.mIsFloating) {
// Dialog 的主題
subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
this.mHasActionBar = this.mOverlayActionBar = false;
} else if (this.mHasActionBar) {
// 添加 ActionBar
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);
}
});
}
}
// 把 DecorView 添加到 Window 上 并且返回 DecorView
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);
ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
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);
}
}
// 把 DecorView 添加到 Window 上
this.mWindow.setContentView(subDecor);
contentView.setAttachListener(new OnAttachListener() {
public void onAttachedFromWindow() {
}
public void onDetachedFromWindow() {
AppCompatDelegateImpl.this.dismissPopups();
}
});
return subDecor;
}
}
}
創(chuàng)建好 DecorView 之后崎脉,DecorView 會被添加到 Windows(實(shí)現(xiàn)類是 PhoneWindow) 中,然后返回 DecorView祭衩。
并且 DecorView 是視圖的頂級容器阅签,我們可以通過 Android Studio 的 Layout Inspector 來查看一個界面的 View Tree。
一個 DecorView 包含這一個縱向的 LinearLayout路克,LinearLayout 內(nèi)部是一個 FrameLayout精算,F(xiàn)rameLayout 是一個包含了內(nèi)容部分和標(biāo)題部分的容器碎连。
在 AppCompatDelegateImpl 中的 setContentView() 方法中還有一句:
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
這句代碼得到的 contentParent 就是剛剛創(chuàng)建的 DecorView 中的 內(nèi)容根部局(id/content (ContentFrameLayout))。
ContentFrameLayout 是 FrameLayout 的子類廉嚼,我們編寫的 xml 布局就是被添加到它的內(nèi)部倒戏。
然后查看為內(nèi)容根布局添加視圖的過程。
# 3. LayoutInflater 的 inflate()
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) + ")");
}
// 獲取解析當(dāng)前布局 xml 文件的 parser 對象
final XmlResourceParser parser = res.getLayout(resource);
try {
// 調(diào)用 inflate() 方法傍念,開始解析 xml 文件葱椭,并返回得到界面
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
在上述方法中孵运,會先獲取解析 xml 文件的 parser 對象,然后調(diào)用另一個 infalte()
方法進(jìn)行解析驳概。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// 通過 while 循環(huán)遍歷 xml 中的節(jié)點(diǎn),直到找到 root
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
// 如果是 merge 節(jié)點(diǎn)更卒,執(zhí)行 rInflate() 方法稚照,按照層次遞歸的去實(shí)例化 xml 文件的子項(xiàng)
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
// 不是 merge 節(jié)點(diǎn)果录,就通過 tag 標(biāo)簽創(chuàng)建一個 view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
// 將創(chuàng)建的 View 添加到 root 視圖中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
在這個 inflate()
方法中辨萍,會先尋找 xml 文件中的起始節(jié)點(diǎn)返弹,如果起始節(jié)點(diǎn)是 merge
,就執(zhí)行 rInflate()
方法拉背,如果不是 merge并扇,就執(zhí)行 createViewFromTag()
方法去創(chuàng)建一個新的 View,最后把 View 添加到內(nèi)容根部局中土陪。
# 4. createViewFromTag()
現(xiàn)在看 createViewFromTag() 的源碼:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
// 如果 name 的值為 blink鬼雀,返回一個 BlinkLayout
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
// 依次尋找合適的 Factory 對象去創(chuàng)建 View
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
我們看一看代碼中的 mFactory2
源哩、mFactory
鸦做、mPrivateFactory
是什么。
在 LayoutInflater.java 的屬性中坛掠,有如下幾個變量:
private Factory mFactory;
private Factory2 mFactory2;
private Factory2 mPrivateFactory;
Factory
是一個接口,F(xiàn)actory2 是繼承了 Factory 的接口舷蒲,它們都有個一個 onCreateView() 的方法友多。
# 5. onCreateView()
我們?nèi)タ此鼈冊?AppCompatDelegateImpl 中的實(shí)現(xiàn):
public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
if (this.mAppCompatViewInflater == null) {
TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
String viewInflaterClassName = a.getString(styleable.AppCompatTheme_viewInflaterClass);
if (viewInflaterClassName != null && !AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
try {
Class viewInflaterClass = Class.forName(viewInflaterClassName);
this.mAppCompatViewInflater = (AppCompatViewInflater)viewInflaterClass.getDeclaredConstructor().newInstance();
} catch (Throwable var8) {
Log.i("AppCompatDelegate", "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", var8);
this.mAppCompatViewInflater = new AppCompatViewInflater();
}
} else {
this.mAppCompatViewInflater = new AppCompatViewInflater();
}
}
boolean inheritContext = false;
if (IS_PRE_LOLLIPOP) {
inheritContext = attrs instanceof XmlPullParser ? ((XmlPullParser)attrs).getDepth() > 1 : this.shouldInheritContext((ViewParent)parent);
}
return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
}
方法的最后可以看出創(chuàng)建視圖的工作交給了 AppCompatViewInflater 的 createView()
去完成
# 6. createView()
final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
Context originalContext = context;
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
byte var12 = -1;
switch(name.hashCode()) {
case -1946472170:
if (name.equals("RatingBar")) {
var12 = 11;
}
break;
case -1455429095:
if (name.equals("CheckedTextView")) {
var12 = 8;
}
break;
case -1346021293:
if (name.equals("MultiAutoCompleteTextView")) {
var12 = 10;
}
break;
case -938935918:
if (name.equals("TextView")) {
var12 = 0;
}
break;
case -937446323:
if (name.equals("ImageButton")) {
var12 = 5;
}
break;
case -658531749:
if (name.equals("SeekBar")) {
var12 = 12;
}
break;
case -339785223:
if (name.equals("Spinner")) {
var12 = 4;
}
break;
case 776382189:
if (name.equals("RadioButton")) {
var12 = 7;
}
break;
case 1125864064:
if (name.equals("ImageView")) {
var12 = 1;
}
break;
case 1413872058:
if (name.equals("AutoCompleteTextView")) {
var12 = 9;
}
break;
case 1601505219:
if (name.equals("CheckBox")) {
var12 = 6;
}
break;
case 1666676343:
if (name.equals("EditText")) {
var12 = 3;
}
break;
case 2001146706:
if (name.equals("Button")) {
var12 = 2;
}
}
switch(var12) {
case 0:
view = this.createTextView(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 1:
view = this.createImageView(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 2:
view = this.createButton(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 3:
view = this.createEditText(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 4:
view = this.createSpinner(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 5:
view = this.createImageButton(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 6:
view = this.createCheckBox(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 7:
view = this.createRadioButton(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 8:
view = this.createCheckedTextView(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 9:
view = this.createAutoCompleteTextView(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 10:
view = this.createMultiAutoCompleteTextView(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 11:
view = this.createRatingBar(context, attrs);
this.verifyNotNull((View)view, name);
break;
case 12:
view = this.createSeekBar(context, attrs);
this.verifyNotNull((View)view, name);
break;
default:
view = this.createView(context, name, attrs);
}
if (view == null && originalContext != context) {
view = this.createViewFromTag(context, name, attrs);
}
if (view != null) {
this.checkOnClickListener((View)view, attrs);
}
return (View)view;
}
通過 createTextView() 等源碼可以發(fā)現(xiàn),creatView() 方法把常用的組件都變成了 AppCompat
的類型藐窄,從而達(dá)到了兼容的目的酬土。
至此撤缴,setContentView() 的流程就走完了叽唱。但是添加好布局文件之后,視圖并不會顯示到界面上棺亭,還需要通過 WindowsManagerService 去渲染界面才能使界面顯示。這部分到內(nèi)容在后面會講到嗽桩。
零碎的東西很多凄敢,為了方便大家記憶,我把上面的內(nèi)容做成了思維導(dǎo)圖扑庞,需要的朋友可以保存下來拒逮,偶爾看一下,幫助自己記憶栅隐。