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    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容