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>   For performance
434     * reasons, view inflation relies heavily on pre-processing of XML files
435     * that is done at build time. Therefore, it is not currently possible to
436     * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
437     *
438     * @param parser XML dom node containing the description of the view
439     *        hierarchy.
440     * @param root Optional view to be the parent of the generated hierarchy (if
441     *        <em>attachToRoot</em> is true), or else simply an object that
442     *        provides a set of LayoutParams values for root of the returned
443     *        hierarchy (if <em>attachToRoot</em> is false.)
444     * @param attachToRoot Whether the inflated hierarchy should be attached to
445     *        the root parameter? If false, root is only used to create the
446     *        correct subclass of LayoutParams for the root view in the XML.
447     * @return The root View of the inflated hierarchy. If root was supplied and
448     *         attachToRoot is true, this is root; otherwise it is the root of
449     *         the inflated XML file.
450     */
451    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
452        synchronized (mConstructorArgs) {
453            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
454
455            final Context inflaterContext = mContext;
456            final AttributeSet attrs = Xml.asAttributeSet(parser);

针对上面这句final AttributeSet attrs = Xml.asAttributeSet(parser);有必要增加一点说明。它的作用是将XmlPullParser转化成AttributesSet.

后面我们会分析到,我们所用的XmlPullParser是XMLBlock.Parser,是XmlPullParser和AttributeSet两个接口都实现了,自然是最好了。如果不是的话,也有办法,生成一个XmlPullAttributes的类,通过它来读取属性。

代码如下:

175    public static AttributeSet asAttributeSet(XmlPullParser parser) {
176        return (parser instanceof AttributeSet)
177                ? (AttributeSet) parser
178                : new XmlPullAttributes(parser);
179    }

XmlPullAttributes的封装是通过对XmlPullParser.getAttributeValue的封装完成的。

比如我们还是看获取boolean值的:

63    public boolean getAttributeBooleanValue(String namespace, String attribute,
64            boolean defaultValue) {
65        return XmlUtils.convertValueToBoolean(
66            getAttributeValue(namespace, attribute), defaultValue);
67    }

它是先通过getAttributeValue的调用,然后再做类型转换。

45    public String getAttributeValue(String namespace, String name) {
46        return mParser.getAttributeValue(namespace, name);
47    }

getAttributeValue直接调用mParser的同名方法。这个mParser就是构造时传入的XmlPullParser对象。

28class XmlPullAttributes implements AttributeSet {
29    public XmlPullAttributes(XmlPullParser parser) {
30        mParser = parser;
31    }
...
145
146    /*package*/ XmlPullParser mParser;
147}

也就是说,在非编译的xml中,可以通过这个方法来访问属性。

闲言少叙,我们还是先回到inflate的主线中来。

457            Context lastContext = (Context) mConstructorArgs[0];
458            mConstructorArgs[0] = inflaterContext;
459            View result = root;
460

下面开始就是我们前面讨论过的XmlPullParser的经典过程了。下面是要寻找根节点,如果遇到的不是XmlPullParser.START_TAG,就继续往下找,直至遇到第一个Tag为止。

461            try {
462                // Look for the root node.
463                int type;
464                while ((type = parser.next()) != XmlPullParser.START_TAG &&
465                        type != XmlPullParser.END_DOCUMENT) {
466                    // Empty
467                }

如果一个Tag也没找到,就报错。

468
469                if (type != XmlPullParser.START_TAG) {
470                    throw new InflateException(parser.getPositionDescription()
471                            + ": No start tag found!");
472                }

找到根Tag了,先打几行log.

473
474                final String name = parser.getName();
475
476                if (DEBUG) {
477                    System.out.println("**************************");
478                    System.out.println("Creating root view: "
479                            + name);
480                    System.out.println("**************************");
481                }
482

如果是Tag是"merge"的话,如果有root可以attach,则递归调用rInflate去将merge的View attach到root上去。rInflate在下面会分析。

483                if (TAG_MERGE.equals(name)) {
484                    if (root == null || !attachToRoot) {
485                        throw new InflateException("<merge /> can be used only with a valid "
486                                + "ViewGroup root and attachToRoot=true");
487                    }
488
489                    rInflate(parser, root, inflaterContext, attrs, false);
490                } else {

如果不是merge,那么说明要建立一个新的根节点,调用createViewFromTag去创建之。createViewFromTag下面分析。

491                    // Temp is the root view that was found in the xml
492                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
493
494                    ViewGroup.LayoutParams params = null;
495
496                    if (root != null) {
497                        if (DEBUG) {
498                            System.out.println("Creating params from root: " +
499                                    root);
500                        }
501                        // Create layout params that match root, if supplied
502                        params = root.generateLayoutParams(attrs);
503                        if (!attachToRoot) {
504                            // Set the layout params for temp if we are not
505                            // attaching. (If we are, we use addView, below)
506                            temp.setLayoutParams(params);
507                        }
508                    }
509
510                    if (DEBUG) {
511                        System.out.println("-----> start inflating children");
512                    }
513

根节点创建就绪,调用rInflateChildren去建立根节点下面的子节点树。

514                    // Inflate all children under temp against its context.
515                    rInflateChildren(parser, temp, attrs, true);
516
517                    if (DEBUG) {
518                        System.out.println("-----> done inflating children");
519                    }
520

如果root节点非空,而且要求attachToRoot,则将我们新建立的根节点attach到root上。否则,我们直接将我们生成的temp根节点作为根节点返回。

521                    // We are supposed to attach all the views we found (int temp)
522                    // to root. Do that now.
523                    if (root != null && attachToRoot) {
524                        root.addView(temp, params);
525                    }
526
527                    // Decide whether to return the root that was passed in or the
528                    // top view found in xml.
529                    if (root == null || !attachToRoot) {
530                        result = temp;
531                    }
532                }
533
534            } catch (XmlPullParserException e) {
535                InflateException ex = new InflateException(e.getMessage());
536                ex.initCause(e);
537                throw ex;
538            } catch (Exception e) {
539                InflateException ex = new InflateException(
540                        parser.getPositionDescription()
541                                + ": " + e.getMessage());
542                ex.initCause(e);
543                throw ex;
544            } finally {
545                // Don't retain static reference on context.
546                mConstructorArgs[0] = lastContext;
547                mConstructorArgs[1] = null;
548            }
549
550            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
551
552            return result;
553        }
554    }

