源碼解析 之 xml佈局時如何生成view

這篇文章主要解決一個疑惑 “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的大致解析邏輯。

  1. 第一次調用rInflate函數,depth=0,解析第一個<LinearLayout>
    START_TAG,實例化LinearLayout對象L1,觸發第一次遞歸 。
  2. depth=1,解析到第一個<TextView>,爲START_TAG,實例化TextView對象,並把自己添加到L1,觸發第二次遞歸 。
  3. depth=2,解析到第一個</TextView>,爲END_TAG不滿足while條件,結束第二次遞歸 。
  4. 回到第一次遞歸while循環,depth=2,解析第一個<LinearLayout>,爲START_TAG,實例化LinearLayout對象L2,觸發第三次遞歸
  5. depth=2,解析到<TextView/>,實際上這種寫法等同於第一個TextView,對於解析器而言,都是按照START_TAG -> TEXT -> END_TAG的解析邏輯。解析到第二個TextView,並把自己添加到L2,觸發第四次遞歸 。
  6. depth=3,不滿足while條件,結束第四次遞歸 。
  7. depth=2,解析到第一個</LinearLayout>,爲END_TAG不滿足while條件,結束第三次遞歸 。
  8. 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) {
		//...
       }
   }

上述代碼的邏輯大致是:

  1. name轉化。如果name是View,則需要轉化爲對應class屬性的值;
  2. name轉成View。如果name是blink,則直接構建BlinkLayout並返回,否則先讓工廠生成,如果工廠都生成不了,則使用默認生成。
    既然mFactorymFactory2mPrivateFactory是優先構建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,只能看setFactorysetFactory2方法

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.javaLayoutInflaterCompatBase.java

  • BaseFragmentActivityGingerbread.java處理低於3.0版本的activity版本都需要設置解析fragmen標籤
  • LayoutInflaterCompatBase.java,則暴露FactoryWrapper分裝類,根據不同的版本實現不同的LayoutInflaterFactory。常規的業務比如換膚,即可在這裏實現自己的Factory達到View換膚效果。


setFactory2的調用來自於LayoutInflaterCompatHC.javaLayoutInflaterCompatLollipop!因此我們有理由相信,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/)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章