【重拾View(二)】——LayoutInflater原始碼解析
前言
看了前一篇文章ofollow,noindex">【重拾View(一)】——setContentView()原始碼解析
我們瞭解到了Activity載入佈局到過程,但是對於View的建立過程還需要進一步的瞭解。熟悉Android的應該都清楚,Android佈局的一大特點就是XML檔案,我們通過編寫XML檔案,便可以輕鬆的建立我們需要的View,這也是和IOS開發的一個很大的區別。但是當我們編寫完一個XML檔案,還需要生成對應可繪製的View物件,這時我們經常使用的方法便是LayoutInflater的inflate()
方法。無論是我們自定義View通過還是Fragment還是我們上篇文章分析的Activity載入佈局的過程,實質都是使用這種方式建立View。所以本篇部落格便對LayoutInflater的原始碼進行分析。
建立物件
要獲取LayoutInflater物件我們經常使用的有三種方式:
- Context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) ;
- LayoutInflater.from(context);
- Activity.getLayoutInflater();
public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
//Activity.java @NonNull public LayoutInflater getLayoutInflater() { return getWindow().getLayoutInflater(); } //PhoneWindow.java @Override public LayoutInflater getLayoutInflater() { return mLayoutInflater; } public PhoneWindow(Context context) { super(context); mLayoutInflater = LayoutInflater.from(context); }
通過上面的原始碼,其實我們可以發現上面三種方式的最終效果都是context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflate原始碼分析
我們最常用的inflate
方法其實有兩種
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); } 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解析器 final XmlResourceParser parser = res.getLayout(resource); try { //解析xml檔案 return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
可以看到inflate方法其實就是做了兩步:
- 建立XML解析器
-
解析XML檔案成View並返回
這裡面我們要注意一下三個引數的傳參,第一個當然就是我們的XML的id,第二個一般是我們傳入的父View,第三個boolean注意當預設我們沒有傳值的時候,預設是root != null
,也就是父View不為空則是true
,為空便是false
。
接下來我們來看一下inflate方法原始碼,看看是怎麼解析xml檔案的。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { //1.執行緒同步 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; //2.首先將root設定為result View result = root; try { // Look for the root node. int type; //3.遍歷尋找佈局的根結點 while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } //4.如果找到的不是根結點,則異常 if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } //5.獲取根結點的名稱 final String name = parser.getName(); if (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " + name); System.out.println("**************************"); } //6.如果是merge節點 if (TAG_MERGE.equals(name)) { //root不能為null,attachToRoot不能為false if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } //7.解析merge節點下的 rInflate(parser, root, inflaterContext, attrs, false); } else { // Temp is the root view that was found in the xml //8.根據解析得到的節點名建立View final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied //9.根據父View建立LayoutParams params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) //10.如果不是attachToRoot,則將LayoutParam設定給View的屬性中 temp.setLayoutParams(params); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // Inflate all children under temp against its context. //11.遞迴解析子佈局 rInflateChildren(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { //12.root不為null,並且attachToRoot,則將View加入到父View中,並將LayoutParams設定 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) { //13.如果root為null或者attachToRoot為false,則返回解析得到的View 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); } //14.返回解析得到的View或者父View return result; } }
首先可以看到,inflate方法是做了執行緒同步的,為了保證執行緒安全。緊接著在註釋2
處可以看到,首先將rootView
賦值給了result
,所以這裡我們需要明白:
有時候我們通過inflate
方法載入的xml檔案得到的View不一定是我們xml中的父佈局,可能是我們傳入的rootView
,如果不明白這個,輕易的就將View強轉
成我們認為的xml中的父佈局,便會產生型別轉換異常
。
接下來的註釋3
和註釋4
可以一起理解,是對於XML
檔案的規則的校驗,可以看到這裡通過while
迴圈查詢佈局中的根結點,當沒找到規定的根結點,在註釋4
處當然便會拋沒有找到根結點的異常。
緊接著在註釋5
處可以看到在找到根結點後,通過getName
方法獲取根結點的名稱,這時我們在註釋6
處就有一個關鍵的判斷了,是關於對於merge
結點的判斷邏輯。
if (TAG_MERGE.equals(name)) { //root不能為null,attachToRoot不能為false if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } //7。解析merge節點下的 rInflate(parser, root, inflaterContext, attrs, false); }
這裡可以看到如果xml
的根結點是merge
標籤,下面有兩個非常重要的判斷條件,root==null|| !attachToRoot
,當條件成立,便會丟擲我們使用merge
標籤經常遇到的異常。這裡細化分析一下,我們root
和attachToRoot
有多種可能。
-
沒有傳attachToRoot
1.1 root=null,則attachToRoot為false
》這時會拋異常
1.2 root!=null,則attahcToRoot為true
》允許解析merge標籤
2.傳入了attachToRoot
2.1 root=null,attachToRoot=true
》拋異常
2.2 roo!=null,attachToRoot=true
》允許解析merge標籤
2.3 root=null,attachToRoot=false
》拋異常
2.4 root!=null,attachToRoot=false
》拋異常
這時這個邏輯就比較清晰了,當我們使用
merge
標籤時,我們的root
一定不能傳入null
,attahToRoot
要不不傳,傳入只能傳入true
,所以這時就可以證明我們使用merge
標籤的規則了:
使用merge標籤必須有父佈局,並且attachToRoot一定為true
當完成這個判斷後面便執行rInflate(parser, root, inflaterContext, attrs, false);
方法,解析merge
標籤下的佈局。關於rInflate
方法後面我們後面再分析。
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
當我們的根佈局不是一個merge
標籤,這時在註釋8
處,便通過createViewFromTag
方法將我們傳入的xml中的父佈局創建出了我們需要的View
物件。關於createViewFromTag
後面我們再分析。
當建立完父佈局的View後,接下來還是和root
和attachToRoot
繞彎彎。
if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied //9。根據父View建立LayoutParams params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) //10。如果不是attachToRoot,則將LayoutParam設定給View的屬性中 temp.setLayoutParams(params); } }
有了上面分析的基礎這裡思路就比較清晰了,可以看到當root
不為null,並且attachToRoot
為false,則將根據root建立的LayoutParams設定給創建出來的View
-temp。所以當root
不為null的情況,當我們傳入attachToRoot=false
,則會把root
的LayoutParam
設定給創建出來的TempView
,當傳入的attachToRoot=true
,則此處
不會設定(注意是此處!)。
緊接著在註釋11
處,解析完根結點後,呼叫rInflateChildren
方法遞迴開始解析子佈局。
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate); }
其實可以看到rInflateChildren
方法實質呼叫的還是rInflate
方法,所以後面我們會一起分析。
if (root != null && attachToRoot) { //12。root不為null,並且attachToRoot,則將View加入到父View中,並將LayoutParams設定 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) { //13。如果root為null或者attachToRoot為false,則返回解析得到的View result = temp; }
這時會看到一個很重要的一點,又和root
和attachToRoot
這兩個變數打交道了,不仔細梳理的話很容易被這兩個變數搞混亂。這裡看到如果root不為null,並且attachToRoot,則將View加入到父View中,並將LayoutParams設定(在addView中會設定)。並且如果root為null或者attachToRoot為false,會將result設定為解析的temp,則返回解析得到的View(最初是將root設定給了result)。
到此我們的infalte
方法其實已經分析完了,這裡我們可以梳理一下關於root
和attachToRoot
的邏輯。
-
root為null
》如果root為null,則返回的就是xml中的父佈局,並且該View也是沒有LayoutParams引數 -
root不為null
2.1 attachToRoot=true
》返回的是root,此時xml已經被加入到了rootView中
2.2 attachToRoot=false
》返回的是xml的父佈局,但是此時xml的父佈局沒有被加入到root中,只是一個單純的View,但是它有LayoutParams,是root型別的LayoutParams.
rInflate方法解析
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { //獲取深度 final int depth = parser.getDepth(); int type; boolean pendingRequestFocus = false; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { pendingRequestFocus = true; consumeChildElements(parser); } else if (TAG_TAG.equals(name)) { //1。解析tag標籤,也就是View.setTag parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { //2。解析include標籤 if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { //3。merge必須為根結點 throw new InflateException("<merge /> must be the root element"); } else { //4。建立當前節點的View final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); //遞迴深度繼續解析 rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } if (pendingRequestFocus) { parent.restoreDefaultFocus(); } if (finishInflate) { parent.onFinishInflate(); } }
可以看到這裡,首先獲取xml的深度,然後依舊是while
迴圈解析,所以下面就是各種標籤情況的處理了。
首先看到註釋1
處,可以看到是對於tag
標籤到解析,當時tag
標籤,則呼叫parseViewTag
方法。
private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs) throws XmlPullParserException, IOException { final Context context = view.getContext(); final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag); final int key = ta.getResourceId(R.styleable.ViewTag_id, 0); final CharSequence value = ta.getText(R.styleable.ViewTag_value); //其實就是setTag... view.setTag(key, value); ta.recycle(); consumeChildElements(parser); }
可以看到,這個方法的本質就是我們常用的setTag
方法,也就是說我們可以有以下這種寫法(雖然不常用)。
<Button android:id="@+id/tag_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="自定義帶監聽事件的通知"> <tag android:id="@+id/tag_id" android:value="@string/app_name" /> </Button>
接下來解析的是我們常用的include
標籤,首先在註釋2
處,我們可以看到,include
是不能再xml的深度=0的,應該也沒人會把include作為xml的根結點吧。。。緊接著便呼叫parseInclude
方法解析include
標籤內的佈局檔案。
private void parseInclude(XmlPullParser parser, Context context, View parent, AttributeSet attrs) throws XmlPullParserException, IOException { int type; if (parent instanceof ViewGroup) { // Apply a theme wrapper, if requested. This is sort of a weird // edge case, since developers think the <include> overwrites // values in the AttributeSet of the included View. So, if the // included View has a theme attribute, we'll need to ignore it. final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); final boolean hasThemeOverride = themeResId != 0; if (hasThemeOverride) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); // If the layout is pointing to a theme attribute, we have to // massage the value to get a resource identifier out of it. int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0); if (layout == 0) { final String value = attrs.getAttributeValue(null, ATTR_LAYOUT); if (value == null || value.length() <= 0) { throw new InflateException("You must specify a layout in the" + " include tag: <include layout=\"@layout/layoutID\" />"); } // Attempt to resolve the "?attr/name" string to an attribute // within the default (e.g. application) package. layout = context.getResources().getIdentifier( value.substring(1), "attr", context.getPackageName()); } // The layout might be referencing a theme attribute. if (mTempValue == null) { mTempValue = new TypedValue(); } if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) { layout = mTempValue.resourceId; } if (layout == 0) { final String value = attrs.getAttributeValue(null, ATTR_LAYOUT); throw new InflateException("You must specify a valid layout " + "reference. The layout ID " + value + " is not valid."); } else { //把inflate方法又寫了一遍,其實感覺Google這裡的寫法是可以優化的。。。。 final XmlResourceParser childParser = context.getResources().getLayout(layout); try { final AttributeSet childAttrs = Xml.asAttributeSet(childParser); while ((type = childParser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty. } if (type != XmlPullParser.START_TAG) { throw new InflateException(childParser.getPositionDescription() + ": No start tag found!"); } final String childName = childParser.getName(); if (TAG_MERGE.equals(childName)) { // The <merge> tag doesn't support android:theme, so // nothing special to do here. rInflate(childParser, parent, context, childAttrs, false); } else { final View view = createViewFromTag(parent, childName, context, childAttrs, hasThemeOverride); final ViewGroup group = (ViewGroup) parent; final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.Include); final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID); final int visibility = a.getInt(R.styleable.Include_visibility, -1); a.recycle(); // We try to load the layout params set in the <include /> tag. // If the parent can't generate layout params (ex. missing width // or height for the framework ViewGroups, though this is not // necessarily true of all ViewGroups) then we expect it to throw // a runtime exception. // We catch this exception and set localParams accordingly: true // means we successfully loaded layout params from the <include> // tag, false means we need to rely on the included layout params. ViewGroup.LayoutParams params = null; try { params = group.generateLayoutParams(attrs); } catch (RuntimeException e) { // Ignore, just fail over to child attrs. } if (params == null) { params = group.generateLayoutParams(childAttrs); } view.setLayoutParams(params); // Inflate all children. rInflateChildren(childParser, view, childAttrs, true); if (id != View.NO_ID) { view.setId(id); } switch (visibility) { case 0: view.setVisibility(View.VISIBLE); break; case 1: view.setVisibility(View.INVISIBLE); break; case 2: view.setVisibility(View.GONE); break; } group.addView(view); } } finally { childParser.close(); } } } else { throw new InflateException("<include /> can only be used inside of a ViewGroup"); } LayoutInflater.consumeChildElements(parser); }
這裡可以看到include標籤記載的佈局必須是ViewGroup,不然會拋異常,剩下的仔細看的話會發現其實和inflate方法幾乎一摸一樣,只不過多了一些屬性判斷,其實感覺Google這裡的寫法是可以優化的。。。
解析完include
方法後在註釋3
處就是解析merge
標籤,這裡可以看到,如果是merge
標籤,便會直接拋異常,當然,在rInflate
方法其實已經是解析子View的方法了。
merge標籤只能用於根結點
else { //4。建立當前節點的View final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); //遞迴深度繼續解析 rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); }
剩下的就是其他常規View的建立了,利用createViewFromTag
方法進行建立,建立完後呼叫rInflateChildren
繼續遞迴深度繼續解析
,然後加入到父佈局中。
最終完成整個XML->View的轉換。
createViewFromTag方法
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) { 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(); } if (name.equals(TAG_1995)) { // Let's party like it's 1995! //1。彩蛋,BlinkLayout,好像是為了慶祝1995年的復活節,是一個Layout,包含後,會一閃一閃 return new BlinkLayout(context, attrs); } try { View view; //2。幾個工廠,可以通過Activity設定後,自定義建立 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('.')) { //3。沒有.說明是原生系統內的控制元件 view = onCreateView(parent, name, attrs); } else { //4。有.說明是自定義控制元件 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; } }
這裡我們首先看一個有趣的地方,那就是註釋1
,這裡可以算是原始碼裡的彩蛋
,從名字上也就看出了這個的不一般~,這裡提供了一個叫做BlinkLayout
的控制元件,好像是為了慶祝1995年的復活節,是一個Layout,包含後,會一閃一閃。
接下來會看到幾個工廠的建立,如果我們實現了幾個工廠,那麼我們就可以自定義View的建立過程,這裡關於工廠的內容後面一篇部落格會進行分析。
如果是常規的使用,我們一般不會使用自定義工廠這種方式的,所以到後面就是一個關鍵判斷。
if (-1 == name.indexOf('.')) { //3。沒有.說明是原生系統內的控制元件 view = onCreateView(parent, name, attrs); } else { //4。有.說明是自定義控制元件 view = createView(name, null, attrs); }
這裡通過標籤名時候包含‘.’作為判斷依據,如果不包含‘.’則說明是原生系統內的控制元件,如果包含則是自定義控制元件。而系統控制元件執行的onCreateView
方法,實質也是createView
方法,只不過加上了"android.view."的字首
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { //最終還是呼叫的是createView方法,只不過加上了"android.view."的字首 return createView(name, "android.view.", attrs); }
接下來我們來看一下createView
方法。
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Constructor<? extends View> constructor = sConstructorMap.get(name); if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Class<? extends View> clazz = null; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it //1。加入的“android.view.”的字首在此處就會用於查詢Class物件 clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } //2。建構函式沒有快取,則直接反射呼叫建構函式,並快取起來 constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { // If we have a filter, apply it to cached constructor 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 lastContext = mConstructorArgs[0]; if (mConstructorArgs[0] == null) { // Fill in the context if not already within inflation. mConstructorArgs[0] = mContext; } Object[] args = mConstructorArgs; args[1] = attrs; //3。利用反射建構函式,進行建立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會設定inflater viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } mConstructorArgs[0] = lastContext; return view; } catch (NoSuchMethodException e) { final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (ClassCastException e) { // If loaded class is not a View subclass final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (ClassNotFoundException e) { // If loadClass fails, we should propagate the exception. throw e; } catch (Exception e) { final InflateException ie = new InflateException( attrs.getPositionDescription() + ": Error inflating class " + (clazz == null ? "<unknown>" : clazz.getName()), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
這個方法還是比較簡單的,首先在註釋1 處我們會發現我們剛才加入的"android.view."的字首會加入到class到全類名 中,用於查詢Class物件。
緊接著在註釋2 其實LayoutInflater查詢快取中的構造器物件,避免頻繁的呼叫反射來查詢使用構造器函式。
private static final HashMap<String, Constructor<? extends View>> sConstructorMap = new HashMap<String, Constructor<? extends View>>();
可以看到這裡就是一個類名作為key,構造器函式作為value的hashMap
。
最後在註釋3 處通過反射呼叫View的建構函式,實現View的建立。
final View view = constructor.newInstance(args);
總結
到此關於LayoutInflater的原始碼分析算是有了一個整體的認識,通過分析inflate
方法我們其實不僅僅可以學習關於載入佈局時root
和attachToRoot
的關係,還可以瞭解到關於include
標籤,merge
標籤,的使用規則。當然我們最終會發現其實LayoutInflater的實質就遞迴解析,解析到類名後,如果是自定義的則是全類名,系統的則自動加上“android.view.”字首,然後通過反射呼叫該類的建構函式,最終創建出View。
下一篇部落格會繼續分析關於LayoutInflater中關於工廠的使用,通過分析我們會發現關於fragment
標籤在系統的解析方式(本篇沒有發現原始碼中有關於fragment標籤的蹤跡)。