摘要:之前的也被取代為。但的內部回調是由來實現的。一個包含這一個縱向的,內部是一個,是一個包含了內容部分和標題部分的容器。是的子類,我們編寫的布局就是被添加到它的內部。至此,的流程就走完了。
1. AppCompatDelegate 的 setContentView()
分析 Android 中的 View,我們先從進入應用的看到的的一個 View 入手,第一個 View 就是 通過 setContentView() 這個方法進行加載的。我們來看 setContentView() 的源碼:
public void setContentView(@LayoutRes int layoutResID) { this.getDelegate().setContentView(layoutResID); }
AppCompatActivity 中的 setContentView() 又調用了 AppCompatDelegate 中的 setContentView() 方法,那 AppCompatDelegate 是做什么的呢?
AppCompat 出現在 v7 包,它的作用是讓 API 等級在 7 之上的設備也能使用 ActionBar,在 v7:21 之后,AppCompat 可以讓 API 在 7 之上的設備使用 MD、ToolBar 等效果。之前的 ActionBarActivity 也被取代為 AppCompatActivity。但 AppCompatActivity 的內部回調是由 AppCompatDelegate 來實現的。
AppCompatDelegate 是一個抽象類,它的實現類是 AppCompatDelegateImpl,現在看 AppCompatDelegateImpl 中的 setContentView() 方法:
public void setContentView(int resId) { // 創建 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() 方法為視圖創建 DecorView,
private void ensureSubDecor() { if (!this.mSubDecorInstalled) { // DecorView 不存在,調用 createSubDecor() 創建 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() { // 獲取設置的主題屬性 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 { // 根據主題的屬性進行設置 if (a.getBoolean(styleable.AppCompatTheme_windowNoTitle, false)) { // 在 requestWindowFeature() 方法中 // 設置 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; // 根據不同的設置,給 subDecor 填充內容 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; } } }
創建好 DecorView 之后,DecorView 會被添加到 Windows(實現類是 PhoneWindow) 中,然后返回 DecorView。
并且 DecorView 是視圖的頂級容器,我們可以通過 Android Studio 的 Layout Inspector 來查看一個界面的 View Tree。
一個 DecorView 包含這一個縱向的 LinearLayout,LinearLayout 內部是一個 FrameLayout,FrameLayout 是一個包含了內容部分和標題部分的容器。
在 AppCompatDelegateImpl 中的 setContentView() 方法中還有一句:
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
這句代碼得到的 contentParent 就是剛剛創建的 DecorView 中的 內容根部局(id/content (ContentFrameLayout))。
ContentFrameLayout 是 FrameLayout 的子類,我們編寫的 xml 布局就是被添加到它的內部。
然后查看為內容根布局添加視圖的過程。
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) + ")"); } // 獲取解析當前布局 xml 文件的 parser 對象 final XmlResourceParser parser = res.getLayout(resource); try { // 調用 inflate() 方法,開始解析 xml 文件,并返回得到界面 return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
在上述方法中,會先獲取解析 xml 文件的 parser 對象,然后調用另一個 infalte() 方法進行解析。
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 循環遍歷 xml 中的節點,直到找到 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 節點,執行 rInflate() 方法,按照層次遞歸的去實例化 xml 文件的子項 if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("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 節點,就通過 tag 標簽創建一個 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. // 將創建的 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 文件中的起始節點,如果起始節點是 merge,就執行 rInflate() 方法,如果不是 merge,就執行 createViewFromTag() 方法去創建一個新的 View,最后把 View 添加到內容根部局中。
4. createViewFromTag()現在看 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 對象去創建 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 是一個接口,Factory2 是繼承了 Factory 的接口,它們都有個一個 onCreateView() 的方法。
5. onCreateView()我們去看它們在 AppCompatDelegateImpl 中的實現:
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()); }
方法的最后可以看出創建視圖的工作交給了 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() 等源碼可以發現,creatView() 方法把常用的組件都變成了 AppCompat 的類型,從而達到了兼容的目的。
至此,setContentView() 的流程就走完了。但是添加好布局文件之后,視圖并不會顯示到界面上,還需要通過 WindowsManagerService 去渲染界面才能使界面顯示。這部分到內容在后面會講到。
零碎的東西很多,為了方便大家記憶,我把上面的內容做成了思維導圖,需要的朋友可以保存下來,偶爾看一下,幫助自己記憶。
歡迎關注本文作者:
掃碼關注并回復「干貨」,獲取我整理的千G Android、iOS、JavaWeb、大數據、人工智能等學習資源。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74405.html
摘要:隨著微信和的不斷普及,現在微信和留言也已經成為了甩鍋證據的一部分,經常郵件里面大量粘貼微信聊天截圖,職場上的宮心斗不比電視劇里面的差。 對有些職場人來講,甩鍋就是一種生存手段。 01.從大學打籃球說起 上大學的時候喜歡打籃球,然后我又特別喜歡搶籃板,經常是跳起來的時候沒...
摘要:前段時間,前同事跳槽,機緣巧合下面了阿里,本來憑著試一試的態度,卻不料好事成雙,拿到了,而且薪資也了。面就沒啥東西可聊的,基本上就是對此次面試的一個評價定薪等等一些之內的話題。如果是現場面試,記得關注當天的天氣,提前查一下路線。 ...
摘要:什么是消息機制說到消息機制,作為一名開發者一定先想到的是。但是,在主線程中創建的時候,我們并沒有看到的執行,這是因為在線程,即的創建過程中,已經被創建好了。將新消息插入到之前,頭消息之后。 1. 什么是消息機制 說到消息機制,作為一名 Android 開發者一定先想到的是 Handler。Handler 就是 Android 消息機制的上層接口,我們可用通過 Handler 輕松的在不...
閱讀 3061·2021-11-23 09:51
閱讀 1040·2021-09-02 15:21
閱讀 3005·2019-08-30 13:56
閱讀 1829·2019-08-29 14:12
閱讀 708·2019-08-29 13:53
閱讀 1664·2019-08-29 11:32
閱讀 1325·2019-08-29 11:25
閱讀 1493·2019-08-28 17:51