Android Layout inflate过程分析(1)-阿里云开发者社区

开发者社区> lusing> 正文

Android Layout inflate过程分析(1)

简介: Android inflate的前因后果
+关注继续查看

综述

在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结束了,处理处理扔到容器里吧。
  1. 构建XmlPullParserFactory的实例. 放在try...catch中是因为有XmlPullParserException异常要处理。
        try {
            XmlPullParserFactory pullParserFactory = XmlPullParserFactory
                    .newInstance();
  1. 获取XmlPullParser的实例
            XmlPullParser xmlPullParser = pullParserFactory.newPullParser();
  1. 设置输入流 xml文件
            xmlPullParser.setInput(
                    context.getResources().openRawResource(R.raw.xml文件名),
                    "UTF-8");
  1. 开始解析
            int eventType = xmlPullParser.getEventType();
  1. 主循环, 直至遇到文档结束事件XmlPullParser.END_DOCUMENT
            try {
                while (eventType != XmlPullParser.END_DOCUMENT) {
  1. XmlPullParser.START_DOCUMENT事件时,处理文档开始。此处可以准备一个数据结构存储等。
                    String nodeName = xmlPullParser.getName();
                    switch (eventType) {
                    case XmlPullParser.START_DOCUMENT:
                        // 处理文档开始
                        break;
  1. 处理节点开始事件
                    case XmlPullParser.START_TAG:
                        // 此处开始一个节点,可以读取下面的属性和值
                        break;
  1. 处理节点结束事件,比如可以将对象加入到容器中。
                    case XmlPullParser.END_TAG:
                        // 结束节点
                        break;
                    default:
                        break;
                    }
  1. 读取下一个事件
                    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>&nbsp;&nbsp;&nbsp;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);
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.view.",最后还调回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}

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【文本分析】新闻分类
新闻分类是文本挖掘领域较为常见的场景。目前很多媒体或是内容生产商对于新闻这种文本的分类常常采用人肉打标的方式,消耗了大量的人力资源。本文尝试通过智能的文本挖掘算法对于新闻文本进行分类。无需任何人肉打标,完全由机器智能化实现。<br />数据源:网络爬取新闻数据<br />数据大小:261 KB<br />字段数量:3<br />使用组件:过滤与映射,SQL脚本,读数据表,增加序号列,类型转换<br />
4277 0
【重新发现PostgreSQL之美】- 35 茅山道士 - rdkit 化学分析
大家好,这里是重新发现PostgreSQL之美 - 35 茅山道士 - rdkit 化学分析
13 0
Android Layout inflate过程分析(1)
Android inflate的前因后果
5699 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
12313 0
【项目分析】利用C#改写JAVA中的Base64.DecodeBase64以及Inflater解码
原文:【项目分析】利用C#改写JAVA中的Base64.DecodeBase64以及Inflater解码 最近正在进行项目服务的移植工作,即将JAVA服务的程序移植到DotNet平台中。 在JAVA程序中,有个HTTP请求数据头中,包含一个BASE64编码的字符串,例如: eJyVjMENgDAMA1fpBMjnIkp3ZzZEpAa1PLmXY10sDdqBqr54Ww5AthG7zxJYa0MYr9p7bPFnK/uqjCj06y7JfHwAX3AhhA== 现在需要将这个字符串转化成原始字符串,原始字符串包含许多重要的信息。
1064 0
+关注
lusing
刘子瑛,阿里系统框架专家。工作十余年,一直对新编程语言、新开发方法、数学与算法相关和并发等相关领域保持浓厚的兴趣。乐于通过技术分享促进新技术。
181
文章
60
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载