LayoutInflater.rInflateChildren

我们先挑个小的方法继续看,先看看上面非merge情况下建立子树的方法rInflateChildren。

rInflateChildren只是rInflate的简单封装。rInflate其实比rInflateChildren就多了一个Context参数,其它都透传。

789    /**
790     * Recursive method used to inflate internal (non-root) children. This
791     * method calls through to {@link #rInflate} using the parent context as
792     * the inflation context.
793     * <strong>Note:</strong> Default visibility so the BridgeInflater can
794     * call it.
795     */
796    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
797            boolean finishInflate) throws XmlPullParserException, IOException {
798        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
799    }

LayoutInflater.rInflate

这个才是递归搜索建立子树的正主。

801    /**
802     * Recursive method used to descend down the xml hierarchy and instantiate
803     * views, instantiate their children, and then call onFinishInflate().
804     * <p>
805     * <strong>Note:</strong> Default visibility so the BridgeInflater can
806     * override it.
807     */
808    void rInflate(XmlPullParser parser, View parent, Context context,
809            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
810

与根相比,子树的方法加上了对层数的限制。

811        final int depth = parser.getDepth();
812        int type;
813
814        while (((type = parser.next()) != XmlPullParser.END_TAG ||
815                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
816
817            if (type != XmlPullParser.START_TAG) {
818                continue;
819            }
820
821            final String name = parser.getName();
822

下面要处理一些特殊的Tag,它们的定义如下:

100    private static final String TAG_MERGE = "merge";
101    private static final String TAG_INCLUDE = "include";
102    private static final String TAG_1995 = "blink";
103    private static final String TAG_REQUEST_FOCUS = "requestFocus";
104    private static final String TAG_TAG = "tag";
823            if (TAG_REQUEST_FOCUS.equals(name)) {
824                parseRequestFocus(parser, parent);
825            } else if (TAG_TAG.equals(name)) {
826                parseViewTag(parser, parent, attrs);
827            } else if (TAG_INCLUDE.equals(name)) {
828                if (parser.getDepth() == 0) {
829                    throw new InflateException("<include /> cannot be the root element");
830                }
831                parseInclude(parser, context, parent, attrs);
832            } else if (TAG_MERGE.equals(name)) {
833                throw new InflateException("<merge /> must be the root element");
834            } else {

还是调用createViewFromTag来生成本级的View对象,然后还是调用rInflateChildren去建子树,实现递归。

835                final View view = createViewFromTag(parent, name, context, attrs);
836                final ViewGroup viewGroup = (ViewGroup) parent;
837                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
838                rInflateChildren(parser, view, attrs, true);

子树建好后,add到父viewGroup中去。

839                viewGroup.addView(view, params);
840            }
841        }
842

递归结束的话,调用onFinishInflate().

843        if (finishInflate) {
844            parent.onFinishInflate();
845        }
846    }

LayoutInflater.createViewFromTag

根据Tag创建View对象。

707    /**
708     * Creates a view from a tag name using the supplied attribute set.
709     * <p>
710     * <strong>Note:</strong> Default visibility so the BridgeInflater can
711     * override it.
712     *
713     * @param parent the parent view, used to inflate layout params
714     * @param name the name of the XML tag used to define the view
715     * @param context the inflation context for the view, typically the
716     *                {@code parent} or base layout inflater context
717     * @param attrs the attribute set for the XML tag used to define the view
718     * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
719     *                        attribute (if set) for the view being inflated,
720     *                        {@code false} otherwise
721     */
722    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
723            boolean ignoreThemeAttr) {

如果Tag的名字是view的话,就从属性中读取类名作为新的name.

724        if (name.equals("view")) {
725            name = attrs.getAttributeValue(null, "class");
726        }
727

下面处理主题相关

728        // Apply a theme wrapper, if allowed and one is specified.
729        if (!ignoreThemeAttr) {
730            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
731            final int themeResId = ta.getResourceId(0, 0);
732            if (themeResId != 0) {
733                context = new ContextThemeWrapper(context, themeResId);
734            }
735            ta.recycle();
736        }

如前面所讲的,如果有定义自己的工厂类的话,则调用那些工厂类的onCreateView。

743        try {
744            View view;
745            if (mFactory2 != null) {
746                view = mFactory2.onCreateView(parent, name, context, attrs);
747            } else if (mFactory != null) {
748                view = mFactory.onCreateView(name, context, attrs);
749            } else {
750                view = null;
751            }
752
753            if (view == null && mPrivateFactory != null) {
754                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
755            }
756

如果没有自定义工厂类,则调用LayoutInflater中的onCreateView或者createView。其中onCreateView也只是简单封装一下,唯一做的一件事就是将省略掉的android.view包名给补上。这样,createViewFromTag的主要逻辑也就结束了。

757            if (view == null) {
758                final Object lastContext = mConstructorArgs[0];
759                mConstructorArgs[0] = context;
760                try {
761                    if (-1 == name.indexOf('.')) {
762                        view = onCreateView(parent, name, attrs);
763                    } else {
764                        view = createView(name, null, attrs);
765                    }
766                } finally {
767                    mConstructorArgs[0] = lastContext;
768                }
769            }
770
771            return view;
772        } catch (InflateException e) {
773            throw e;
774
775        } catch (ClassNotFoundException e) {
776            final InflateException ie = new InflateException(attrs.getPositionDescription()
777                    + ": Error inflating class " + name);
778            ie.initCause(e);
779            throw ie;
780
781        } catch (Exception e) {
782            final InflateException ie = new InflateException(attrs.getPositionDescription()
783                    + ": Error inflating class " + name);
784            ie.initCause(e);
785            throw ie;
786        }
787    }

