綜述
在aapt編譯apk的過程中,aapt中的XMLNode類會將資源生成一個ResXMLTree對象,并將其序列化到apk文件中。
Android系統中,首先用C++實現了ResXMLParser類,用來解析存儲在apk中的ResXMLTree。然后用Java封裝了一個XmlBlock對象,通過JNI方法調用ResXMLParser。
XmlBlock.Parser類是一個XmlResourceParser接口的實現。XmlResourceParser接口繼承自XmlPullParser接口和AttributeSet接口。其中XmlPullParser是xml pull方式解析xml的標準接口。AttributeSet是訪問資源的封裝接口。
LayoutInflater使用根據上下文獲得的XmlBlock.Parser對象去獲取layout的描述,并生成View對象,將子View附著到父View中。
預備知識
XmlPullParser解析xml的簡要教程
XmlPullParser是一種基于流,根據事件來解析xml的解析器。
我們所要做的事情,主要就是解析文檔開始結束,Tag開始結束這4個事件:
- XmlPullParser.START_DOCUMENT: 文檔開始,該準備什么數據結構或者暫存邏輯,可以在此時把容器new出來。
- XmlPullParser.END_DOCUMENT:文檔結束。可以真正處理暫存的結構了。
- XmlPullParser.START_TAG: Tag起始,一個新的Tag發現了。
- XmlPullParser.END_TAG: 這個Tag結束了,處理處理扔到容器里吧。
- 構建XmlPullParserFactory的實例. 放在try...catch中是因為有XmlPullParserException異常要處理。
try {
XmlPullParserFactory pullParserFactory = XmlPullParserFactory
.newInstance();
- 獲取XmlPullParser的實例
XmlPullParser xmlPullParser = pullParserFactory.newPullParser();
- 設置輸入流 xml文件
xmlPullParser.setInput(
context.getResources().openRawResource(R.raw.xml文件名),
"UTF-8");
- 開始解析
int eventType = xmlPullParser.getEventType();
- 主循環, 直至遇到文檔結束事件XmlPullParser.END_DOCUMENT
try {
while (eventType != XmlPullParser.END_DOCUMENT) {
- XmlPullParser.START_DOCUMENT事件時,處理文檔開始。此處可以準備一個數據結構存儲等。
String nodeName = xmlPullParser.getName();
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
// 處理文檔開始
break;
- 處理節點開始事件
case XmlPullParser.START_TAG:
// 此處開始一個節點,可以讀取下面的屬性和值
break;
- 處理節點結束事件,比如可以將對象加入到容器中。
case XmlPullParser.END_TAG:
// 結束節點
break;
default:
break;
}
- 讀取下一個事件
eventType = xmlPullParser.next();
}
} catch (NumberFormatException e) {
Log.e(TAG, e.getLocalizedMessage());
} catch (IOException e) {
Log.e(TAG, e.getLocalizedMessage());
}
} catch (XmlPullParserException e) {
Log.e("Xml", e.getLocalizedMessage());
}
下面我們來看一下XmlPullParser的官方例子:
來自類的定義:http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/util/AttributeSet.java#58
import java.io.IOException;
import java.io.StringReader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
public class SimpleXmlPullApp
{
public static void main (String args[])
throws XmlPullParserException, IOException
{
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
xpp.setInput( new StringReader ( "<foo>Hello World!</foo>" ) );
int eventType = xpp.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if(eventType == XmlPullParser.START_DOCUMENT) {
System.out.println("Start document");
} else if(eventType == XmlPullParser.START_TAG) {
System.out.println("Start tag "+xpp.getName());
} else if(eventType == XmlPullParser.END_TAG) {
System.out.println("End tag "+xpp.getName());
} else if(eventType == XmlPullParser.TEXT) {
System.out.println("Text "+xpp.getText()</a>);
}
eventType = xpp.next();
}
System.out.println("End document");
}
}
XmlPullParser接口
141public interface XmlPullParser {
XmlPullParser其實只是一個接口。學習了如何使用之后,我們看一下XmlPullParser接口都要求實現些什么。
可選特性
可選的特性,這些特性默認都是關閉的
- FEATURE_PROCESS_NAMESPACES:解析器是否處理命名空間。必須在解析開始前就設好,中途不能再反悔了
- FEATURE_REPORT_NAMESPACE_ATTRIBUTES:命名空間的屬性是否通過對屬性的訪問方式暴露出來
- FEATURE_PROCESS_DOCDECL:是否支持DTD
- FEATURE_VALIDATION: 是否支持在XML 1.0規范中定義的所有驗證錯誤都將上報。
相關方法:
- void setFeature(String name, boolean state) throws XmlPullParserException; 設置feature
- boolean getFeature(String name); 獲取feature值,未定義的值當然是false。
相關的代碼如下:
345 // ----------------------------------------------------------------------------
346 // namespace related features
347
348 /**
349 * This feature determines whether the parser processes
350 * namespaces. As for all features, the default value is false.
351 * <p><strong>NOTE:</strong> The value can not be changed during
352 * parsing an must be set before parsing.
353 *
354 * @see #getFeature
355 * @see #setFeature
356 */
357 String FEATURE_PROCESS_NAMESPACES =
358 "http://xmlpull.org/v1/doc/features.html#process-namespaces";
359
360 /**
361 * This feature determines whether namespace attributes are
362 * exposed via the attribute access methods. Like all features,
363 * the default value is false. This feature cannot be changed
364 * during parsing.
365 *
366 * @see #getFeature
367 * @see #setFeature
368 */
369 String FEATURE_REPORT_NAMESPACE_ATTRIBUTES =
370 "http://xmlpull.org/v1/doc/features.html#report-namespace-prefixes";
371
372 /**
373 * This feature determines whether the document declaration
374 * is processed. If set to false,
375 * the DOCDECL event type is reported by nextToken()
376 * and ignored by next().
377 *
378 * If this feature is activated, then the document declaration
379 * must be processed by the parser.
380 *
381 * <p><strong>Please note:</strong> If the document type declaration
382 * was ignored, entity references may cause exceptions
383 * later in the parsing process.
384 * The default value of this feature is false. It cannot be changed
385 * during parsing.
386 *
387 * @see #getFeature
388 * @see #setFeature
389 */
390 String FEATURE_PROCESS_DOCDECL =
391 "http://xmlpull.org/v1/doc/features.html#process-docdecl";
392
393 /**
394 * If this feature is activated, all validation errors as
395 * defined in the XML 1.0 specification are reported.
396 * This implies that FEATURE_PROCESS_DOCDECL is true and both, the
397 * internal and external document type declaration will be processed.
398 * <p><strong>Please Note:</strong> This feature can not be changed
399 * during parsing. The default value is false.
400 *
401 * @see #getFeature
402 * @see #setFeature
403 */
404 String FEATURE_VALIDATION =
405 "http://xmlpull.org/v1/doc/features.html#validation";
406
407 /**
408 * Use this call to change the general behaviour of the parser,
409 * such as namespace processing or doctype declaration handling.
410 * This method must be called before the first call to next or
411 * nextToken. Otherwise, an exception is thrown.
412 * <p>Example: call setFeature(FEATURE_PROCESS_NAMESPACES, true) in order
413 * to switch on namespace processing. The initial settings correspond
414 * to the properties requested from the XML Pull Parser factory.
415 * If none were requested, all features are deactivated by default.
416 *
417 * @exception XmlPullParserException If the feature is not supported or can not be set
418 * @exception IllegalArgumentException If string with the feature name is null
419 */
420 void setFeature(String name,
421 boolean state) throws XmlPullParserException;
422
423 /**
424 * Returns the current value of the given feature.
425 * <p><strong>Please note:</strong> unknown features are
426 * <strong>always</strong> returned as false.
427 *
428 * @param name The name of feature to be retrieved.
429 * @return The value of the feature.
430 * @exception IllegalArgumentException if string the feature name is null
431 */
432
433 boolean getFeature(String name);
事件類型
XmlPullParser定義了兩種API,高級API和低級API。高級API通過next();方法取下一個事件,取得的事件如前面的例子所述,主要有下面5種:
- 基本事件類型常量
- int START_DOCUMENT = 0; xml文檔開始
- int END_DOCUMENT = 1; xml文檔結束
- int START_TAG = 2; tag開始,可以通過getName();方法獲取Tag名
- int END_TAG = 3; tag開始,可以通過getName();方法獲取Tag名
- int TEXT = 4; 文本,可通過getText()方法獲取文本
而下面的高級事件API可以通過nextToken()方法獲取。
- 高級事件類型常量
- int CDSECT = 5; CDATA
- int ENTITY_REF = 6; entity reference
- int IGNORABLE_WHITESPACE = 7; 可被忽略的空白符
- int PROCESSING_INSTRUCTION = 8; XML處理指令
- int COMMENT = 9; 注釋
- int DOCDECL = 10; DTD
START_DOCUMENT
第一次調用getEvent()時才會遇到。
149 /**
150 * Signalize that parser is at the very beginning of the document
151 * and nothing was read yet.
152 * This event type can only be observed by calling getEvent()
153 * before the first call to next(), nextToken, or nextTag()</a>).
154 *
155 * @see #next
156 * @see #nextToken
157 */
158 int START_DOCUMENT = 0;
END_DOCUMENT
xml文檔已經到結尾。可以通過getEventType(), next()和nextToken()遇到。
如果在此狀態下繼續調next()或者nextToken()將引發異常。
160 /**
161 * Logical end of the xml document. Returned from getEventType, next()
162 * and nextToken()
163 * when the end of the input document has been reached.
164 * <p><strong>NOTE:</strong> subsequent calls to
165 * <a href="#next()">next()</a> or <a href="#nextToken()">nextToken()</a>
166 * may result in exception being thrown.
167 *
168 * @see #next
169 * @see #nextToken
170 */
171 int END_DOCUMENT = 1;
START_TAG
獲取到一個新標簽。可以通過getName()方法取得標簽名。還可以通過getNamespace()和getPrefix()獲取名字空間和前綴。
如果FEATURE_PROCESS_NAMESPACES支持的話,還可以通過getAttribute()方法獲取屬性。
173 /**
174 * Returned from getEventType(),
175 * <a href="#next()">next()</a>, <a href="#nextToken()">nextToken()</a> when
176 * a start tag was read.
177 * The name of start tag is available from getName(), its namespace and prefix are
178 * available from getNamespace() and getPrefix()
179 * if <a href='#FEATURE_PROCESS_NAMESPACES'>namespaces are enabled</a>.
180 * See getAttribute* methods to retrieve element attributes.
181 * See getNamespace* methods to retrieve newly declared namespaces.
182 *
183 * @see #next
184 * @see #nextToken
185 * @see #getName
186 * @see #getPrefix
187 * @see #getNamespace
188 * @see #getAttributeCount
189 * @see #getDepth
190 * @see #getNamespaceCount
191 * @see #getNamespace
192 * @see #FEATURE_PROCESS_NAMESPACES
193 */
194 int START_TAG = 2;
END_TAG
標簽結事,可以獲得的信息與START_TAG基本一致。
196 /**
197 * Returned from getEventType(), <a href="#next()">next()</a>, or
198 * <a href="#nextToken()">nextToken()</a> when an end tag was read.
199 * The name of start tag is available from getName(), its
200 * namespace and prefix are
201 * available from getNamespace() and getPrefix().
202 *
203 * @see #next
204 * @see #nextToken
205 * @see #getName
206 * @see #getPrefix
207 * @see #getNamespace
208 * @see #FEATURE_PROCESS_NAMESPACES
209 */
210 int END_TAG = 3;
TEXT
可以通過getText()方法獲取文本的內容。
213 /**
214 * Character data was read and will is available by calling getText().
215 * <p><strong>Please note:</strong> <a href="#next()">next()</a> will
216 * accumulate multiple
217 * events into one TEXT event, skipping IGNORABLE_WHITESPACE,
218 * PROCESSING_INSTRUCTION and COMMENT events,
219 * In contrast, <a href="#nextToken()">nextToken()</a> will stop reading
220 * text when any other event is observed.
221 * Also, when the state was reached by calling next(), the text value will
222 * be normalized, whereas getText() will
223 * return unnormalized content in the case of nextToken(). This allows
224 * an exact roundtrip without changing line ends when examining low
225 * level events, whereas for high level applications the text is
226 * normalized appropriately.
227 *
228 * @see #next
229 * @see #nextToken
230 * @see #getText
231 */
232 int TEXT = 4;
AttributeSet接口
XmlPullParser上面介紹的API中,沒有專門提及處理屬性相關的API,是因為我們專門有一個AttributeSet接口,它的實現類會處理資源中的屬性,比如讀取資源字符串的值。
下面例程介紹如何生成AttributeSet接口的對象。
XmlPullParser parser = resources.getXml(myResource);
AttributeSet attributes = Xml.asAttributeSet(parser);
XmlPullParser和AttributeSet兩個接口的實現都高度依賴于aapt對于資源xml的預編譯優化。
舉例來說:getAttributeFloatValue讀取的值,在預編譯時就是按浮點數存儲的,不存在從文本轉化的過程。
這個接口中基本都是獲取值的方法,僅僅是類型不同。我們只以兩個為例看一下:
- abstract boolean getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue):以boolean類型返回namespace空間的attribute屬性的值
- abstract boolean getAttributeBooleanValue(int index, boolean defaultValue): 以boolean類型返回索引為index號屬性的值。
XmlResourceParser
將前面介紹的XmlPullParser和AttributeSet兩個接口整合在一起,既支持解析xml結構,又能適用于屬性資源,這就是XmlResourceParser.
我們先看看這個XmlResourceParser的定義:
23/**
24 * The XML parsing interface returned for an XML resource. This is a standard
25 * XmlPullParser interface, as well as an extended AttributeSet interface and
26 * an additional close() method on this interface for the client to indicate
27 * when it is done reading the resource.
28 */
29public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
30 /**
31 * Close this interface to the resource. Calls on the interface are no
32 * longer value after this call.
33 */
34 public void close();
35}
XmlResourceParser只定義了一個close方法,另外,它繼承自XmlPullParser, AttributeSet和AutoCloseable三個接口。
AutoCloseable是標準的Java 7的接口:
35public interface AutoCloseable {
36 /**
37 * Closes the object and release any system resources it holds.
38 */
39 void close() throws Exception;
40}
LayoutInflater
類介紹
44/**
45 * Instantiates a layout XML file into its corresponding {@link android.view.View}
46 * objects. It is never used directly. Instead, use
47 * {@link android.app.Activity#getLayoutInflater()} or
48 * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
49 * that is already hooked up to the current context and correctly configured
50 * for the device you are running on. For example:
51 *
52 * <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService
53 * (Context.LAYOUT_INFLATER_SERVICE);</pre>
54 *
55 * <p>
56 * To create a new LayoutInflater with an additional {@link Factory} for your
57 * own views, you can use {@link #cloneInContext} to clone an existing
58 * ViewFactory, and then call {@link #setFactory} on it to include your
59 * Factory.
60 *
61 * <p>
62 * For performance reasons, view inflation relies heavily on pre-processing of
63 * XML files that is done at build time. Therefore, it is not currently possible
64 * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
65 * it only works with an XmlPullParser returned from a compiled resource
66 * (R.<em>something</em> file.)
67 *
68 * @see Context#getSystemService
69 */
LayoutInflater用于解析xml,根據xml所寫的布局生成View對象。
這個類的用法是從來不直接調用。而是通過兩種方法間接調用:
- android.app.Activity.getLayoutInflater()
- Context.getSystemService()
通過這兩個方法可以獲取跟上下文綁定的LayoutInflater對象。調用例:
LayoutInflater inflater = (LayoutInflater)context.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
如果你的View需要用額外的工廠類創建一種新的LayoutInflater,可以通過cloneInContext()去復制一份ViewFactory,然后用setFactory方法將你的工廠類添加進去。
因為性能的原因,View的inflate過程重度依賴于在編譯時對XML的預處理. 所以,LayoutInflater不支持在運行時解析xml源文件。
詳細分析
首先,LayoutInflater是個抽象類,具體被調用到的實現類如前面所述,根據上下文不同會有變化。
70public abstract class LayoutInflater {
71
72 private static final String TAG = LayoutInflater.class.getSimpleName();
73 private static final boolean DEBUG = false;
74
75 /**
76 * This field should be made private, so it is hidden from the SDK.
77 * {@hide}
78 */
79 protected final Context mContext;
下面是一些可選項,是可以定制的。
81 // these are optional, set by the caller
82 private boolean mFactorySet;
83 private Factory mFactory;
84 private Factory2 mFactory2;
85 private Factory2 mPrivateFactory;
86 private Filter mFilter;
接口
LayoutInflater為子類定義了一些接口,通過實現這些接口,可以實現一些定制化的功能。
Filter接口 - 實現過濾功能
如果允許,則onLoadClass返回真值。
111 /**
112 * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
113 * to be inflated.
114 *
115 */
116 public interface Filter {
117 /**
118 * Hook to allow clients of the LayoutInflater to restrict the set of Views
119 * that are allowed to be inflated.
120 *
121 * @param clazz The class object for the View that is about to be inflated
122 *
123 * @return True if this class is allowed to be inflated, or false otherwise
124 */
125 @SuppressWarnings("unchecked")
126 boolean onLoadClass(Class clazz);
127 }
Factory接口 - 解析自定義Tag
129 public interface Factory {
130 /**
131 * Hook you can supply that is called when inflating from a LayoutInflater.
132 * You can use this to customize the tag names available in your XML
133 * layout files.
134 *
135 * <p>
136 * Note that it is good practice to prefix these custom names with your
137 * package (i.e., com.coolcompany.apps) to avoid conflicts with system
138 * names.
139 *
140 * @param name Tag name to be inflated.
141 * @param context The context the view is being created in.
142 * @param attrs Inflation attributes as specified in XML file.
143 *
144 * @return View Newly created view. Return null for the default
145 * behavior.
146 */
147 public View onCreateView(String name, Context context, AttributeSet attrs);
148 }
Factory2 - 工廠類版本2 - 支持父View參數
150 public interface Factory2 extends Factory {
151 /**
152 * Version of {@link #onCreateView(String, Context, AttributeSet)}
153 * that also supplies the parent that the view created view will be
154 * placed in.
155 *
156 * @param parent The parent that the created view will be placed
157 * in; <em>note that this may be null</em>.
158 * @param name Tag name to be inflated.
159 * @param context The context the view is being created in.
160 * @param attrs Inflation attributes as specified in XML file.
161 *
162 * @return View Newly created view. Return null for the default
163 * behavior.
164 */
165 public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
166 }
抽象方法
cloneInContext方法
為新的上下文環境下創建新的LayoutInflater
236 /**
237 * Create a copy of the existing LayoutInflater object, with the copy
238 * pointing to a different Context than the original. This is used by
239 * {@link ContextThemeWrapper} to create a new LayoutInflater to go along
240 * with the new Context theme.
241 *
242 * @param newContext The new Context to associate with the new LayoutInflater.
243 * May be the same as the original Context if desired.
244 *
245 * @return Returns a brand spanking new LayoutInflater object associated with
246 * the given Context.
247 */
248 public abstract LayoutInflater cloneInContext(Context newContext);
好了,準備知識告一段落,我們下面正式開始分析inflate的流程。
inflate流程解析
View.inflate
路徑:/frameworks/base/core/java/android/view/View.java
通過xml來進行inflate的入口,在View類的inflate方法中。
首先通過LayoutInflater.from(context)得到一個LayoutInflater類的對象,然后調用LayoutInflater的inflate方法。
19778 /**
19779 * Inflate a view from an XML resource. This convenience method wraps the {@link
19780 * LayoutInflater} class, which provides a full range of options for view inflation.
19781 *
19782 * @param context The Context object for your activity or application.
19783 * @param resource The resource ID to inflate
19784 * @param root A view group that will be the parent. Used to properly inflate the
19785 * layout_* parameters.
19786 * @see LayoutInflater
19787 */
19788 public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
19789 LayoutInflater factory = LayoutInflater.from(context);
19790 return factory.inflate(resource, root);
19791 }
LayoutInflater.from
首先要獲取一個LayoutInflater對象,通過LayoutInflater.from方法,通過系統服務獲得服務對象。
路徑:/frameworks/base/core/java/android/view/LayoutInflater.java
224 /**
225 * Obtains the LayoutInflater from the given context.
226 */
227 public static LayoutInflater from(Context context) {
228 LayoutInflater LayoutInflater =
229 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
230 if (LayoutInflater == null) {
231 throw new AssertionError("LayoutInflater not found.");
232 }
233 return LayoutInflater;
234 }
LayoutInflater.inflate
inflate的標準用法是inflate一個資源ID,這個xml是經過預編譯的,性能比解析文件上的原始xml的性能要更好一些。
我們先看這個入口的解析資源中預編譯xml的inflate版本:
397 /**
398 * Inflate a new view hierarchy from the specified xml resource. Throws
399 * {@link InflateException} if there is an error.
400 *
401 * @param resource ID for an XML layout resource to load (e.g.,
402 * <code>R.layout.main_page</code>)
403 * @param root Optional view to be the parent of the generated hierarchy (if
404 * <em>attachToRoot</em> is true), or else simply an object that
405 * provides a set of LayoutParams values for root of the returned
406 * hierarchy (if <em>attachToRoot</em> is false.)
407 * @param attachToRoot Whether the inflated hierarchy should be attached to
408 * the root parameter? If false, root is only used to create the
409 * correct subclass of LayoutParams for the root view in the XML.
410 * @return The root View of the inflated hierarchy. If root was supplied and
411 * attachToRoot is true, this is root; otherwise it is the root of
412 * the inflated XML file.
413 */
414 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
首先,通過上下文的getResource()方法來獲取Resource的對象。
415 final Resources res = getContext().getResources();
416 if (DEBUG) {
417 Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
418 + Integer.toHexString(resource) + ")");
419 }
420
下面會通過資源對象的getLayout方法獲取相關的XmlResourceParser。
421 final XmlResourceParser parser = res.getLayout(resource);
422 try {
423 return inflate(parser, root, attachToRoot);
424 } finally {
425 parser.close();
426 }
427 }
然后,調用inflate方法
429 /**
430 * Inflate a new view hierarchy from the specified XML node. Throws
431 * {@link InflateException} if there is an error.
432 * <p>
433 * <em><strong>Important</strong></em> For performance
434 * reasons, view inflation relies heavily on pre-processing of XML files
435 * that is done at build time. Therefore, it is not currently possible to
436 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
437 *
438 * @param parser XML dom node containing the description of the view
439 * hierarchy.
440 * @param root Optional view to be the parent of the generated hierarchy (if
441 * <em>attachToRoot</em> is true), or else simply an object that
442 * provides a set of LayoutParams values for root of the returned
443 * hierarchy (if <em>attachToRoot</em> is false.)
444 * @param attachToRoot Whether the inflated hierarchy should be attached to
445 * the root parameter? If false, root is only used to create the
446 * correct subclass of LayoutParams for the root view in the XML.
447 * @return The root View of the inflated hierarchy. If root was supplied and
448 * attachToRoot is true, this is root; otherwise it is the root of
449 * the inflated XML file.
450 */
451 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
452 synchronized (mConstructorArgs) {
453 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
454
455 final Context inflaterContext = mContext;
456 final AttributeSet attrs = Xml.asAttributeSet(parser);
針對上面這句final AttributeSet attrs = Xml.asAttributeSet(parser);有必要增加一點說明。它的作用是將XmlPullParser轉化成AttributesSet.
后面我們會分析到,我們所用的XmlPullParser是XMLBlock.Parser,是XmlPullParser和AttributeSet兩個接口都實現了,自然是最好了。如果不是的話,也有辦法,生成一個XmlPullAttributes的類,通過它來讀取屬性。
代碼如下:
175 public static AttributeSet asAttributeSet(XmlPullParser parser) {
176 return (parser instanceof AttributeSet)
177 ? (AttributeSet) parser
178 : new XmlPullAttributes(parser);
179 }
XmlPullAttributes的封裝是通過對XmlPullParser.getAttributeValue的封裝完成的。
比如我們還是看獲取boolean值的:
63 public boolean getAttributeBooleanValue(String namespace, String attribute,
64 boolean defaultValue) {
65 return XmlUtils.convertValueToBoolean(
66 getAttributeValue(namespace, attribute), defaultValue);
67 }
它是先通過getAttributeValue的調用,然后再做類型轉換。
45 public String getAttributeValue(String namespace, String name) {
46 return mParser.getAttributeValue(namespace, name);
47 }
getAttributeValue直接調用mParser的同名方法。這個mParser就是構造時傳入的XmlPullParser對象。
28class XmlPullAttributes implements AttributeSet {
29 public XmlPullAttributes(XmlPullParser parser) {
30 mParser = parser;
31 }
...
145
146 /*package*/ XmlPullParser mParser;
147}
也就是說,在非編譯的xml中,可以通過這個方法來訪問屬性。
閑言少敘,我們還是先回到inflate的主線中來。
457 Context lastContext = (Context) mConstructorArgs[0];
458 mConstructorArgs[0] = inflaterContext;
459 View result = root;
460
下面開始就是我們前面討論過的XmlPullParser的經典過程了。下面是要尋找根節點,如果遇到的不是XmlPullParser.START_TAG,就繼續往下找,直至遇到第一個Tag為止。
461 try {
462 // Look for the root node.
463 int type;
464 while ((type = parser.next()) != XmlPullParser.START_TAG &&
465 type != XmlPullParser.END_DOCUMENT) {
466 // Empty
467 }
如果一個Tag也沒找到,就報錯。
468
469 if (type != XmlPullParser.START_TAG) {
470 throw new InflateException(parser.getPositionDescription()
471 + ": No start tag found!");
472 }
找到根Tag了,先打幾行log.
473
474 final String name = parser.getName();
475
476 if (DEBUG) {
477 System.out.println("**************************");
478 System.out.println("Creating root view: "
479 + name);
480 System.out.println("**************************");
481 }
482
如果是Tag是"merge"的話,如果有root可以attach,則遞歸調用rInflate去將merge的View attach到root上去。rInflate在下面會分析。
483 if (TAG_MERGE.equals(name)) {
484 if (root == null || !attachToRoot) {
485 throw new InflateException("<merge /> can be used only with a valid "
486 + "ViewGroup root and attachToRoot=true");
487 }
488
489 rInflate(parser, root, inflaterContext, attrs, false);
490 } else {
如果不是merge,那么說明要建立一個新的根節點,調用createViewFromTag去創建之。createViewFromTag下面分析。
491 // Temp is the root view that was found in the xml
492 final View temp = createViewFromTag(root, name, inflaterContext, attrs);
493
494 ViewGroup.LayoutParams params = null;
495
496 if (root != null) {
497 if (DEBUG) {
498 System.out.println("Creating params from root: " +
499 root);
500 }
501 // Create layout params that match root, if supplied
502 params = root.generateLayoutParams(attrs);
503 if (!attachToRoot) {
504 // Set the layout params for temp if we are not
505 // attaching. (If we are, we use addView, below)
506 temp.setLayoutParams(params);
507 }
508 }
509
510 if (DEBUG) {
511 System.out.println("-----> start inflating children");
512 }
513
根節點創建就緒,調用rInflateChildren去建立根節點下面的子節點樹。
514 // Inflate all children under temp against its context.
515 rInflateChildren(parser, temp, attrs, true);
516
517 if (DEBUG) {
518 System.out.println("-----> done inflating children");
519 }
520
如果root節點非空,而且要求attachToRoot,則將我們新建立的根節點attach到root上。否則,我們直接將我們生成的temp根節點作為根節點返回。
521 // We are supposed to attach all the views we found (int temp)
522 // to root. Do that now.
523 if (root != null && attachToRoot) {
524 root.addView(temp, params);
525 }
526
527 // Decide whether to return the root that was passed in or the
528 // top view found in xml.
529 if (root == null || !attachToRoot) {
530 result = temp;
531 }
532 }
533
534 } catch (XmlPullParserException e) {
535 InflateException ex = new InflateException(e.getMessage());
536 ex.initCause(e);
537 throw ex;
538 } catch (Exception e) {
539 InflateException ex = new InflateException(
540 parser.getPositionDescription()
541 + ": " + e.getMessage());
542 ex.initCause(e);
543 throw ex;
544 } finally {
545 // Don't retain static reference on context.
546 mConstructorArgs[0] = lastContext;
547 mConstructorArgs[1] = null;
548 }
549
550 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
551
552 return result;
553 }
554 }
LayoutInflater.rInflateChildren
我們先挑個小的方法繼續看,先看看上面非merge情況下建立子樹的方法rInflateChildren。
rInflateChildren只是rInflate的簡單封裝。rInflate其實比rInflateChildren就多了一個Context參數,其它都透傳。
789 /**
790 * Recursive method used to inflate internal (non-root) children. This
791 * method calls through to {@link #rInflate} using the parent context as
792 * the inflation context.
793 * <strong>Note:</strong> Default visibility so the BridgeInflater can
794 * call it.
795 */
796 final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
797 boolean finishInflate) throws XmlPullParserException, IOException {
798 rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
799 }
LayoutInflater.rInflate
這個才是遞歸搜索建立子樹的正主。
801 /**
802 * Recursive method used to descend down the xml hierarchy and instantiate
803 * views, instantiate their children, and then call onFinishInflate().
804 * <p>
805 * <strong>Note:</strong> Default visibility so the BridgeInflater can
806 * override it.
807 */
808 void rInflate(XmlPullParser parser, View parent, Context context,
809 AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
810
與根相比,子樹的方法加上了對層數的限制。
811 final int depth = parser.getDepth();
812 int type;
813
814 while (((type = parser.next()) != XmlPullParser.END_TAG ||
815 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
816
817 if (type != XmlPullParser.START_TAG) {
818 continue;
819 }
820
821 final String name = parser.getName();
822
下面要處理一些特殊的Tag,它們的定義如下:
100 private static final String TAG_MERGE = "merge";
101 private static final String TAG_INCLUDE = "include";
102 private static final String TAG_1995 = "blink";
103 private static final String TAG_REQUEST_FOCUS = "requestFocus";
104 private static final String TAG_TAG = "tag";
823 if (TAG_REQUEST_FOCUS.equals(name)) {
824 parseRequestFocus(parser, parent);
825 } else if (TAG_TAG.equals(name)) {
826 parseViewTag(parser, parent, attrs);
827 } else if (TAG_INCLUDE.equals(name)) {
828 if (parser.getDepth() == 0) {
829 throw new InflateException("<include /> cannot be the root element");
830 }
831 parseInclude(parser, context, parent, attrs);
832 } else if (TAG_MERGE.equals(name)) {
833 throw new InflateException("<merge /> must be the root element");
834 } else {
還是調用createViewFromTag來生成本級的View對象,然后還是調用rInflateChildren去建子樹,實現遞歸。
835 final View view = createViewFromTag(parent, name, context, attrs);
836 final ViewGroup viewGroup = (ViewGroup) parent;
837 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
838 rInflateChildren(parser, view, attrs, true);
子樹建好后,add到父viewGroup中去。
839 viewGroup.addView(view, params);
840 }
841 }
842
遞歸結束的話,調用onFinishInflate().
843 if (finishInflate) {
844 parent.onFinishInflate();
845 }
846 }
LayoutInflater.createViewFromTag
根據Tag創建View對象。
707 /**
708 * Creates a view from a tag name using the supplied attribute set.
709 * <p>
710 * <strong>Note:</strong> Default visibility so the BridgeInflater can
711 * override it.
712 *
713 * @param parent the parent view, used to inflate layout params
714 * @param name the name of the XML tag used to define the view
715 * @param context the inflation context for the view, typically the
716 * {@code parent} or base layout inflater context
717 * @param attrs the attribute set for the XML tag used to define the view
718 * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
719 * attribute (if set) for the view being inflated,
720 * {@code false} otherwise
721 */
722 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
723 boolean ignoreThemeAttr) {
如果Tag的名字是view的話,就從屬性中讀取類名作為新的name.
724 if (name.equals("view")) {
725 name = attrs.getAttributeValue(null, "class");
726 }
727
下面處理主題相關
728 // Apply a theme wrapper, if allowed and one is specified.
729 if (!ignoreThemeAttr) {
730 final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
731 final int themeResId = ta.getResourceId(0, 0);
732 if (themeResId != 0) {
733 context = new ContextThemeWrapper(context, themeResId);
734 }
735 ta.recycle();
736 }
如前面所講的,如果有定義自己的工廠類的話,則調用那些工廠類的onCreateView。
743 try {
744 View view;
745 if (mFactory2 != null) {
746 view = mFactory2.onCreateView(parent, name, context, attrs);
747 } else if (mFactory != null) {
748 view = mFactory.onCreateView(name, context, attrs);
749 } else {
750 view = null;
751 }
752
753 if (view == null && mPrivateFactory != null) {
754 view = mPrivateFactory.onCreateView(parent, name, context, attrs);
755 }
756
如果沒有自定義工廠類,則調用LayoutInflater中的onCreateView或者createView。其中onCreateView也只是簡單封裝一下,唯一做的一件事就是將省略掉的android.view包名給補上。這樣,createViewFromTag的主要邏輯也就結束了。
757 if (view == null) {
758 final Object lastContext = mConstructorArgs[0];
759 mConstructorArgs[0] = context;
760 try {
761 if (-1 == name.indexOf('.')) {
762 view = onCreateView(parent, name, attrs);
763 } else {
764 view = createView(name, null, attrs);
765 }
766 } finally {
767 mConstructorArgs[0] = lastContext;
768 }
769 }
770
771 return view;
772 } catch (InflateException e) {
773 throw e;
774
775 } catch (ClassNotFoundException e) {
776 final InflateException ie = new InflateException(attrs.getPositionDescription()
777 + ": Error inflating class " + name);
778 ie.initCause(e);
779 throw ie;
780
781 } catch (Exception e) {
782 final InflateException ie = new InflateException(attrs.getPositionDescription()
783 + ": Error inflating class " + name);
784 ie.initCause(e);
785 throw ie;
786 }
787 }
LayoutInflater.onCreateView
這個方法啥情況,唯一的作用就是把parent參數給扔了,呵呵。
/**
* Version of {@link #onCreateView(String, AttributeSet)} that also takes
* the future parent of the view being constructed. The default
* implementation simply calls {@link #onCreateView(String, AttributeSet)}.
*
* @param parent
* The future parent of the returned view. <em>Note that
* this may be null.</em>
* @param name
* The fully qualified class name of the View to be create.
* @param attrs
* An AttributeSet of attributes to apply to the View.
*
* @return View The View created.
*/
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
兩個參數的版本,加個前綴"android.widget.",最后還調回createView.
/**
* This routine is responsible for creating the correct subclass of View
* given the xml element name. Override it to handle custom view objects. If
* you override this in your subclass be sure to call through to
* super.onCreateView(name) for names you do not recognize.
*
* @param name
* The fully qualified class name of the View to be create.
* @param attrs
* An AttributeSet of attributes to apply to the View.
*
* @return View The View created.
*/
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
LayoutInflater.createView
通過反射去生成View對象。
556 /**
557 * Low-level function for instantiating a view by name. This attempts to
558 * instantiate a view class of the given <var>name</var> found in this
559 * LayoutInflater's ClassLoader.
560 *
561 * <p>
562 * There are two things that can happen in an error case: either the
563 * exception describing the error will be thrown, or a null will be
564 * returned. You must deal with both possibilities -- the former will happen
565 * the first time createView() is called for a class of a particular name,
566 * the latter every time there-after for that class name.
567 *
568 * @param name The full name of the class to be instantiated.
569 * @param attrs The XML attributes supplied for this instance.
570 *
571 * @return View The newly instantiated view, or null.
572 */
573 public final View createView(String name, String prefix, AttributeSet attrs)
574 throws ClassNotFoundException, InflateException {
sConstructorMap是構造方法的緩存,如果有了就用現成的吧。
575 Constructor<? extends View> constructor = sConstructorMap.get(name);
576 Class<? extends View> clazz = null;
577
578 try {
579 Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
580
581 if (constructor == null) {
582 // Class not found in the cache, see if it's real, and try to add it
583 clazz = mContext.getClassLoader().loadClass(
584 prefix != null ? (prefix + name) : name).asSubclass(View.class);
585
如前所述,如果定義了過濾的話,則調用mFilter的onLoadClass判斷是否允許,不允許則調用failNotAllowed去拋Exception。
586 if (mFilter != null && clazz != null) {
587 boolean allowed = mFilter.onLoadClass(clazz);
588 if (!allowed) {
589 failNotAllowed(name, prefix, attrs);
590 }
591 }
592 constructor = clazz.getConstructor(mConstructorSignature);
593 constructor.setAccessible(true);
594 sConstructorMap.put(name, constructor);
595 } else {
下面一個分支是能拿到可重用的構造器的情況
596 // If we have a filter, apply it to cached constructor
597 if (mFilter != null) {
598 // Have we seen this name before?
599 Boolean allowedState = mFilterMap.get(name);
600 if (allowedState == null) {
601 // New class -- remember whether it is allowed
602 clazz = mContext.getClassLoader().loadClass(
603 prefix != null ? (prefix + name) : name).asSubclass(View.class);
604
605 boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
606 mFilterMap.put(name, allowed);
607 if (!allowed) {
608 failNotAllowed(name, prefix, attrs);
609 }
610 } else if (allowedState.equals(Boolean.FALSE)) {
611 failNotAllowed(name, prefix, attrs);
612 }
613 }
614 }
615
616 Object[] args = mConstructorArgs;
617 args[1] = attrs;
618
619 final View view = constructor.newInstance(args);
如果是ViewStub的話,暫時不需要inflate了,但是需要clone一個inflater給它。
620 if (view instanceof ViewStub) {
621 // Use the same context when inflating ViewStub later.
622 final ViewStub viewStub = (ViewStub) view;
623 viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
624 }
625 return view;
626
627 } catch (NoSuchMethodException e) {
628 InflateException ie = new InflateException(attrs.getPositionDescription()
629 + ": Error inflating class "
630 + (prefix != null ? (prefix + name) : name));
631 ie.initCause(e);
632 throw ie;
633
634 } catch (ClassCastException e) {
635 // If loaded class is not a View subclass
636 InflateException ie = new InflateException(attrs.getPositionDescription()
637 + ": Class is not a View "
638 + (prefix != null ? (prefix + name) : name));
639 ie.initCause(e);
640 throw ie;
641 } catch (ClassNotFoundException e) {
642 // If loadClass fails, we should propagate the exception.
643 throw e;
644 } catch (Exception e) {
645 InflateException ie = new InflateException(attrs.getPositionDescription()
646 + ": Error inflating class "
647 + (clazz == null ? "<unknown>" : clazz.getName()));
648 ie.initCause(e);
649 throw ie;
650 } finally {
651 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
652 }
653 }
failNotAllowed
就是個Exception拼字符串的方法。
/**
* Throw an exception because the specified class is not allowed to be
* inflated.
*/
private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
throw new InflateException(attrs.getPositionDescription()
+ ": Class not allowed to be inflated "
+ (prefix != null ? (prefix + name) : name));
}
XmlBlock.Parser
當夢想照進現實,我們看看XmlResourceParser接口的真正實現類XmlBlock.Parser。
定義
77 /*package*/ final class Parser implements XmlResourceParser {
78 Parser(long parseState, XmlBlock block) {
79 mParseState = parseState;
80 mBlock = block;
81 block.mOpenCount++;
82 }
native函數唱主角
基本上主要的功能都靠native函數來實現
491 private static final native long nativeCreate(byte[] data,
492 int offset,
493 int size);
494 private static final native long nativeGetStringBlock(long obj);
495
496 private static final native long nativeCreateParseState(long obj);
497 /*package*/ static final native int nativeNext(long state);
498 private static final native int nativeGetNamespace(long state);
499 /*package*/ static final native int nativeGetName(long state);
500 private static final native int nativeGetText(long state);
501 private static final native int nativeGetLineNumber(long state);
502 private static final native int nativeGetAttributeCount(long state);
503 private static final native int nativeGetAttributeNamespace(long state, int idx);
504 private static final native int nativeGetAttributeName(long state, int idx);
505 private static final native int nativeGetAttributeResource(long state, int idx);
506 private static final native int nativeGetAttributeDataType(long state, int idx);
507 private static final native int nativeGetAttributeData(long state, int idx);
508 private static final native int nativeGetAttributeStringValue(long state, int idx);
509 private static final native int nativeGetIdAttribute(long state);
510 private static final native int nativeGetClassAttribute(long state);
511 private static final native int nativeGetStyleAttribute(long state);
512 private static final native int nativeGetAttributeIndex(long state, String namespace, String name);
513 private static final native void nativeDestroyParseState(long state);
514
515 private static final native void nativeDestroy(long obj);
這些方法和本地函數的對照表在/frameworks/base/core/jni/android_util_XmlBlock.cpp中,
364/*
365 * JNI registration.
366 */
367static JNINativeMethod gXmlBlockMethods[] = {
368 /* name, signature, funcPtr */
369 { "nativeCreate", "([BII)J",
370 (void*) android_content_XmlBlock_nativeCreate },
371 { "nativeGetStringBlock", "(J)J",
372 (void*) android_content_XmlBlock_nativeGetStringBlock },
373 { "nativeCreateParseState", "(J)J",
374 (void*) android_content_XmlBlock_nativeCreateParseState },
375 { "nativeNext", "(J)I",
376 (void*) android_content_XmlBlock_nativeNext },
377 { "nativeGetNamespace", "(J)I",
378 (void*) android_content_XmlBlock_nativeGetNamespace },
379 { "nativeGetName", "(J)I",
380 (void*) android_content_XmlBlock_nativeGetName },
381 { "nativeGetText", "(J)I",
382 (void*) android_content_XmlBlock_nativeGetText },
383 { "nativeGetLineNumber", "(J)I",
384 (void*) android_content_XmlBlock_nativeGetLineNumber },
385 { "nativeGetAttributeCount", "(J)I",
386 (void*) android_content_XmlBlock_nativeGetAttributeCount },
387 { "nativeGetAttributeNamespace","(JI)I",
388 (void*) android_content_XmlBlock_nativeGetAttributeNamespace },
389 { "nativeGetAttributeName", "(JI)I",
390 (void*) android_content_XmlBlock_nativeGetAttributeName },
391 { "nativeGetAttributeResource", "(JI)I",
392 (void*) android_content_XmlBlock_nativeGetAttributeResource },
393 { "nativeGetAttributeDataType", "(JI)I",
394 (void*) android_content_XmlBlock_nativeGetAttributeDataType },
395 { "nativeGetAttributeData", "(JI)I",
396 (void*) android_content_XmlBlock_nativeGetAttributeData },
397 { "nativeGetAttributeStringValue", "(JI)I",
398 (void*) android_content_XmlBlock_nativeGetAttributeStringValue },
399 { "nativeGetAttributeIndex", "(JLjava/lang/String;Ljava/lang/String;)I",
400 (void*) android_content_XmlBlock_nativeGetAttributeIndex },
401 { "nativeGetIdAttribute", "(J)I",
402 (void*) android_content_XmlBlock_nativeGetIdAttribute },
403 { "nativeGetClassAttribute", "(J)I",
404 (void*) android_content_XmlBlock_nativeGetClassAttribute },
405 { "nativeGetStyleAttribute", "(J)I",
406 (void*) android_content_XmlBlock_nativeGetStyleAttribute },
407 { "nativeDestroyParseState", "(J)V",
408 (void*) android_content_XmlBlock_nativeDestroyParseState },
409 { "nativeDestroy", "(J)V",
410 (void*) android_content_XmlBlock_nativeDestroy },
411};
我們看幾個例子:
getText
getText是涉及到訪問資源的,我們先看看這個。
資源ID查找的過程是在nativeGetText本地方法中實現的,如下所示:
141 public String getText() {
142 int id = nativeGetText(mParseState);
143 return id >= 0 ? mStrings.get(id).toString() : null;
144 }
查上面的表,找到對應的函數:
150static jint android_content_XmlBlock_nativeGetText(JNIEnv* env, jobject clazz,
151 jlong token)
152{
153 ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
154 if (st == NULL) {
155 return -1;
156 }
157
158 return static_cast<jint>(st->getTextID());
159}
核心功能指向一個C++的類ResXMLParser,我們下面再看這個類的詳細定義,先看看getTextID().
1062int32_t ResXMLParser::getTextID() const
1063{
1064 if (mEventCode == TEXT) {
1065 return dtohl(((const ResXMLTree_cdataExt*)mCurExt)->data.index);
1066 }
1067 return -1;
1068}
next
我們再看一個next的實現。
236 public int next() throws XmlPullParserException,IOException {
237 if (!mStarted) {
238 mStarted = true;
239 return START_DOCUMENT;
240 }
241 if (mParseState == 0) {
242 return END_DOCUMENT;
243 }
244 int ev = nativeNext(mParseState);
245 if (mDecNextDepth) {
246 mDepth--;
247 mDecNextDepth = false;
248 }
249 switch (ev) {
250 case START_TAG:
251 mDepth++;
252 break;
253 case END_TAG:
254 mDecNextDepth = true;
255 break;
256 }
257 mEventType = ev;
258 if (ev == END_DOCUMENT) {
259 // Automatically close the parse when we reach the end of
260 // a document, since the standard XmlPullParser interface
261 // doesn't have such an API so most clients will leave us
262 // dangling.
263 close();
264 }
265 return ev;
266 }
基本上處理一下深度等,主要邏輯全靠JNI函數。
94static jint android_content_XmlBlock_nativeNext(JNIEnv* env, jobject clazz,
95 jlong token)
96{
97 ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
98 if (st == NULL) {
99 return ResXMLParser::END_DOCUMENT;
100 }
101
102 do {
103 ResXMLParser::event_code_t code = st->next();
104 switch (code) {
105 case ResXMLParser::START_TAG:
106 return 2;
107 case ResXMLParser::END_TAG:
108 return 3;
109 case ResXMLParser::TEXT:
110 return 4;
111 case ResXMLParser::START_DOCUMENT:
112 return 0;
113 case ResXMLParser::END_DOCUMENT:
114 return 1;
115 case ResXMLParser::BAD_DOCUMENT:
116 goto bad;
117 default:
118 break;
119 }
120 } while (true);
121
122bad:
123 jniThrowException(env, "org/xmlpull/v1/XmlPullParserException",
124 "Corrupt XML binary file");
125 return ResXMLParser::BAD_DOCUMENT;
126}
最終的實現還是靠ResXMLParser:
1034ResXMLParser::event_code_t ResXMLParser::next()
1035{
1036 if (mEventCode == START_DOCUMENT) {
1037 mCurNode = mTree.mRootNode;
1038 mCurExt = mTree.mRootExt;
1039 return (mEventCode=mTree.mRootCode);
1040 } else if (mEventCode >= FIRST_CHUNK_CODE) {
1041 return nextNode();
1042 }
1043 return mEventCode;
1044}
ResXMLParser
這個類的定義在/frameworks/base/include/androidfw/ResourceTypes.h中,
680class ResXMLParser
681{
682public:
683 ResXMLParser(const ResXMLTree& tree);
684
685 enum event_code_t {
686 BAD_DOCUMENT = -1,
687 START_DOCUMENT = 0,
688 END_DOCUMENT = 1,
689
690 FIRST_CHUNK_CODE = RES_XML_FIRST_CHUNK_TYPE,
691
692 START_NAMESPACE = RES_XML_START_NAMESPACE_TYPE,
693 END_NAMESPACE = RES_XML_END_NAMESPACE_TYPE,
694 START_TAG = RES_XML_START_ELEMENT_TYPE,
695 END_TAG = RES_XML_END_ELEMENT_TYPE,
696 TEXT = RES_XML_CDATA_TYPE
697 };
698
699 struct ResXMLPosition
700 {
701 event_code_t eventCode;
702 const ResXMLTree_node* curNode;
703 const void* curExt;
704 };
705
706 void restart();
707
708 const ResStringPool& getStrings() const;
709
710 event_code_t getEventType() const;
711 // Note, unlike XmlPullParser, the first call to next() will return
712 // START_TAG of the first element.
713 event_code_t next();
714
715 // These are available for all nodes:
716 int32_t getCommentID() const;
717 const char16_t* getComment(size_t* outLen) const;
718 uint32_t getLineNumber() const;
719
720 // This is available for TEXT:
721 int32_t getTextID() const;
722 const char16_t* getText(size_t* outLen) const;
723 ssize_t getTextValue(Res_value* outValue) const;
724
725 // These are available for START_NAMESPACE and END_NAMESPACE:
726 int32_t getNamespacePrefixID() const;
727 const char16_t* getNamespacePrefix(size_t* outLen) const;
728 int32_t getNamespaceUriID() const;
729 const char16_t* getNamespaceUri(size_t* outLen) const;
730
731 // These are available for START_TAG and END_TAG:
732 int32_t getElementNamespaceID() const;
733 const char16_t* getElementNamespace(size_t* outLen) const;
734 int32_t getElementNameID() const;
735 const char16_t* getElementName(size_t* outLen) const;
736
737 // Remaining methods are for retrieving information about attributes
738 // associated with a START_TAG:
739
740 size_t getAttributeCount() const;
741
742 // Returns -1 if no namespace, -2 if idx out of range.
743 int32_t getAttributeNamespaceID(size_t idx) const;
744 const char16_t* getAttributeNamespace(size_t idx, size_t* outLen) const;
745
746 int32_t getAttributeNameID(size_t idx) const;
747 const char16_t* getAttributeName(size_t idx, size_t* outLen) const;
748 uint32_t getAttributeNameResID(size_t idx) const;
749
750 // These will work only if the underlying string pool is UTF-8.
751 const char* getAttributeNamespace8(size_t idx, size_t* outLen) const;
752 const char* getAttributeName8(size_t idx, size_t* outLen) const;
753
754 int32_t getAttributeValueStringID(size_t idx) const;
755 const char16_t* getAttributeStringValue(size_t idx, size_t* outLen) const;
756
757 int32_t getAttributeDataType(size_t idx) const;
758 int32_t getAttributeData(size_t idx) const;
759 ssize_t getAttributeValue(size_t idx, Res_value* outValue) const;
760
761 ssize_t indexOfAttribute(const char* ns, const char* attr) const;
762 ssize_t indexOfAttribute(const char16_t* ns, size_t nsLen,
763 const char16_t* attr, size_t attrLen) const;
764
765 ssize_t indexOfID() const;
766 ssize_t indexOfClass() const;
767 ssize_t indexOfStyle() const;
768
769 void getPosition(ResXMLPosition* pos) const;
770 void setPosition(const ResXMLPosition& pos);
771
772private:
773 friend class ResXMLTree;
774
775 event_code_t nextNode();
776
777 const ResXMLTree& mTree;
778 event_code_t mEventCode;
779 const ResXMLTree_node* mCurNode;
780 const void* mCurExt;
781};
不支持的功能
不支持的feature
XmlBlock.Parser只支持兩個feature:
- FEATURE_PROCESS_NAMESPACES
- FEATURE_REPORT_NAMESPACE_ATTRIBUTES
DTD是不支持的,也不要提validation了
84 public void setFeature(String name, boolean state) throws XmlPullParserException {
85 if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
86 return;
87 }
88 if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
89 return;
90 }
91 throw new XmlPullParserException("Unsupported feature: " + name);
92 }
93 public boolean getFeature(String name) {
94 if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
95 return true;
96 }
97 if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
98 return true;
99 }
100 return false;
101 }
屬性不支持
setProperty不支持啦~
102 public void setProperty(String name, Object value) throws XmlPullParserException {
103 throw new XmlPullParserException("setProperty() not supported");
104 }
105 public Object getProperty(String name) {
106 return null;
107 }
不支持定義input
setInput不支持
108 public void setInput(Reader in) throws XmlPullParserException {
109 throw new XmlPullParserException("setInput() not supported");
110 }
111 public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
112 throw new XmlPullParserException("setInput() not supported");
113 }
Entity Replacement Text不支持
114 public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException {
115 throw new XmlPullParserException("defineEntityReplacementText() not supported");
116 }
命名空間不支持
117 public String getNamespacePrefix(int pos) throws XmlPullParserException {
118 throw new XmlPullParserException("getNamespacePrefix() not supported");
119 }
120 public String getInputEncoding() {
121 return null;
122 }
123 public String getNamespace(String prefix) {
124 throw new RuntimeException("getNamespace() not supported");
125 }
126 public int getNamespaceCount(int depth) throws XmlPullParserException {
127 throw new XmlPullParserException("getNamespaceCount() not supported");
128 }
XMLBlock的編譯生成 - aapt中的XMLNode
這個過程的實現在/frameworks/base/tools/aapt/XMLNode.cpp中.
例如,下面的函數就是將AaptFile生成前面我們所看到的ResXMLTree對象。
554status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
555 bool stripAll, bool keepComments,
556 const char** cDataTags)
557{
558 sp<XMLNode> root = XMLNode::parse(file);
559 if (root == NULL) {
560 return UNKNOWN_ERROR;
561 }
562 root->removeWhitespace(stripAll, cDataTags);
563
564 if (kIsDebug) {
565 printf("Input XML from %s:\n", (const char*)file->getPrintableSource());
566 root->print();
567 }
568 sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8());
569 status_t err = root->flatten(rsc, !keepComments, false);
570 if (err != NO_ERROR) {
571 return err;
572 }
573 err = outTree->setTo(rsc->getData(), rsc->getSize(), true);
574 if (err != NO_ERROR) {
575 return err;
576 }
577
578 if (kIsDebug) {
579 printf("Output XML:\n");
580 printXMLBlock(outTree);
581 }
582
583 return NO_ERROR;
584}
UI控件
ViewGroup
ViewGroup實現了ViewManager和ViewParent兩個接口。
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
ViewManager
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
在inflate的過程中,主要用到的是構造和addView。第一個View參數不用說了,要加入的子View。另外一個重要的參數是ViewGroup.LayoutParams. 這個參數的主要用途是指定子View的位置。
ViewGroup.LayoutParams
ViewGroup.LayoutParams的基本屬性
作為一個基本類,它的主要作用是指定子View的寬和高。
除了直接指定大小之外,它還接受兩個值:MATCH_PARENT(老的名字叫FILL_PARENT)和WRAP_CONTENT. 這大家都太熟悉了,就不多說了。
下面抽象一下,常量和寬高,是我們熟悉的部分。
6770 public static class LayoutParams {
...
6777 @SuppressWarnings({"UnusedDeclaration"})
6778 @Deprecated
6779 public static final int FILL_PARENT = -1;
...
6786 public static final int MATCH_PARENT = -1;
...
6793 public static final int WRAP_CONTENT = -2;
...
6804 public int width;
6815 public int height;
下面是布局動畫的,先放在這里,用到再說。
/**
* Used to animate layouts.
*/
public LayoutAnimationController.AnimationParameters layoutAnimationParameters;
ViewGroup.LayoutParams的構造方法
別看下面都是又是主題,又是繞來繞去的高大上方法。本質上,ViewGroup.LayoutParams就是寬和高兩個域。這兩個值賦正確了,其它的就都不用管。值可以是具體的pixel值,也可以是MATCH_PARENT或者WRAP_CONTENT兩個常量。
我們把代碼中的幾個構造方法的順序調整一下,先看說人話的。
第一個是最正宗的賦值型構造,兩個值一賦就OK。
public LayoutParams(int width, int height) {
this.width = width;
this.height = height;
}
再看下拷貝構造方法:
/**
* Copy constructor. Clones the width and height values of the source.
*
* @param source The layout params to copy from.
*/
public LayoutParams(LayoutParams source) {
this.width = source.width;
this.height = source.height;
}
然后再看說文言的,這個得轉幾道手,看幾個其它類的方法:
6840 public LayoutParams(Context c, AttributeSet attrs) {
6841 TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
6842 setBaseAttributes(a,
6843 R.styleable.ViewGroup_Layout_layout_width,
6844 R.styleable.ViewGroup_Layout_layout_height);
6845 a.recycle();
6846 }
首先來看這個Context.obtainStyledAttributes,從主題中讀取值。先獲取當前上下文的主題,然后調用主題類的obtainStyledAttributes.
530 public final TypedArray obtainStyledAttributes(
531 AttributeSet set, @StyleableRes int[] attrs) {
532 return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
533 }
我們移步/frameworks/base/core/java/android/content/res/Resources.java,看看Theme中的obtainStyledAttributes的實現,我們刪節一下,一共也沒幾句邏輯:
1593 public TypedArray obtainStyledAttributes(AttributeSet set,
1594 @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
1595 final int len = attrs.length;
1596 final TypedArray array = TypedArray.obtain(Resources.this, len);
1597
...
1602 final XmlBlock.Parser parser = (XmlBlock.Parser)set;
1603 AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
1604 parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);
1605
1606 array.mTheme = this;
1607 array.mXml = parser;
...
1638 return array;
1639 }
然后我們轉戰TypedArray.obtain:
43 static TypedArray obtain(Resources res, int len) {
44 final TypedArray attrs = res.mTypedArrayPool.acquire();
45 if (attrs != null) {
46 attrs.mLength = len;
47 attrs.mRecycled = false;
48
49 final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
50 if (attrs.mData.length >= fullLen) {
51 return attrs;
52 }
53
54 attrs.mData = new int[fullLen];
55 attrs.mIndices = new int[1 + len];
56 return attrs;
57 }
58
59 return new TypedArray(res,
60 new int[len*AssetManager.STYLE_NUM_ENTRIES],
61 new int[1+len], len);
62 }
得到了TypedArray結果之后,再通過setBaseAttributes將值設置好。上面已經反復強調了,在ViewGroup.LayoutParams一共就只有寬和高兩個參數,不管怎么復雜地折騰,最終落實的一定是這兩個值。
6881 /**
6882 * Extracts the layout parameters from the supplied attributes.
6883 *
6884 * @param a the style attributes to extract the parameters from
6885 * @param widthAttr the identifier of the width attribute
6886 * @param heightAttr the identifier of the height attribute
6887 */
6888 protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
6889 width = a.getLayoutDimension(widthAttr, "layout_width");
6890 height = a.getLayoutDimension(heightAttr, "layout_height");
6891 }
MarginLayoutParams
ViewGroup.LayoutParams只有寬和高兩個參數,簡單是極簡了。下面我們給它周圍加個白邊。一共6個變量,上下左右4個邊距,加上起始和結束2個邊距。
6969 public static class MarginLayoutParams extends ViewGroup.LayoutParams {
6970 /**
6971 * The left margin in pixels of the child. Margin values should be positive.
6972 * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6973 * to this field.
6974 */
6975 @ViewDebug.ExportedProperty(category = "layout")
6976 public int leftMargin;
6977
6978 /**
6979 * The top margin in pixels of the child. Margin values should be positive.
6980 * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6981 * to this field.
6982 */
6983 @ViewDebug.ExportedProperty(category = "layout")
6984 public int topMargin;
6985
6986 /**
6987 * The right margin in pixels of the child. Margin values should be positive.
6988 * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6989 * to this field.
6990 */
6991 @ViewDebug.ExportedProperty(category = "layout")
6992 public int rightMargin;
6993
6994 /**
6995 * The bottom margin in pixels of the child. Margin values should be positive.
6996 * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6997 * to this field.
6998 */
6999 @ViewDebug.ExportedProperty(category = "layout")
7000 public int bottomMargin;
7001
7002 /**
7003 * The start margin in pixels of the child. Margin values should be positive.
7004 * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
7005 * to this field.
7006 */
7007 @ViewDebug.ExportedProperty(category = "layout")
7008 private int startMargin = DEFAULT_MARGIN_RELATIVE;
7009
7010 /**
7011 * The end margin in pixels of the child. Margin values should be positive.
7012 * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
7013 * to this field.
7014 */
7015 @ViewDebug.ExportedProperty(category = "layout")
7016 private int endMargin = DEFAULT_MARGIN_RELATIVE;
ViewGroup的構造
前三個都是陪太子讀書的,一共是4個參數,前三個是給1個參數,2個參數,3個參數時其它給空參數時的調用。
560 public ViewGroup(Context context) {
561 this(context, null);
562 }
563
564 public ViewGroup(Context context, AttributeSet attrs) {
565 this(context, attrs, 0);
566 }
567
568 public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
569 this(context, attrs, defStyleAttr, 0);
570 }
其余就下面這一個,它一共有3步,我們分別分析。
572 public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
573 super(context, attrs, defStyleAttr, defStyleRes);
574 initViewGroup();
575 initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
576 }
initViewGroup
這個好,基本上都是設一些屬性
582 private void initViewGroup() {
583 // ViewGroup doesn't draw by default
584 if (!debugDraw()) {
585 setFlags(WILL_NOT_DRAW, DRAW_MASK);
586 }
587 mGroupFlags |= FLAG_CLIP_CHILDREN;
588 mGroupFlags |= FLAG_CLIP_TO_PADDING;
589 mGroupFlags |= FLAG_ANIMATION_DONE;
590 mGroupFlags |= FLAG_ANIMATION_CACHE;
591 mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
592
593 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
594 mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
595 }
596
597 setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
598
599 mChildren = new View[ARRAY_INITIAL_CAPACITY];
600 mChildrenCount = 0;
601
602 mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
603 }
initFromAttributes
605 private void initFromAttributes(
606 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
又到了我們熟悉的context.obtainStyledAttributes,下面就是分門別類放東西,就不多說了。
607 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup, defStyleAttr,
608 defStyleRes);
609
610 final int N = a.getIndexCount();
611 for (int i = 0; i < N; i++) {
612 int attr = a.getIndex(i);
613 switch (attr) {
614 case R.styleable.ViewGroup_clipChildren:
615 setClipChildren(a.getBoolean(attr, true));
616 break;
617 case R.styleable.ViewGroup_clipToPadding:
618 setClipToPadding(a.getBoolean(attr, true));
619 break;
620 case R.styleable.ViewGroup_animationCache:
621 setAnimationCacheEnabled(a.getBoolean(attr, true));
622 break;
623 case R.styleable.ViewGroup_persistentDrawingCache:
624 setPersistentDrawingCache(a.getInt(attr, PERSISTENT_SCROLLING_CACHE));
625 break;
626 case R.styleable.ViewGroup_addStatesFromChildren:
627 setAddStatesFromChildren(a.getBoolean(attr, false));
628 break;
629 case R.styleable.ViewGroup_alwaysDrawnWithCache:
630 setAlwaysDrawnWithCacheEnabled(a.getBoolean(attr, true));
631 break;
632 case R.styleable.ViewGroup_layoutAnimation:
633 int id = a.getResourceId(attr, -1);
634 if (id > 0) {
635 setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));
636 }
637 break;
638 case R.styleable.ViewGroup_descendantFocusability:
639 setDescendantFocusability(DESCENDANT_FOCUSABILITY_FLAGS[a.getInt(attr, 0)]);
640 break;
641 case R.styleable.ViewGroup_splitMotionEvents:
642 setMotionEventSplittingEnabled(a.getBoolean(attr, false));
643 break;
644 case R.styleable.ViewGroup_animateLayoutChanges:
645 boolean animateLayoutChanges = a.getBoolean(attr, false);
646 if (animateLayoutChanges) {
647 setLayoutTransition(new LayoutTransition());
648 }
649 break;
650 case R.styleable.ViewGroup_layoutMode:
651 setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED));
652 break;
653 case R.styleable.ViewGroup_transitionGroup:
654 setTransitionGroup(a.getBoolean(attr, false));
655 break;
656 case R.styleable.ViewGroup_touchscreenBlocksFocus:
657 setTouchscreenBlocksFocus(a.getBoolean(attr, false));
658 break;
659 }
660 }
661
662 a.recycle();
663 }