综述
在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 ( "Hello World! " ) );
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());
}
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 * NOTE: 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 *
Please note: 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 *
Please Note: 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 *
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 *
Please note: unknown features are
426 * always 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()).
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 * NOTE: subsequent calls to
165 * next() or nextToken()
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 * next(), nextToken() 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 namespaces are enabled.
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(), next(), or
198 * nextToken() 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 * Please note: next() will
216 * accumulate multiple
217 * events into one TEXT event, skipping IGNORABLE_WHITESPACE,
218 * PROCESSING_INSTRUCTION and COMMENT events,
219 * In contrast, nextToken() 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 * LayoutInflater inflater = (LayoutInflater)context.getSystemService
53 * (Context.LAYOUT_INFLATER_SERVICE);
54 *
55 *
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 *
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.something 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 *
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; note that this may be null.
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 * R.layout.main_page
)
403 * @param root Optional view to be the parent of the generated hierarchy (if
404 * attachToRoot is true), or else simply an object that
405 * provides a set of LayoutParams values for root of the returned
406 * hierarchy (if attachToRoot 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 *
433 * Important 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 * attachToRoot is true), or else simply an object that
442 * provides a set of LayoutParams values for root of the returned
443 * hierarchy (if attachToRoot 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(" 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 * Note: 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 *
805 * Note: 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(" cannot be the root element");
830 }
831 parseInclude(parser, context, parent, attrs);
832 } else if (TAG_MERGE.equals(name)) {
833 throw new InflateException(" 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 *
710 * Note: 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. Note that
* this may be null.
* @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 name found in this
559 * LayoutInflater's ClassLoader.
560 *
561 *
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 ? "" : 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(token);
154 if (st == NULL) {
155 return -1;
156 }
157
158 return static_cast(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(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& file, ResXMLTree* outTree,
555 bool stripAll, bool keepComments,
556 const char** cDataTags)
557{
558 sp 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 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 }