LayoutInflater.onCreateView

这个方法啥情况,唯一的作用就是把parent参数给扔了,呵呵。

    /**
     * Version of {@link #onCreateView(String, AttributeSet)} that also takes
     * the future parent of the view being constructed. The default
     * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
     * 
     * @param parent
     *            The future parent of the returned view. <em>Note that
     * this may be null.</em>
     * @param name
     *            The fully qualified class name of the View to be create.
     * @param attrs
     *            An AttributeSet of attributes to apply to the View.
     * 
     * @return View The View created.
     */
    protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }

两个参数的版本,加个前缀"android.widget.",最后还调回createView.

    /**
     * This routine is responsible for creating the correct subclass of View
     * given the xml element name. Override it to handle custom view objects. If
     * you override this in your subclass be sure to call through to
     * super.onCreateView(name) for names you do not recognize.
     * 
     * @param name
     *            The fully qualified class name of the View to be create.
     * @param attrs
     *            An AttributeSet of attributes to apply to the View.
     * 
     * @return View The View created.
     */
    protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }

LayoutInflater.createView

通过反射去生成View对象。

556    /**
557     * Low-level function for instantiating a view by name. This attempts to
558     * instantiate a view class of the given <var>name</var> found in this
559     * LayoutInflater's ClassLoader.
560     *
561     * <p>
562     * There are two things that can happen in an error case: either the
563     * exception describing the error will be thrown, or a null will be
564     * returned. You must deal with both possibilities -- the former will happen
565     * the first time createView() is called for a class of a particular name,
566     * the latter every time there-after for that class name.
567     *
568     * @param name The full name of the class to be instantiated.
569     * @param attrs The XML attributes supplied for this instance.
570     *
571     * @return View The newly instantiated view, or null.
572     */
573    public final View createView(String name, String prefix, AttributeSet attrs)
574            throws ClassNotFoundException, InflateException {

sConstructorMap是构造方法的缓存,如果有了就用现成的吧。

575        Constructor<? extends View> constructor = sConstructorMap.get(name);
576        Class<? extends View> clazz = null;
577
578        try {
579            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
580
581            if (constructor == null) {
582                // Class not found in the cache, see if it's real, and try to add it
583                clazz = mContext.getClassLoader().loadClass(
584                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
585

如前所述,如果定义了过滤的话,则调用mFilter的onLoadClass判断是否允许,不允许则调用failNotAllowed去抛Exception。

586                if (mFilter != null && clazz != null) {
587                    boolean allowed = mFilter.onLoadClass(clazz);
588                    if (!allowed) {
589                        failNotAllowed(name, prefix, attrs);
590                    }
591                }
592                constructor = clazz.getConstructor(mConstructorSignature);
593                constructor.setAccessible(true);
594                sConstructorMap.put(name, constructor);
595            } else {

下面一个分支是能拿到可重用的构造器的情况

596                // If we have a filter, apply it to cached constructor
597                if (mFilter != null) {
598                    // Have we seen this name before?
599                    Boolean allowedState = mFilterMap.get(name);
600                    if (allowedState == null) {
601                        // New class -- remember whether it is allowed
602                        clazz = mContext.getClassLoader().loadClass(
603                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
604
605                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
606                        mFilterMap.put(name, allowed);
607                        if (!allowed) {
608                            failNotAllowed(name, prefix, attrs);
609                        }
610                    } else if (allowedState.equals(Boolean.FALSE)) {
611                        failNotAllowed(name, prefix, attrs);
612                    }
613                }
614            }
615
616            Object[] args = mConstructorArgs;
617            args[1] = attrs;
618
619            final View view = constructor.newInstance(args);

如果是ViewStub的话,暂时不需要inflate了,但是需要clone一个inflater给它。

620            if (view instanceof ViewStub) {
621                // Use the same context when inflating ViewStub later.
622                final ViewStub viewStub = (ViewStub) view;
623                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
624            }
625            return view;
626
627        } catch (NoSuchMethodException e) {
628            InflateException ie = new InflateException(attrs.getPositionDescription()
629                    + ": Error inflating class "
630                    + (prefix != null ? (prefix + name) : name));
631            ie.initCause(e);
632            throw ie;
633
634        } catch (ClassCastException e) {
635            // If loaded class is not a View subclass
636            InflateException ie = new InflateException(attrs.getPositionDescription()
637                    + ": Class is not a View "
638                    + (prefix != null ? (prefix + name) : name));
639            ie.initCause(e);
640            throw ie;
641        } catch (ClassNotFoundException e) {
642            // If loadClass fails, we should propagate the exception.
643            throw e;
644        } catch (Exception e) {
645            InflateException ie = new InflateException(attrs.getPositionDescription()
646                    + ": Error inflating class "
647                    + (clazz == null ? "<unknown>" : clazz.getName()));
648            ie.initCause(e);
649            throw ie;
650        } finally {
651            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
652        }
653    }

failNotAllowed

就是个Exception拼字符串的方法。

    /**
     * Throw an exception because the specified class is not allowed to be
     * inflated.
     */
    private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
        throw new InflateException(attrs.getPositionDescription()
                + ": Class not allowed to be inflated "
                + (prefix != null ? (prefix + name) : name));
    }

XmlBlock.Parser

当梦想照进现实,我们看看XmlResourceParser接口的真正实现类XmlBlock.Parser。

定义

77    /*package*/ final class Parser implements XmlResourceParser {
78        Parser(long parseState, XmlBlock block) {
79            mParseState = parseState;
80            mBlock = block;
81            block.mOpenCount++;
82        }

native函数唱主角

基本上主要的功能都靠native函数来实现

491    private static final native long nativeCreate(byte[] data,
492                                                 int offset,
493                                                 int size);
494    private static final native long nativeGetStringBlock(long obj);
495
496    private static final native long nativeCreateParseState(long obj);
497    /*package*/ static final native int nativeNext(long state);
498    private static final native int nativeGetNamespace(long state);
499    /*package*/ static final native int nativeGetName(long state);
500    private static final native int nativeGetText(long state);
501    private static final native int nativeGetLineNumber(long state);
502    private static final native int nativeGetAttributeCount(long state);
503    private static final native int nativeGetAttributeNamespace(long state, int idx);
504    private static final native int nativeGetAttributeName(long state, int idx);
505    private static final native int nativeGetAttributeResource(long state, int idx);
506    private static final native int nativeGetAttributeDataType(long state, int idx);
507    private static final native int nativeGetAttributeData(long state, int idx);
508    private static final native int nativeGetAttributeStringValue(long state, int idx);
509    private static final native int nativeGetIdAttribute(long state);
510    private static final native int nativeGetClassAttribute(long state);
511    private static final native int nativeGetStyleAttribute(long state);
512    private static final native int nativeGetAttributeIndex(long state, String namespace, String name);
513    private static final native void nativeDestroyParseState(long state);
514
515    private static final native void nativeDestroy(long obj);

这些方法和本地函数的对照表在/frameworks/base/core/jni/android_util_XmlBlock.cpp中,

364/*
365 * JNI registration.
366 */
367static JNINativeMethod gXmlBlockMethods[] = {
368    /* name, signature, funcPtr */
369    { "nativeCreate",               "([BII)J",
370            (void*) android_content_XmlBlock_nativeCreate },
371    { "nativeGetStringBlock",       "(J)J",
372            (void*) android_content_XmlBlock_nativeGetStringBlock },
373    { "nativeCreateParseState",     "(J)J",
374            (void*) android_content_XmlBlock_nativeCreateParseState },
375    { "nativeNext",                 "(J)I",
376            (void*) android_content_XmlBlock_nativeNext },
377    { "nativeGetNamespace",         "(J)I",
378            (void*) android_content_XmlBlock_nativeGetNamespace },
379    { "nativeGetName",              "(J)I",
380            (void*) android_content_XmlBlock_nativeGetName },
381    { "nativeGetText",              "(J)I",
382            (void*) android_content_XmlBlock_nativeGetText },
383    { "nativeGetLineNumber",        "(J)I",
384            (void*) android_content_XmlBlock_nativeGetLineNumber },
385    { "nativeGetAttributeCount",    "(J)I",
386            (void*) android_content_XmlBlock_nativeGetAttributeCount },
387    { "nativeGetAttributeNamespace","(JI)I",
388            (void*) android_content_XmlBlock_nativeGetAttributeNamespace },
389    { "nativeGetAttributeName",     "(JI)I",
390            (void*) android_content_XmlBlock_nativeGetAttributeName },
391    { "nativeGetAttributeResource", "(JI)I",
392            (void*) android_content_XmlBlock_nativeGetAttributeResource },
393    { "nativeGetAttributeDataType", "(JI)I",
394            (void*) android_content_XmlBlock_nativeGetAttributeDataType },
395    { "nativeGetAttributeData",    "(JI)I",
396            (void*) android_content_XmlBlock_nativeGetAttributeData },
397    { "nativeGetAttributeStringValue", "(JI)I",
398            (void*) android_content_XmlBlock_nativeGetAttributeStringValue },
399    { "nativeGetAttributeIndex",    "(JLjava/lang/String;Ljava/lang/String;)I",
400            (void*) android_content_XmlBlock_nativeGetAttributeIndex },
401    { "nativeGetIdAttribute",      "(J)I",
402            (void*) android_content_XmlBlock_nativeGetIdAttribute },
403    { "nativeGetClassAttribute",   "(J)I",
404            (void*) android_content_XmlBlock_nativeGetClassAttribute },
405    { "nativeGetStyleAttribute",   "(J)I",
406            (void*) android_content_XmlBlock_nativeGetStyleAttribute },
407    { "nativeDestroyParseState",    "(J)V",
408            (void*) android_content_XmlBlock_nativeDestroyParseState },
409    { "nativeDestroy",              "(J)V",
410            (void*) android_content_XmlBlock_nativeDestroy },
411};

我们看几个例子:

getText

getText是涉及到访问资源的,我们先看看这个。

资源ID查找的过程是在nativeGetText本地方法中实现的,如下所示:

141        public String getText() {
142            int id = nativeGetText(mParseState);
143            return id >= 0 ? mStrings.get(id).toString() : null;
144        }

查上面的表,找到对应的函数:

150static jint android_content_XmlBlock_nativeGetText(JNIEnv* env, jobject clazz,
151                                                jlong token)
152{
153    ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
154    if (st == NULL) {
155        return -1;
156    }
157
158    return static_cast<jint>(st->getTextID());
159}

核心功能指向一个C++的类ResXMLParser,我们下面再看这个类的详细定义,先看看getTextID().

1062int32_t ResXMLParser::getTextID() const
1063{
1064    if (mEventCode == TEXT) {
1065        return dtohl(((const ResXMLTree_cdataExt*)mCurExt)->data.index);
1066    }
1067    return -1;
1068}

next

我们再看一个next的实现。

236        public int next() throws XmlPullParserException,IOException {
237            if (!mStarted) {
238                mStarted = true;
239                return START_DOCUMENT;
240            }
241            if (mParseState == 0) {
242                return END_DOCUMENT;
243            }
244            int ev = nativeNext(mParseState);
245            if (mDecNextDepth) {
246                mDepth--;
247                mDecNextDepth = false;
248            }
249            switch (ev) {
250            case START_TAG:
251                mDepth++;
252                break;
253            case END_TAG:
254                mDecNextDepth = true;
255                break;
256            }
257            mEventType = ev;
258            if (ev == END_DOCUMENT) {
259                // Automatically close the parse when we reach the end of
260                // a document, since the standard XmlPullParser interface
261                // doesn't have such an API so most clients will leave us
262                // dangling.
263                close();
264            }
265            return ev;
266        }

基本上处理一下深度等,主要逻辑全靠JNI函数。

94static jint android_content_XmlBlock_nativeNext(JNIEnv* env, jobject clazz,
95                                             jlong token)
96{
97    ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
98    if (st == NULL) {
99        return ResXMLParser::END_DOCUMENT;
100    }
101
102    do {
103        ResXMLParser::event_code_t code = st->next();
104        switch (code) {
105            case ResXMLParser::START_TAG:
106                return 2;
107            case ResXMLParser::END_TAG:
108                return 3;
109            case ResXMLParser::TEXT:
110                return 4;
111            case ResXMLParser::START_DOCUMENT:
112                return 0;
113            case ResXMLParser::END_DOCUMENT:
114                return 1;
115            case ResXMLParser::BAD_DOCUMENT:
116                goto bad;
117            default:
118                break;
119        }
120    } while (true);
121
122bad:
123    jniThrowException(env, "org/xmlpull/v1/XmlPullParserException",
124            "Corrupt XML binary file");
125    return ResXMLParser::BAD_DOCUMENT;
126}

最终的实现还是靠ResXMLParser:

1034ResXMLParser::event_code_t ResXMLParser::next()
1035{
1036    if (mEventCode == START_DOCUMENT) {
1037        mCurNode = mTree.mRootNode;
1038        mCurExt = mTree.mRootExt;
1039        return (mEventCode=mTree.mRootCode);
1040    } else if (mEventCode >= FIRST_CHUNK_CODE) {
1041        return nextNode();
1042    }
1043    return mEventCode;
1044}

ResXMLParser

这个类的定义在/frameworks/base/include/androidfw/ResourceTypes.h中,

680class ResXMLParser
681{
682public:
683    ResXMLParser(const ResXMLTree& tree);
684
685    enum event_code_t {
686        BAD_DOCUMENT = -1,
687        START_DOCUMENT = 0,
688        END_DOCUMENT = 1,
689
690        FIRST_CHUNK_CODE = RES_XML_FIRST_CHUNK_TYPE,
691
692        START_NAMESPACE = RES_XML_START_NAMESPACE_TYPE,
693        END_NAMESPACE = RES_XML_END_NAMESPACE_TYPE,
694        START_TAG = RES_XML_START_ELEMENT_TYPE,
695        END_TAG = RES_XML_END_ELEMENT_TYPE,
696        TEXT = RES_XML_CDATA_TYPE
697    };
698
699    struct ResXMLPosition
700    {
701        event_code_t                eventCode;
702        const ResXMLTree_node*      curNode;
703        const void*                 curExt;
704    };
705
706    void restart();
707
708    const ResStringPool& getStrings() const;
709
710    event_code_t getEventType() const;
711    // Note, unlike XmlPullParser, the first call to next() will return
712    // START_TAG of the first element.
713    event_code_t next();
714
715    // These are available for all nodes:
716    int32_t getCommentID() const;
717    const char16_t* getComment(size_t* outLen) const;
718    uint32_t getLineNumber() const;
719
720    // This is available for TEXT:
721    int32_t getTextID() const;
722    const char16_t* getText(size_t* outLen) const;
723    ssize_t getTextValue(Res_value* outValue) const;
724
725    // These are available for START_NAMESPACE and END_NAMESPACE:
726    int32_t getNamespacePrefixID() const;
727    const char16_t* getNamespacePrefix(size_t* outLen) const;
728    int32_t getNamespaceUriID() const;
729    const char16_t* getNamespaceUri(size_t* outLen) const;
730
731    // These are available for START_TAG and END_TAG:
732    int32_t getElementNamespaceID() const;
733    const char16_t* getElementNamespace(size_t* outLen) const;
734    int32_t getElementNameID() const;
735    const char16_t* getElementName(size_t* outLen) const;
736
737    // Remaining methods are for retrieving information about attributes
738    // associated with a START_TAG:
739
740    size_t getAttributeCount() const;
741
742    // Returns -1 if no namespace, -2 if idx out of range.
743    int32_t getAttributeNamespaceID(size_t idx) const;
744    const char16_t* getAttributeNamespace(size_t idx, size_t* outLen) const;
745
746    int32_t getAttributeNameID(size_t idx) const;
747    const char16_t* getAttributeName(size_t idx, size_t* outLen) const;
748    uint32_t getAttributeNameResID(size_t idx) const;
749
750    // These will work only if the underlying string pool is UTF-8.
751    const char* getAttributeNamespace8(size_t idx, size_t* outLen) const;
752    const char* getAttributeName8(size_t idx, size_t* outLen) const;
753
754    int32_t getAttributeValueStringID(size_t idx) const;
755    const char16_t* getAttributeStringValue(size_t idx, size_t* outLen) const;
756
757    int32_t getAttributeDataType(size_t idx) const;
758    int32_t getAttributeData(size_t idx) const;
759    ssize_t getAttributeValue(size_t idx, Res_value* outValue) const;
760
761    ssize_t indexOfAttribute(const char* ns, const char* attr) const;
762    ssize_t indexOfAttribute(const char16_t* ns, size_t nsLen,
763                             const char16_t* attr, size_t attrLen) const;
764
765    ssize_t indexOfID() const;
766    ssize_t indexOfClass() const;
767    ssize_t indexOfStyle() const;
768
769    void getPosition(ResXMLPosition* pos) const;
770    void setPosition(const ResXMLPosition& pos);
771
772private:
773    friend class ResXMLTree;
774
775    event_code_t nextNode();
776
777    const ResXMLTree&           mTree;
778    event_code_t                mEventCode;
779    const ResXMLTree_node*      mCurNode;
780    const void*                 mCurExt;
781};

不支持的功能

不支持的feature

XmlBlock.Parser只支持两个feature:

  • FEATURE_PROCESS_NAMESPACES
  • FEATURE_REPORT_NAMESPACE_ATTRIBUTES

DTD是不支持的,也不要提validation了

84        public void setFeature(String name, boolean state) throws XmlPullParserException {
85            if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
86                return;
87            }
88            if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
89                return;
90            }
91            throw new XmlPullParserException("Unsupported feature: " + name);
92        }
93        public boolean getFeature(String name) {
94            if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
95                return true;
96            }
97            if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
98                return true;
99            }
100            return false;
101        }

