這篇文章主要解決一個疑惑 “layout目錄下XML文件是如何轉化爲View對象”。
源碼閱讀不應該是味如嚼蠟,帶着問題去刨根問底可能會發現不同的世界。
整篇文章較長,總共分爲5個小結,如果你能完整地閱讀完這5節並仔細琢磨其細節,相信必定會有很大收穫。如有錯誤之處,望指出
- XML to View之日常
- 讀懂LayoutInflater並不難
- 尋找XML解析入口是關鍵
- 遞歸解析子節點
- 反射標籤View初現
你的任何問題,都可以在源碼中得到答案,只是你願不願意而已。
XML to Vieww之日常
下面爲加載view的一種實現,對絕大多數Android工程師而言絕不陌生。
1 2 |
LayoutInflater layoutInflater = LayoutInflater.from(context); View layout = layoutInflater.inflate(R.layout.layout, parent, false); |
平時我們覆蓋Activity#setContentView的時候,你可能留意過:
1 2 3 4 5 |
Activity.java public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } |
如果處於好奇心的驅使,對getWindow().setContentView(layoutResID)一探究竟,那下面的代碼你可能會見過。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
PhoneWindow.java @Override public void setContentView(int layoutResID) { //... if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { //重點看這裏 mLayoutInflater.inflate(layoutResID, mContentParent); } //... } |
如果你不瞭解PhoneWindow
,或者除inflate方法外都不瞭解。沒有關係,我們只解決當前的問題。從我們接觸的代碼中至少可以發現LayoutInflater
就是幫助我們把xml轉化爲view的中介。事實上它就是扮演這樣的角色。
小結: 通過LayoutInflater#inflate可加載layout佈局生成View對象
讀懂LayoutInflater並不難
註釋幫你讀懂一個類,api教你使用一個類,摘了句核心的註釋語句:
1 2 3 4 5 6 7 8 |
1. Instantiates a layout XML file into its corresponding {@link android.view.View} objects. 將XML文件轉化爲對應的View對象。 2. use {@link android.app.Activity#getLayoutInflater()} or {@link Context#getSystemService} to retrieve a standard LayoutInflater. 使用註釋中兩個方法可以獲取一個標準的LayoutInflater。 3. To create a new LayoutInflater with an additional {@link Factory} for your own views, you can use {@link #cloneInContext} to clone an existing ViewFactory, and then call {@link #setFactory} on it to include your Factory. 爲了創建帶有自定義Factory的LayoutInflater對象,你可以克隆一個當前已存在的ViewFactory並通過調用setFactory來設置自己的Factory對象。 |
LayoutInflater
幹什麼大概瞭解了,怎麼獲取也知道了,第三條Factroy
是什麼概念?根據註釋可以猜測,可能是用於生產View
的工廠。要產生Android大量的widget對象,這種模式再適合不過。
小結: 通過獲取標準的LayoutInflater對象我們可以將XML文件轉化爲對應的View對象,並且可能可以通過自定義Factory來控制生成View的邏輯。
尋找XML解析入口是關鍵
除了註釋,可以利用Ctrl+12(AS快捷)來查看方法。LayoutInflater
有許多方案,有Factroy
相關的方法,還有我們熟悉的inflate
方法,先看看inflate
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
LayoutInflater.java // 1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); } // 2. public View inflate(XmlPullParser parser, @Nullable ViewGroup root) { return inflate(parser, root, root != null); } // 3. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); //... final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } } |
源碼中一共有四個inflate方法,上述顯示前三個方法。1處調用3處的方法,先獲取Resources
對象,再獲取XmlResourceParser
對象,最終調用2處方法,而2處方法中調用的是第四個方法。摘取了核心的註釋及邏輯。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
LayoutInflater.java /** * Inflate a new view hierarchy from the specified XML node. * 從特定的XML節點中解析View對象 * For performance reasons, view inflation relies heavily on pre-processing of * XML files that is done at build time. Therefore, it is not currently possible * touse LayoutInflater with an XmlPullParser over a plain XML file at runtime. * 爲了提升性能,需要對XML文件做預處理 */ public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final Context inflaterContext = mContext; //1. 獲取xml佈局屬性,如wigth height等 final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; //... // 2. 查找根節點 int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } // 3. 如果不是根節點不是START_TAG if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); // 4. 如果是Merge標籤 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 { // 5. 找到根節點View final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { // 5. 根據AttributeSet生成layout params 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); } } // 6. inflate所有子節點 rInflateChildren(parser, temp, attrs, true); // 7. 如果滿足附着條件,則添加根節點View if (root != null && attachToRoot) { root.addView(temp, params); } // 8. 如果父容器爲空或者不附着在父容器,則返回根節點View。反之,則返回父容器 if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { //... } catch (Exception e) { //... } finally { //... } return result; } } |
梳理下上述邏輯,可以得到以下大致的流程:
- 先解析XML文件中的
Attribute
屬性,保存在屬性集 - 遍歷查找到第一個根節點。如果是
<merge>
,則把父容器作爲父佈局參數。反之,獲取到根view,則把根view作爲父佈局參數,走rInflateChildren邏輯。 - 根據父容器不爲是否爲空和是否需要附着在父容器來返回不同結果。
基於上述解析第一個根節點的邏輯基礎上可以猜測:rInflateChildren的處理邏輯應該是遞歸處理根節點下的節點樹並解析成對應的View
樹,放父佈局中。
小結: inflate方法解析出根節點並根據根節點的類型設置不同的父佈局,剩餘子節點樹遞歸解析成View樹添加到父佈局中
遞歸解析子節點
無論根節點是否是<merge>
,最終都會調用rInflate方法,只是<merge>
不作爲子View樹
的父容器,而是使用其上層的ViewGroup
作爲容器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
LayoutInflater.java //1. 本質還是調用rInflate final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate); } /** * Recursive method used to descend down the xml hierarchy and instantiate * views, instantiate their children, and then call onFinishInflate(). * 遞歸解析xml結構並實例化View對象,最後調用onFinishInflate方法 */ void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { //2. 獲取當前元素的高度。除了根元素(定義爲0)外,每檢索到一個start tag標誌則遞增1 final int depth = parser.getDepth(); int type; //3. 內層元素且非結束符號 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { //4. 如果不是start tag,則調過,直到檢索到start tag if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); //5. 如果是requestFocus標籤 if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); //6. 如果是tag標籤 } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); //7. 如果是include標籤 } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); //8. 如果是merge標籤 } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); //9. 其他標籤,比如TextView或者LinearLayout等 } else { final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); //10. 遞歸處理子佈局 rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } //11. 是否回調onFinishInFlate if (finishInflate) { parent.onFinishInflate(); } } |
1處表明,rInflateChildren函數本質上是調用rInflate函數處理,只是區分語境而做了不同命名而已。
5-7處解析處理特殊標籤,這裏不做詳細解析,感興趣的童鞋可以直接閱讀源碼。
2-11處爲rInflate函數的主要邏輯,也是遞歸解析的關鍵所在。寫一個遞歸函數,核心是理解遞歸的終止及觸發條件,舉個栗子。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0" encoding="utf-8"?> <!-- depth=0 --> <LinearLayout> <!-- depth=1 --> <TextView> <!-- depth=2 --> </TextView> <!-- depth=2 --> <LinearLayout> <!-- depth=2 --> <TextView/> <!-- depth=3 --> </LinearLayout> <!-- depth=2 --> </LinearLayout> <!-- depth=1 --> |
針對這個栗子,我只說關鍵的邏輯說明,在遞歸子節點樹的時候實際上外層已經解析過了,這裏只是演示下XmlPullParser
的大致解析邏輯。
- 第一次調用rInflate函數,depth=0,解析第一個
<LinearLayout>
,
爲START_TAG
,實例化LinearLayout
對象L1,觸發第一次遞歸 。 - depth=1,解析到第一個
<TextView>
,爲START_TAG
,實例化TextView
對象,並把自己添加到L1,觸發第二次遞歸 。 - depth=2,解析到第一個
</TextView>
,爲END_TAG
不滿足while條件,結束第二次遞歸 。 - 回到第一次遞歸while循環,depth=2,解析第一個
<LinearLayout>
,爲START_TAG
,實例化LinearLayout
對象L2,觸發第三次遞歸。 - depth=2,解析到
<TextView/>
,實際上這種寫法等同於第一個TextView
,對於解析器而言,都是按照START_TAG
->TEXT
->END_TAG
的解析邏輯。解析到第二個TextView
,並把自己添加到L2,觸發第四次遞歸 。 - depth=3,不滿足while條件,結束第四次遞歸 。
- depth=2,解析到
第一個</LinearLayout>
,爲END_TAG
不滿足while條件,結束第三次遞歸 。 - depth=1,解析到
第二個</LinearLayout>
,爲END_TAG
不滿足while條件,結束第一次遞歸 。
上述的栗子基本走了一遍rInflate函數的邏輯。而遞歸的邏輯可能有點繞,不過理解起來並不困難。相信有心看到這裏的童鞋都比較聰明哈。在整個遞歸過程中,我們又多了一個問題,遞歸中怎麼創建View
的呢?沒錯,,就是9處的createViewFromTag,不急,走完這步先小結。
小結: 遞歸解析的流程從XML文件次層開始向內解析節點樹,間接調用rInflate函數遞歸檢索所有節點並把符合轉化爲View
的節點轉化爲View
對象
反射標籤View初現
繼續上一節的邏輯,可以通過createViewFromTag的方法的把Tag
轉化爲View
。還是相信,代碼會幫你解除所有疑惑。
1 2 3 4 |
LayoutInflater.java final String name = parser.getName(); //... final View view = createViewFromTag(parent, name, context, attrs); |
name即獲取當前節點的名稱,比如<TextView>
獲取的是TextView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
//1. 默認是ignoreThemeAttr爲false private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) { return createViewFromTag(parent, name, context, attrs, false); } View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { //2. 提取class屬性,並把class的值作爲tag名 //WTF?還有這種操作,也就是意味着,view標籤可以隨便變成 //任意一個標籤,比如說LinearLayout,RelativeLayout等 if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } // 3.如果採用主題配置屬性,則構建一個ContextThemeWrapper 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(); } // 4. what?如果是blink,則構建BlinkLayout if (name.equals(TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(context, attrs); } // 5. 典型工廠構建。wait,這裏的factory不正是在介紹的LayoutInflater時涉及到的Factory嗎? try { View view; // 6.優先通過mFactory2處理,再通過mFacotry處理 if (mFactory2 != null) { view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, context, attrs); } else { view = null; } // 7.如果上述工廠都無法生成View,則使用私有工廠處理 if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } // 8.如果私有工廠都處理不了,那隻能通過默認手段 // createView(onCreateView調用createView實現) 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) { //... } catch (ClassNotFoundException e) { //... } catch (Exception e) { //... } } |
上述代碼的邏輯大致是:
- name轉化。如果name是
View
,則需要轉化爲對應class屬性的值; - name轉成
View
。如果name是blink,則直接構建BlinkLayout
並返回,否則先讓工廠生成,如果工廠都生成不了,則使用默認生成。
既然mFactory,mFactory2,mPrivateFactory是優先構建View
對象,所以有必要了解這些是什麼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
LayoutInflater.java //1. mFactory public interface Factory { /** * Hook you can supply that is called when inflating from a LayoutInflater. * You can use this to customize the tag names available in your XML * layout files. */ public View onCreateView(String name, Context context, AttributeSet attrs); } //2. mFactory2,mPrivateFactory public interface Factory2 extends Factory { /** * Version of {@link #onCreateView(String, Context, AttributeSet)} * that also supplies the parent that the view created view will be * placed in. */ public View onCreateView(View parent, String name, Context context, AttributeSet attrs); } |
實際上只是一個暴露構建View
方法的接口,而接口對象賦值是在構造器內和set方法中。LayoutInflater
是一個抽象類,所以先檢查其實現類。
通過源碼的引用檢索,可以發現,實現繼承LayoutInflater
的類一共有兩個個
1 2 3 |
android源碼類 AsyncLayoutInflater.java的私有靜態內部類BasicInflater PhoneLayoutInflater |
其中PhoneLayoutInflater
爲android系統默認實現的LayoutInflater對象,也就是我們經常獲取的LayoutInflater對象啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
PhoneLayoutInflater.java public PhoneLayoutInflater(Context context) { super(context); } protected PhoneLayoutInflater(LayoutInflater original, Context newContext) { super(original, newContext); } //1. 外部調用 public LayoutInflater cloneInContext(Context newContext) { return new PhoneLayoutInflater(this, newContext); } |
實際上只有1處被外部調用,通過檢索有多出引用。其中有一處引用比較有趣是在Activity
,看看就散了哈。
1 2 3 4 5 6 7 8 9 |
@Override public LayoutInflater onGetLayoutInflater() { //1. 最終調用window層的layoutInfalter,這裏不多做解析 final LayoutInflater result = Activity.this.getLayoutInflater(); if (onUseFragmentManagerInflaterFactory()) { return result.cloneInContext(Activity.this); } return result; } |
既然實現類沒有處理Factory
,只能看setFactory和setFactory2方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
LayoutInflater.java /** * Attach a custom Factory interface for creating views while using * this LayoutInflater. This must not be null, and can only be set once; * after setting, you can not change the factory. This is * called on each element name as the xml is parsed. If the factory returns * a View, that is added to the hierarchy. If it returns null, the next * factory default {@link #onCreateView} method is called. * * <p>If you have an existing * LayoutInflater and want to add your own factory to it, use * {@link #cloneInContext} to clone the existing instance and then you * can use this function (once) on the returned new instance. This will * merge your own factory with whatever factory the original instance is * using. */ public void setFactory(Factory factory) { //1. 保證無法重複setFactory if (mFactorySet) { throw new IllegalStateException("A factory has already been set on this LayoutInflater"); } //2. 保證factory不能爲null if (factory == null) { throw new NullPointerException("Given factory can not be null"); } //3. 設置mFactory mFactorySet = true; if (mFactory == null) { mFactory = factory; } else { mFactory = new FactoryMerger(factory, null, mFactory, mFactory2); } } public void setFactory2(Factory2 factory) { if (mFactorySet) { throw new IllegalStateException("A factory has already been set on this LayoutInflater"); } if (factory == null) { throw new NullPointerException("Given factory can not be null"); } //4. 設置mFactory和mFactory mFactorySet = true; if (mFactory == null) { mFactory = mFactory2 = factory; } else { mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2); } } /** * @hide for use by framework */ public void setPrivateFactory(Factory2 factory) { if (mPrivateFactory == null) { mPrivateFactory = factory; } else { mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory); } } //5. factory合併 private static class FactoryMerger implements Factory2 { private final Factory mF1, mF2; private final Factory2 mF12, mF22; FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) { mF1 = f1; mF2 = f2; mF12 = f12; mF22 = f22; } public View onCreateView(String name, Context context, AttributeSet attrs) { View v = mF1.onCreateView(name, context, attrs); if (v != null) return v; return mF2.onCreateView(name, context, attrs); } public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs) : mF1.onCreateView(name, context, attrs); if (v != null) return v; return mF22 != null ? mF22.onCreateView(parent, name, context, attrs) : mF2.onCreateView(name, context, attrs); } } |
上述的註釋非常重要,Factory
工廠不能爲空且只能設置一次。從1-4處也可知兩個set方法的調用是互斥的。如果開發者希望設置自定義的工廠,則需要從原來的LayoutInflater
中複製一個對象,然後調用setFactory或者setFactory2方法設置解析工廠。另外setPrivateFactory是系統底層調用的,所以不開放對外設置。
那麼setFactory或者setFactory2在android中如何設置的呢?通過AS的檢索功能,顯示如圖,這些類可在v4包中查閱到。
setFactory的調用來自於BaseFragmentActivityGingerbread.java
和LayoutInflaterCompatBase.java
。
BaseFragmentActivityGingerbread.java
處理低於3.0版本的activity版本都需要設置解析fragmen標籤LayoutInflaterCompatBase.java
,則暴露FactoryWrapper
分裝類,根據不同的版本實現不同的LayoutInflaterFactory
。常規的業務比如換膚,即可在這裏實現自己的Factory
達到View
換膚效果。
setFactory2的調用來自於LayoutInflaterCompatHC.java
和LayoutInflaterCompatLollipop
!因此我們有理由相信,HC版本(3.0)和Loolopop版本(5.0)必定需要兼容解析一些佈局,哪些呢?2333,自己想想。其中LayoutInflaterCompatHC.java
實際上內部實現了一個靜態的FactoryWrapperHC
類,該類繼承1-2中的FactoryWrapper
類,用來提供HC版本的Factory
而已。對於LayoutInflaterCompatLollipop
,基本邏輯也是如此。
花了挺長的篇幅講了Factory
相關的邏輯。到這裏,大致能明白,實際上Factory
大致就是一種攔截思想。優先通過選擇自定義Factory
來構建View
對象。那麼如果這些Factory
都沒有的話,則需要走默認邏輯,即LayoutInflater#createView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
/** * Low-level function for instantiating a view by name. This attempts to * instantiate a view class of the given <var>name</var> found in this * LayoutInflater's ClassLoader. * * 通過Tag名稱來反射構建View對象 */ public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { //1. 內存緩存構造器,通過tag名唯一指定構造器 Constructor<? extends View> constructor = sConstructorMap.get(name); //2. 這裏你可能會有疑惑,需要verifyClassLoader的輔助判斷是因爲有可能出現相同包名 //但是不同的類的情況(插件加載),所需還需要ClassLoader輔助判斷才能唯一識別一個構建起 if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Class<? extends View> clazz = null; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); //3. 如果構造器爲null if (constructor == null) { //4. 默認是android.view.作爲前綴 //而之前我們說到的PhoneLayoutInflater前綴包含3個 // "android.widget." // "android.webkit." // "android.app." 所以,你懂的 clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); //5. 過濾一些不需要加載的View,比如RemoteView等 if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { //6. 記錄是否被允許加載 if (mFilter != null) { // Have we seen this name before? Boolean allowedState = mFilterMap.get(name); if (allowedState == null) { // New class -- remember whether it is allowed clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); boolean allowed = clazz != null && mFilter.onLoadClass(clazz); mFilterMap.put(name, allowed); if (!allowed) { failNotAllowed(name, prefix, attrs); } } else if (allowedState.equals(Boolean.FALSE)) { failNotAllowed(name, prefix, attrs); } } } Object[] args = mConstructorArgs; args[1] = attrs; //7. 反射構建View final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } return view; } catch (NoSuchMethodException e) { //... } catch (ClassCastException e) { //... } catch (ClassNotFoundException e) { //... } catch (Exception e) { //... } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } |
上述代碼可知,LayoutInflater
內存緩存了構造器,過濾部分tag後通過類名反射View對象。
其中4處給我們的啓示是,實際上系統PhoneLayoutInflater
只是擴展了類名的路徑,讓LayoutInflater
識別更多的類而已。如果正真想要處理View定製樣式,還是需要自定義LayoutInflater.Factory2
。
小結: 遞歸XML文件得到的標籤名來轉化成View的優先級爲:mFactory2 > mFactory > mPrivateFactory > onCreateView。憑接全限定類名並經過剔除篩選後反射得到View對象。
至此,android中XML文件如何轉化爲View對象的流程已完整走通。
希望該文章能對你有一點幫助。
出處:yummyLau,原文鏈接(https://yummylau.com/2018/01/30/源碼解析_2018-01-30_xml佈局時如何生成view/)