属性不支持

setProperty不支持啦~

102        public void setProperty(String name, Object value) throws XmlPullParserException {
103            throw new XmlPullParserException("setProperty() not supported");
104        }
105        public Object getProperty(String name) {
106            return null;
107        }

不支持定义input

setInput不支持

108        public void setInput(Reader in) throws XmlPullParserException {
109            throw new XmlPullParserException("setInput() not supported");
110        }
111        public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
112            throw new XmlPullParserException("setInput() not supported");
113        }

Entity Replacement Text不支持

114        public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException {
115            throw new XmlPullParserException("defineEntityReplacementText() not supported");
116        }

命名空间不支持

117        public String getNamespacePrefix(int pos) throws XmlPullParserException {
118            throw new XmlPullParserException("getNamespacePrefix() not supported");
119        }
120        public String getInputEncoding() {
121            return null;
122        }
123        public String getNamespace(String prefix) {
124            throw new RuntimeException("getNamespace() not supported");
125        }
126        public int getNamespaceCount(int depth) throws XmlPullParserException {
127            throw new XmlPullParserException("getNamespaceCount() not supported");
128        }

XMLBlock的编译生成 - aapt中的XMLNode

这个过程的实现在/frameworks/base/tools/aapt/XMLNode.cpp中.

例如,下面的函数就是将AaptFile生成前面我们所看到的ResXMLTree对象。

554status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
555                          bool stripAll, bool keepComments,
556                          const char** cDataTags)
557{
558    sp<XMLNode> root = XMLNode::parse(file);
559    if (root == NULL) {
560        return UNKNOWN_ERROR;
561    }
562    root->removeWhitespace(stripAll, cDataTags);
563
564    if (kIsDebug) {
565        printf("Input XML from %s:\n", (const char*)file->getPrintableSource());
566        root->print();
567    }
568    sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8());
569    status_t err = root->flatten(rsc, !keepComments, false);
570    if (err != NO_ERROR) {
571        return err;
572    }
573    err = outTree->setTo(rsc->getData(), rsc->getSize(), true);
574    if (err != NO_ERROR) {
575        return err;
576    }
577
578    if (kIsDebug) {
579        printf("Output XML:\n");
580        printXMLBlock(outTree);
581    }
582
583    return NO_ERROR;
584}

UI控件

ViewGroup

ViewGroup实现了ViewManager和ViewParent两个接口。

@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {

ViewManager

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

在inflate的过程中,主要用到的是构造和addView。第一个View参数不用说了,要加入的子View。另外一个重要的参数是ViewGroup.LayoutParams. 这个参数的主要用途是指定子View的位置。

ViewGroup.LayoutParams

ViewGroup.LayoutParams的基本属性

作为一个基本类,它的主要作用是指定子View的宽和高。
除了直接指定大小之外,它还接受两个值:MATCH_PARENT(老的名字叫FILL_PARENT)和WRAP_CONTENT. 这大家都太熟悉了,就不多说了。

下面抽象一下,常量和宽高,是我们熟悉的部分。

6770    public static class LayoutParams {
...
6777        @SuppressWarnings({"UnusedDeclaration"})
6778        @Deprecated
6779        public static final int FILL_PARENT = -1;
...
6786        public static final int MATCH_PARENT = -1;
...
6793        public static final int WRAP_CONTENT = -2;
...
6804        public int width;
6815        public int height;

下面是布局动画的,先放在这里,用到再说。

        /**
         * Used to animate layouts.
         */
        public LayoutAnimationController.AnimationParameters layoutAnimationParameters;

ViewGroup.LayoutParams的构造方法

别看下面都是又是主题,又是绕来绕去的高大上方法。本质上,ViewGroup.LayoutParams就是宽和高两个域。这两个值赋正确了,其它的就都不用管。值可以是具体的pixel值,也可以是MATCH_PARENT或者WRAP_CONTENT两个常量。

我们把代码中的几个构造方法的顺序调整一下,先看说人话的。
第一个是最正宗的赋值型构造,两个值一赋就OK。

public LayoutParams(int width, int height) {
    this.width = width;
    this.height = height;
}

再看下拷贝构造方法:

/**
 * Copy constructor. Clones the width and height values of the source.
 *
 * @param source The layout params to copy from.
 */
public LayoutParams(LayoutParams source) {
    this.width = source.width;
    this.height = source.height;
}

然后再看说文言的,这个得转几道手,看几个其它类的方法:

6840        public LayoutParams(Context c, AttributeSet attrs) {
6841            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
6842            setBaseAttributes(a,
6843                    R.styleable.ViewGroup_Layout_layout_width,
6844                    R.styleable.ViewGroup_Layout_layout_height);
6845            a.recycle();
6846        }

首先来看这个Context.obtainStyledAttributes,从主题中读取值。先获取当前上下文的主题,然后调用主题类的obtainStyledAttributes.

530    public final TypedArray obtainStyledAttributes(
531            AttributeSet set, @StyleableRes int[] attrs) {
532        return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
533    }

我们移步/frameworks/base/core/java/android/content/res/Resources.java,看看Theme中的obtainStyledAttributes的实现,我们删节一下,一共也没几句逻辑:

1593        public TypedArray obtainStyledAttributes(AttributeSet set,
1594                @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
1595            final int len = attrs.length;
1596            final TypedArray array = TypedArray.obtain(Resources.this, len);
1597
...
1602            final XmlBlock.Parser parser = (XmlBlock.Parser)set;
1603            AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
1604                    parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);
1605
1606            array.mTheme = this;
1607            array.mXml = parser;
...
1638            return array;
1639        }

然后我们转战TypedArray.obtain:

43    static TypedArray obtain(Resources res, int len) {
44        final TypedArray attrs = res.mTypedArrayPool.acquire();
45        if (attrs != null) {
46            attrs.mLength = len;
47            attrs.mRecycled = false;
48
49            final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
50            if (attrs.mData.length >= fullLen) {
51                return attrs;
52            }
53
54            attrs.mData = new int[fullLen];
55            attrs.mIndices = new int[1 + len];
56            return attrs;
57        }
58
59        return new TypedArray(res,
60                new int[len*AssetManager.STYLE_NUM_ENTRIES],
61                new int[1+len], len);
62    }

得到了TypedArray结果之后,再通过setBaseAttributes将值设置好。上面已经反复强调了,在ViewGroup.LayoutParams一共就只有宽和高两个参数,不管怎么复杂地折腾,最终落实的一定是这两个值。

6881        /**
6882         * Extracts the layout parameters from the supplied attributes.
6883         *
6884         * @param a the style attributes to extract the parameters from
6885         * @param widthAttr the identifier of the width attribute
6886         * @param heightAttr the identifier of the height attribute
6887         */
6888        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
6889            width = a.getLayoutDimension(widthAttr, "layout_width");
6890            height = a.getLayoutDimension(heightAttr, "layout_height");
6891        }

MarginLayoutParams

ViewGroup.LayoutParams只有宽和高两个参数,简单是极简了。下面我们给它周围加个白边。一共6个变量,上下左右4个边距,加上起始和结束2个边距。

6969    public static class MarginLayoutParams extends ViewGroup.LayoutParams {
6970        /**
6971         * The left margin in pixels of the child. Margin values should be positive.
6972         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6973         * to this field.
6974         */
6975        @ViewDebug.ExportedProperty(category = "layout")
6976        public int leftMargin;
6977
6978        /**
6979         * The top margin in pixels of the child. Margin values should be positive.
6980         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6981         * to this field.
6982         */
6983        @ViewDebug.ExportedProperty(category = "layout")
6984        public int topMargin;
6985
6986        /**
6987         * The right margin in pixels of the child. Margin values should be positive.
6988         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6989         * to this field.
6990         */
6991        @ViewDebug.ExportedProperty(category = "layout")
6992        public int rightMargin;
6993
6994        /**
6995         * The bottom margin in pixels of the child. Margin values should be positive.
6996         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
6997         * to this field.
6998         */
6999        @ViewDebug.ExportedProperty(category = "layout")
7000        public int bottomMargin;
7001
7002        /**
7003         * The start margin in pixels of the child. Margin values should be positive.
7004         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
7005         * to this field.
7006         */
7007        @ViewDebug.ExportedProperty(category = "layout")
7008        private int startMargin = DEFAULT_MARGIN_RELATIVE;
7009
7010        /**
7011         * The end margin in pixels of the child. Margin values should be positive.
7012         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
7013         * to this field.
7014         */
7015        @ViewDebug.ExportedProperty(category = "layout")
7016        private int endMargin = DEFAULT_MARGIN_RELATIVE;

ViewGroup的构造

前三个都是陪太子读书的,一共是4个参数,前三个是给1个参数,2个参数,3个参数时其它给空参数时的调用。

560    public ViewGroup(Context context) {
561        this(context, null);
562    }
563
564    public ViewGroup(Context context, AttributeSet attrs) {
565        this(context, attrs, 0);
566    }
567
568    public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
569        this(context, attrs, defStyleAttr, 0);
570    }

其余就下面这一个,它一共有3步,我们分别分析。

572    public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
573        super(context, attrs, defStyleAttr, defStyleRes);
574        initViewGroup();
575        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
576    }

initViewGroup

这个好,基本上都是设一些属性

582    private void initViewGroup() {
583        // ViewGroup doesn't draw by default
584        if (!debugDraw()) {
585            setFlags(WILL_NOT_DRAW, DRAW_MASK);
586        }
587        mGroupFlags |= FLAG_CLIP_CHILDREN;
588        mGroupFlags |= FLAG_CLIP_TO_PADDING;
589        mGroupFlags |= FLAG_ANIMATION_DONE;
590        mGroupFlags |= FLAG_ANIMATION_CACHE;
591        mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
592
593        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
594            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
595        }
596
597        setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
598
599        mChildren = new View[ARRAY_INITIAL_CAPACITY];
600        mChildrenCount = 0;
601
602        mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
603    }

initFromAttributes

605    private void initFromAttributes(
606            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

又到了我们熟悉的context.obtainStyledAttributes,下面就是分门别类放东西,就不多说了。

607        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup, defStyleAttr,
608                defStyleRes);
609
610        final int N = a.getIndexCount();
611        for (int i = 0; i < N; i++) {
612            int attr = a.getIndex(i);
613            switch (attr) {
614                case R.styleable.ViewGroup_clipChildren:
615                    setClipChildren(a.getBoolean(attr, true));
616                    break;
617                case R.styleable.ViewGroup_clipToPadding:
618                    setClipToPadding(a.getBoolean(attr, true));
619                    break;
620                case R.styleable.ViewGroup_animationCache:
621                    setAnimationCacheEnabled(a.getBoolean(attr, true));
622                    break;
623                case R.styleable.ViewGroup_persistentDrawingCache:
624                    setPersistentDrawingCache(a.getInt(attr, PERSISTENT_SCROLLING_CACHE));
625                    break;
626                case R.styleable.ViewGroup_addStatesFromChildren:
627                    setAddStatesFromChildren(a.getBoolean(attr, false));
628                    break;
629                case R.styleable.ViewGroup_alwaysDrawnWithCache:
630                    setAlwaysDrawnWithCacheEnabled(a.getBoolean(attr, true));
631                    break;
632                case R.styleable.ViewGroup_layoutAnimation:
633                    int id = a.getResourceId(attr, -1);
634                    if (id > 0) {
635                        setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));
636                    }
637                    break;
638                case R.styleable.ViewGroup_descendantFocusability:
639                    setDescendantFocusability(DESCENDANT_FOCUSABILITY_FLAGS[a.getInt(attr, 0)]);
640                    break;
641                case R.styleable.ViewGroup_splitMotionEvents:
642                    setMotionEventSplittingEnabled(a.getBoolean(attr, false));
643                    break;
644                case R.styleable.ViewGroup_animateLayoutChanges:
645                    boolean animateLayoutChanges = a.getBoolean(attr, false);
646                    if (animateLayoutChanges) {
647                        setLayoutTransition(new LayoutTransition());
648                    }
649                    break;
650                case R.styleable.ViewGroup_layoutMode:
651                    setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED));
652                    break;
653                case R.styleable.ViewGroup_transitionGroup:
654                    setTransitionGroup(a.getBoolean(attr, false));
655                    break;
656                case R.styleable.ViewGroup_touchscreenBlocksFocus:
657                    setTouchscreenBlocksFocus(a.getBoolean(attr, false));
658                    break;
659            }
660        }
661
662        a.recycle();
663    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容