天天看點

LayoutInflater效率分析及源碼跟蹤

LayoutInflater效率分析及源碼跟蹤

一、效率分析

測試裝置

測試設配:魅族MX4

作業系統:Android5.1作業系統

CPU型号:聯發科MT6595

記憶體: 2GB

測試方法

使用LayoutInflater對3組不同複雜度的xml布局進行解析,每次解析100次,測試10次,求其100次的平均運作時間。機關為ms。

測試結果

|               |    Xml檔案              |Time(ms/100)|
|---------------|-----------------------|------------|
|第一組(簡單)   |深度2節點4 屬性30個       |   245ms    |
|               |深度3節點5 屬性36個       |   378ms    |

|第二組(中度)    |深度4節點19 屬性141個 |   1283ms   |
                |深度5節點24 屬性148個 |   1156ms   |

|第三組(重度)    |深度4節點37 屬性251個 |   2503ms   |
|               |深度4節點40 屬性306個 |   3272ms   |      

注:

深度指的是xml的嵌套層次,節點數指的是xml中View的個數,屬性個數指的是所有View的屬性總數。

結論:從資料表格上可以看出,解析100次所需要的平均時間,與xml中屬性的個數成正比關系。并且非常複雜的布局,其解析時間也能控制在30ms左右。

二、LayoutInflater源碼跟蹤

前言

1.編譯期

編譯期,aapt工具會對xml布局檔案做預處理(pre-processing),檢視aapt源碼,可以知道,其中的XMLNode類會将資源生成一個ResXMLTree對象,并将其序列化。最終會生成二進制的xml檔案,實作對xml的壓縮。打包過程中,xml等資源檔案存在resource.ap_檔案中。

2.運作時解析

為了提高解析效率,運作時的inflate會嚴重依賴編譯期aapt對xml的預處理。底層首先用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中。

源碼分析

inflate方法

首先看第一個核心的方法inflate

關注點:

1. attachToRoot參數

這個大家一般都會關注,false的時候,xml的rootView,其屬性會被保留。見下面代碼片段:

...
temp.setLayoutParams(params);
...      

2.rInflate遞歸方法

1.遞歸建立View,綁定View的屬性

2.綁定ViewGroup的LayoutParams,并将View添加到ViewGroup中。

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context)mConstructorArgs[0];
        mConstructorArgs[0] = mContext;
        View result = root;
        try {
            // Look for the root node.
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }
            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }
            final String name = parser.getName();

            if (DEBUG) {
                System.out.println("**************************");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("**************************");
            }
            if (TAG_MERGE.equals(name)) {
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }
                rInflate(parser, root, attrs, false, false);
            } else {
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, attrs, false);
                ViewGroup.LayoutParams params = null;
                if (root != null) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);
                    }
                }
                if (DEBUG) {
                    System.out.println("-----> start inflating children");
                }
                // Inflate all children under temp
                rInflate(parser, temp, attrs, true, true);
                if (DEBUG) {
                    System.out.println("-----> done inflating children");
                }
                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }
        } catch (XmlPullParserException e) {
            InflateException ex = new InflateException(e.getMessage());
            ex.initCause(e);
            throw ex;
        } catch (IOException e) {
            InflateException ex = new InflateException(
                    parser.getPositionDescription()
                    + ": " + e.getMessage());
            ex.initCause(e);
            throw ex;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;
        }
        return result;
    }
}      

另外一個我們需要注意的是,AttributeSet是怎麼通過XML.asAttributeSet方法,從parse中抽取出來的?

XML.asAttributeSet

其實,就生成了XmlPullAttributes對象,它持有了XmlPullParser對象,并實作了AttributeSet接口。

class XmlPullAttributes implements AttributeSet {
public XmlPullAttributes(XmlPullParser parser) {
    mParser = parser;
}

public int getAttributeCount() {
    return mParser.getAttributeCount();
}

public String getAttributeName(int index) {
    return mParser.getAttributeName(index);
}

public String getAttributeValue(int index) {
    return mParser.getAttributeValue(index);
}

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

public String getPositionDescription() {
    return mParser.getPositionDescription();
}

public int getAttributeNameResource(int index) {
    return 0;
}

public int getAttributeListValue(String namespace, String attribute,
        String[] options, int defaultValue) {
    return XmlUtils.convertValueToList(
        getAttributeValue(namespace, attribute), options, defaultValue);
}

public boolean getAttributeBooleanValue(String namespace, String attribute,
        boolean defaultValue) {
    return XmlUtils.convertValueToBoolean(
        getAttributeValue(namespace, attribute), defaultValue);
}

public int getAttributeResourceValue(String namespace, String attribute,
        int defaultValue) {
    return XmlUtils.convertValueToInt(
        getAttributeValue(namespace, attribute), defaultValue);
}

public int getAttributeIntValue(String namespace, String attribute,
        int defaultValue) {
    return XmlUtils.convertValueToInt(
        getAttributeValue(namespace, attribute), defaultValue);
}

public int getAttributeUnsignedIntValue(String namespace, String attribute,
        int defaultValue) {
    return XmlUtils.convertValueToUnsignedInt(
        getAttributeValue(namespace, attribute), defaultValue);
}

public float getAttributeFloatValue(String namespace, String attribute,
        float defaultValue) {
    String s = getAttributeValue(namespace, attribute);
    if (s != null) {
        return Float.parseFloat(s);
    }
    return defaultValue;
}

public int getAttributeListValue(int index,
        String[] options, int defaultValue) {
    return XmlUtils.convertValueToList(
        getAttributeValue(index), options, defaultValue);
}

public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
    return XmlUtils.convertValueToBoolean(
        getAttributeValue(index), defaultValue);
}

public int getAttributeResourceValue(int index, int defaultValue) {
    return XmlUtils.convertValueToInt(
        getAttributeValue(index), defaultValue);
}

public int getAttributeIntValue(int index, int defaultValue) {
    return XmlUtils.convertValueToInt(
        getAttributeValue(index), defaultValue);
}

public int getAttributeUnsignedIntValue(int index, int defaultValue) {
    return XmlUtils.convertValueToUnsignedInt(
        getAttributeValue(index), defaultValue);
}

public float getAttributeFloatValue(int index, float defaultValue) {
    String s = getAttributeValue(index);
    if (s != null) {
        return Float.parseFloat(s);
    }
    return defaultValue;
}

public String getIdAttribute() {
    return getAttributeValue(null, "id");
}

public String getClassAttribute() {
    return getAttributeValue(null, "class");
}

public int getIdAttributeResourceValue(int defaultValue) {
    return getAttributeResourceValue(null, "id", defaultValue);
}

public int getStyleAttribute() {
    return getAttributeResourceValue(null, "style", 0);
}

/*package*/ XmlPullParser mParser;
}      

部分需要轉換的值,比如需要将String轉成int的方法,使用了com.android.internal.util.XmlUtils這個類做了value的解析。但是String類型的value,仍然是直接傳回的。

rInflate遞歸方法建立View樹

循環對标簽進行解析,需要特殊處理的标題主要有,include、merge、tag、requestFocus,除此之外,都是一些View和ViewGroup的标簽。主要看else裡面的實作:最核心的問題是:

1.View的建立和屬性的綁定createViewFromTag方法

2.ViewGroup的LayoutParams屬性綁定,以及ViewGroup和View的父子關系的生成addView。

void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
        boolean finishInflate, boolean inheritContext) throws XmlPullParserException,
        IOException {
    final int depth = parser.getDepth();
    int type;
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
        if (type != XmlPullParser.START_TAG) {
            continue;
        }
        final String name = parser.getName();

        if (TAG_REQUEST_FOCUS.equals(name)) {
            parseRequestFocus(parser, parent);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, parent, attrs, inheritContext);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            final View view = createViewFromTag(parent, name, attrs, inheritContext);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflate(parser, view, attrs, true, true);
            viewGroup.addView(view, params);
        }
    }
    if (finishInflate) parent.onFinishInflate();
}      

createViewFromTag 建立View

首先看View類的拼接

1.系統自帶View的包名拼接

View的name如果不包含點“.”,說明是系統的View,比如TextView,ImageView等,會調用onCreateView,對包名進行拼接

protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}      

2.自定義View直接調用createView方法

View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }
    Context viewContext;
    if (parent != null && inheritContext) {
        viewContext = parent.getContext();
    } else {
        viewContext = mContext;
    }
    // Apply a theme wrapper, if requested.
    final TypedArray ta = viewContext.obtainStyledAttributes(attrs, ATTRS_THEME);
    final int themeResId = ta.getResourceId(0, 0);
    if (themeResId != 0) {
        viewContext = new ContextThemeWrapper(viewContext, themeResId);
    }
    ta.recycle();
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(viewContext, attrs);
    }
    if (DEBUG) System.out.println("******** Creating view: " + name);
    try {
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, viewContext, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, viewContext, attrs);
        } else {
            view = null;
        }
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs);
        }
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = viewContext;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        if (DEBUG) System.out.println("Created view is: " + view);
        return view;
    } catch (InflateException e) {
        throw e;
    } catch (ClassNotFoundException e) {
        InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name);
        ie.initCause(e);
        throw ie;
    } catch (Exception e) {
        InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name);
        ie.initCause(e);
        throw ie;
    }
 }      

createView

通過反射的方式,生成View

1.首先從緩存的Map中查構造方法,如果沒有,反射

2.獲得目前的ClassLoader,然後查找參數為mConstructorSignature的構造方法,然後将構造方法緩存

3.使用constructor方法,反射生成View

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    Class<? extends View> clazz = null;
    try {
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);

            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);
            sConstructorMap.put(name, constructor);
        } else {
            // If we have a filter, apply it to cached constructor
            if (mFilter != null) {
                // Have we seen this name before?
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    // New class -- remember whether it is allowed
                    clazz = mContext.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);

                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
        }
        Object[] args = mConstructorArgs;
        args[1] = attrs;
        constructor.setAccessible(true);
        final View view = constructor.newInstance(args);
        if (view instanceof ViewStub) {
            // always use ourselves when inflating ViewStub later
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(this);
        }
        return view;
    } catch (NoSuchMethodException e) {
        InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class "
                + (prefix != null ? (prefix + name) : name));
        ie.initCause(e);
        throw ie;
    } catch (ClassCastException e) {
        // If loaded class is not a View subclass
        InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Class is not a View "
                + (prefix != null ? (prefix + name) : name));
        ie.initCause(e);
        throw ie;
    } catch (ClassNotFoundException e) {
        // If loadClass fails, we should propagate the exception.
        throw e;
    } catch (Exception e) {
        InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class "
                + (clazz == null ? "<unknown>" : clazz.getName()));
        ie.initCause(e);
        throw ie;
    } finally {
    }
}      

以上用的構造方法是兩個參數的構造方法:

final Object[] mConstructorArgs = new Object[2];

static final Class<?>[] mConstructorSignature = new Class[] {
        Context.class, AttributeSet.class};      

也就是說,建立View的過程,也就是通過反射的形式,調用參數為context和attributeSet這兩個參數的構造方法。而View的屬性解析和綁定,都是在View的構造方法中完成的。如下為View構造方法的代碼片段:

View的構造方法

...

  final TypedArray a = context.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);

  ...
  final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;

    final int N = a.getIndexCount();
    for (int i = 0; i < N; i++) {
        int attr = a.getIndex(i);
        switch (attr) {
            case com.android.internal.R.styleable.View_background:
                background = a.getDrawable(attr);
                break;
            case com.android.internal.R.styleable.View_padding:
                padding = a.getDimensionPixelSize(attr, -1);
                mUserPaddingLeftInitial = padding;
                mUserPaddingRightInitial = padding;
                leftPaddingDefined = true;
                rightPaddingDefined = true;
                break;
             case com.android.internal.R.styleable.View_paddingLeft:
                leftPadding = a.getDimensionPixelSize(attr, -1);
                mUserPaddingLeftInitial = leftPadding;
                leftPaddingDefined = true;
                break;
            case com.android.internal.R.styleable.View_paddingTop:
                topPadding = a.getDimensionPixelSize(attr, -1);
                break;
            case com.android.internal.R.styleable.View_paddingRight:
                rightPadding = a.getDimensionPixelSize(attr, -1);
                mUserPaddingRightInitial = rightPadding;
                rightPaddingDefined = true;
                break;
            case com.android.internal.R.styleable.View_paddingBottom:
                bottomPadding = a.getDimensionPixelSize(attr, -1);
                break;
            case com.android.internal.R.styleable.View_paddingStart:
                startPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
                startPaddingDefined = (startPadding != UNDEFINED_PADDING);
                break;
            case com.android.internal.R.styleable.View_paddingEnd:
                endPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
                endPaddingDefined = (endPadding != UNDEFINED_PADDING);
                break;
            case com.android.internal.R.styleable.View_scrollX:
                x = a.getDimensionPixelOffset(attr, 0);
                break;
            case com.android.internal.R.styleable.View_scrollY:
                y = a.getDimensionPixelOffset(attr, 0);
                break;
            case com.android.internal.R.styleable.View_alpha:
                setAlpha(a.getFloat(attr, 1f));
                break;
            case com.android.internal.R.styleable.View_transformPivotX:
                setPivotX(a.getDimensionPixelOffset(attr, 0));
                break;
            case com.android.internal.R.styleable.View_transformPivotY:
                setPivotY(a.getDimensionPixelOffset(attr, 0));
                break;
            case com.android.internal.R.styleable.View_translationX:
                tx = a.getDimensionPixelOffset(attr, 0);
                transformSet = true;
                break;
            case com.android.internal.R.styleable.View_translationY:
                ty = a.getDimensionPixelOffset(attr, 0);
                transformSet = true;
                break;
            case com.android.internal.R.styleable.View_translationZ:
                tz = a.getDimensionPixelOffset(attr, 0);
                transformSet = true;
                break;
            case com.android.internal.R.styleable.View_elevation:
                elevation = a.getDimensionPixelOffset(attr, 0);
                transformSet = true;
                break;
            case com.android.internal.R.styleable.View_rotation:
                rotation = a.getFloat(attr, 0);
                transformSet = true;
                break;
            case com.android.internal.R.styleable.View_rotationX:
                rotationX = a.getFloat(attr, 0);
                transformSet = true;
                break;
            case com.android.internal.R.styleable.View_rotationY:
                rotationY = a.getFloat(attr, 0);
                transformSet = true;
                break;
            case com.android.internal.R.styleable.View_scaleX:
                sx = a.getFloat(attr, 1f);
                transformSet = true;
                break;
            case com.android.internal.R.styleable.View_scaleY:
                sy = a.getFloat(attr, 1f);
                transformSet = true;
                break;
            case com.android.internal.R.styleable.View_id:
                mID = a.getResourceId(attr, NO_ID);
                break;
            case com.android.internal.R.styleable.View_tag:
                mTag = a.getText(attr);
                break;
            case com.android.internal.R.styleable.View_fitsSystemWindows:
                if (a.getBoolean(attr, false)) {
                    viewFlagValues |= FITS_SYSTEM_WINDOWS;
                    viewFlagMasks |= FITS_SYSTEM_WINDOWS;
                }
                break;
            case com.android.internal.R.styleable.View_focusable:
                if (a.getBoolean(attr, false)) {
                    viewFlagValues |= FOCUSABLE;
                    viewFlagMasks |= FOCUSABLE_MASK;
                }
                break;
            case com.android.internal.R.styleable.View_focusableInTouchMode:
                if (a.getBoolean(attr, false)) {
                    viewFlagValues |= FOCUSABLE_IN_TOUCH_MODE | FOCUSABLE;
                    viewFlagMasks |= FOCUSABLE_IN_TOUCH_MODE | FOCUSABLE_MASK;
                }
                break;
            case com.android.internal.R.styleable.View_clickable:
                if (a.getBoolean(attr, false)) {
                    viewFlagValues |= CLICKABLE;
                    viewFlagMasks |= CLICKABLE;
                }
                break;
            case com.android.internal.R.styleable.View_longClickable:
                if (a.getBoolean(attr, false)) {
                    viewFlagValues |= LONG_CLICKABLE;
                    viewFlagMasks |= LONG_CLICKABLE;
                }
                break;
            case com.android.internal.R.styleable.View_contextClickable:
                if (a.getBoolean(attr, false)) {
                    viewFlagValues |= CONTEXT_CLICKABLE;
                    viewFlagMasks |= CONTEXT_CLICKABLE;
                }
                break;
            case com.android.internal.R.styleable.View_saveEnabled:
                if (!a.getBoolean(attr, true)) {
                    viewFlagValues |= SAVE_DISABLED;
                    viewFlagMasks |= SAVE_DISABLED_MASK;
                }
                break;
            case com.android.internal.R.styleable.View_duplicateParentState:
                if (a.getBoolean(attr, false)) {
                    viewFlagValues |= DUPLICATE_PARENT_STATE;
                    viewFlagMasks |= DUPLICATE_PARENT_STATE;
                }
                break;
            case com.android.internal.R.styleable.View_visibility:
                final int visibility = a.getInt(attr, 0);
                if (visibility != 0) {
                    viewFlagValues |= VISIBILITY_FLAGS[visibility];
                    viewFlagMasks |= VISIBILITY_MASK;
                }
                break;
            case com.android.internal.R.styleable.View_layoutDirection:
                // Clear any layout direction flags (included resolved bits) already set
                mPrivateFlags2 &=
                        ~(PFLAG2_LAYOUT_DIRECTION_MASK | PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK);
                // Set the layout direction flags depending on the value of the attribute
                final int layoutDirection = a.getInt(attr, -1);
                final int value = (layoutDirection != -1) ?
                        LAYOUT_DIRECTION_FLAGS[layoutDirection] : LAYOUT_DIRECTION_DEFAULT;
                mPrivateFlags2 |= (value << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT);
                break;
            case com.android.internal.R.styleable.View_drawingCacheQuality:
                final int cacheQuality = a.getInt(attr, 0);
                if (cacheQuality != 0) {
                    viewFlagValues |= DRAWING_CACHE_QUALITY_FLAGS[cacheQuality];
                    viewFlagMasks |= DRAWING_CACHE_QUALITY_MASK;
                }
                break;
            case com.android.internal.R.styleable.View_contentDescription:
                setContentDescription(a.getString(attr));
                break;
            case com.android.internal.R.styleable.View_accessibilityTraversalBefore:
                setAccessibilityTraversalBefore(a.getResourceId(attr, NO_ID));
                break;
            case com.android.internal.R.styleable.View_accessibilityTraversalAfter:
                setAccessibilityTraversalAfter(a.getResourceId(attr, NO_ID));
                break;
            case com.android.internal.R.styleable.View_labelFor:
                setLabelFor(a.getResourceId(attr, NO_ID));
                break;
            case com.android.internal.R.styleable.View_soundEffectsEnabled:
                if (!a.getBoolean(attr, true)) {
                    viewFlagValues &= ~SOUND_EFFECTS_ENABLED;
                    viewFlagMasks |= SOUND_EFFECTS_ENABLED;
                }
                break;
            case com.android.internal.R.styleable.View_hapticFeedbackEnabled:
                if (!a.getBoolean(attr, true)) {
                    viewFlagValues &= ~HAPTIC_FEEDBACK_ENABLED;
                    viewFlagMasks |= HAPTIC_FEEDBACK_ENABLED;
                }
                break;
            case R.styleable.View_scrollbars:
                final int scrollbars = a.getInt(attr, SCROLLBARS_NONE);
                if (scrollbars != SCROLLBARS_NONE) {
                    viewFlagValues |= scrollbars;
                    viewFlagMasks |= SCROLLBARS_MASK;
                    initializeScrollbars = true;
                }
                break;
            //noinspection deprecation
            case R.styleable.View_fadingEdge:
                if (targetSdkVersion >= ICE_CREAM_SANDWICH) {
                    // Ignore the attribute starting with ICS
                    break;
                }
                // With builds < ICS, fall through and apply fading edges
            case R.styleable.View_requiresFadingEdge:
                final int fadingEdge = a.getInt(attr, FADING_EDGE_NONE);
                if (fadingEdge != FADING_EDGE_NONE) {
                    viewFlagValues |= fadingEdge;
                    viewFlagMasks |= FADING_EDGE_MASK;
                    initializeFadingEdgeInternal(a);
                }
                break;
            case R.styleable.View_scrollbarStyle:
                scrollbarStyle = a.getInt(attr, SCROLLBARS_INSIDE_OVERLAY);
                if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
                    viewFlagValues |= scrollbarStyle & SCROLLBARS_STYLE_MASK;
                    viewFlagMasks |= SCROLLBARS_STYLE_MASK;
                }
                break;
            case R.styleable.View_isScrollContainer:
                setScrollContainer = true;
                if (a.getBoolean(attr, false)) {
                    setScrollContainer(true);
                }
                break;
            case com.android.internal.R.styleable.View_keepScreenOn:
                if (a.getBoolean(attr, false)) {
                    viewFlagValues |= KEEP_SCREEN_ON;
                    viewFlagMasks |= KEEP_SCREEN_ON;
                }
                break;
            case R.styleable.View_filterTouchesWhenObscured:
                if (a.getBoolean(attr, false)) {
                    viewFlagValues |= FILTER_TOUCHES_WHEN_OBSCURED;
                    viewFlagMasks |= FILTER_TOUCHES_WHEN_OBSCURED;
                }
                break;
            case R.styleable.View_nextFocusLeft:
                mNextFocusLeftId = a.getResourceId(attr, View.NO_ID);
                break;
            case R.styleable.View_nextFocusRight:
                mNextFocusRightId = a.getResourceId(attr, View.NO_ID);
                break;
            case R.styleable.View_nextFocusUp:
                mNextFocusUpId = a.getResourceId(attr, View.NO_ID);
                break;
            case R.styleable.View_nextFocusDown:
                mNextFocusDownId = a.getResourceId(attr, View.NO_ID);
                break;
            case R.styleable.View_nextFocusForward:
                mNextFocusForwardId = a.getResourceId(attr, View.NO_ID);
                break;
            case R.styleable.View_minWidth:
                mMinWidth = a.getDimensionPixelSize(attr, 0);
                break;
            case R.styleable.View_minHeight:
                mMinHeight = a.getDimensionPixelSize(attr, 0);
                break;
            case R.styleable.View_onClick:
                if (context.isRestricted()) {
                    throw new IllegalStateException("The android:onClick attribute cannot "
                            + "be used within a restricted context");
                }

                final String handlerName = a.getString(attr);
                if (handlerName != null) {
                    setOnClickListener(new DeclaredOnClickListener(this, handlerName));
                }
                break;
            case R.styleable.View_overScrollMode:
                overScrollMode = a.getInt(attr, OVER_SCROLL_IF_CONTENT_SCROLLS);
                break;
            case R.styleable.View_verticalScrollbarPosition:
                mVerticalScrollbarPosition = a.getInt(attr, SCROLLBAR_POSITION_DEFAULT);
                break;
            case R.styleable.View_layerType:
                setLayerType(a.getInt(attr, LAYER_TYPE_NONE), null);
                break;
            case R.styleable.View_textDirection:
                // Clear any text direction flag already set
                mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK;
                // Set the text direction flags depending on the value of the attribute
                final int textDirection = a.getInt(attr, -1);
                if (textDirection != -1) {
                    mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_FLAGS[textDirection];
                }
                break;
            case R.styleable.View_textAlignment:
                // Clear any text alignment flag already set
                mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK;
                // Set the text alignment flag depending on the value of the attribute
                final int textAlignment = a.getInt(attr, TEXT_ALIGNMENT_DEFAULT);
                mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_FLAGS[textAlignment];
                break;
            case R.styleable.View_importantForAccessibility:
                setImportantForAccessibility(a.getInt(attr,
                        IMPORTANT_FOR_ACCESSIBILITY_DEFAULT));
                break;
            case R.styleable.View_accessibilityLiveRegion:
                setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT));
                break;
            case R.styleable.View_transitionName:
                setTransitionName(a.getString(attr));
                break;
            case R.styleable.View_nestedScrollingEnabled:
                setNestedScrollingEnabled(a.getBoolean(attr, false));
                break;
            case R.styleable.View_stateListAnimator:
                setStateListAnimator(AnimatorInflater.loadStateListAnimator(context,
                        a.getResourceId(attr, 0)));
                break;
            case R.styleable.View_backgroundTint:
                // This will get applied later during setBackground().
                if (mBackgroundTint == null) {
                    mBackgroundTint = new TintInfo();
                }
                mBackgroundTint.mTintList = a.getColorStateList(
                        R.styleable.View_backgroundTint);
                mBackgroundTint.mHasTintList = true;
                break;
            case R.styleable.View_backgroundTintMode:
                // This will get applied later during setBackground().
                if (mBackgroundTint == null) {
                    mBackgroundTint = new TintInfo();
                }
                mBackgroundTint.mTintMode = Drawable.parseTintMode(a.getInt(
                        R.styleable.View_backgroundTintMode, -1), null);
                mBackgroundTint.mHasTintMode = true;
                break;
            case R.styleable.View_outlineProvider:
                setOutlineProviderFromAttribute(a.getInt(R.styleable.View_outlineProvider,
                        PROVIDER_BACKGROUND));
                break;
            case R.styleable.View_foreground:
                if (targetSdkVersion >= VERSION_CODES.M || this instanceof FrameLayout) {
                    setForeground(a.getDrawable(attr));
                }
                break;
            case R.styleable.View_foregroundGravity:
                if (targetSdkVersion >= VERSION_CODES.M || this instanceof FrameLayout) {
                    setForegroundGravity(a.getInt(attr, Gravity.NO_GRAVITY));
                }
                break;
            case R.styleable.View_foregroundTintMode:
                if (targetSdkVersion >= VERSION_CODES.M || this instanceof FrameLayout) {
                    setForegroundTintMode(Drawable.parseTintMode(a.getInt(attr, -1), null));
                }
                break;
            case R.styleable.View_foregroundTint:
                if (targetSdkVersion >= VERSION_CODES.M || this instanceof FrameLayout) {
                    setForegroundTintList(a.getColorStateList(attr));
                }
                break;
            case R.styleable.View_foregroundInsidePadding:
                if (targetSdkVersion >= VERSION_CODES.M || this instanceof FrameLayout) {
                    if (mForegroundInfo == null) {
                        mForegroundInfo = new ForegroundInfo();
                    }
                    mForegroundInfo.mInsidePadding = a.getBoolean(attr,
                            mForegroundInfo.mInsidePadding);
                }
                break;
            case R.styleable.View_scrollIndicators:
                final int scrollIndicators =
                        (a.getInt(attr, 0) << SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT)
                                & SCROLL_INDICATORS_PFLAG3_MASK;
                if (scrollIndicators != 0) {
                    mPrivateFlags3 |= scrollIndicators;
                    initializeScrollIndicators = true;
                }
                break;
        }
    }
 ...      

對View的通用屬性進行解析。如果是TextView或ImageView的特有屬性,這些屬性的解析在對應的構造方法中,以TextView為例:

TextView的構造方法片段

...
   TypedArray appearance = null;
    int ap = a.getResourceId(
            com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
    a.recycle();
    if (ap != -1) {
        appearance = theme.obtainStyledAttributes(
                ap, com.android.internal.R.styleable.TextAppearance);
    }
    if (appearance != null) {
        int n = appearance.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = appearance.getIndex(i);

            switch (attr) {
            case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
                textColorHighlight = appearance.getColor(attr, textColorHighlight);
                break;

            case com.android.internal.R.styleable.TextAppearance_textColor:
                textColor = appearance.getColorStateList(attr);
                break;

            case com.android.internal.R.styleable.TextAppearance_textColorHint:
                textColorHint = appearance.getColorStateList(attr);
                break;

            case com.android.internal.R.styleable.TextAppearance_textColorLink:
                textColorLink = appearance.getColorStateList(attr);
                break;

            case com.android.internal.R.styleable.TextAppearance_textSize:
                textSize = appearance.getDimensionPixelSize(attr, textSize);
                break;

            case com.android.internal.R.styleable.TextAppearance_typeface:
                typefaceIndex = appearance.getInt(attr, -1);
                break;

            case com.android.internal.R.styleable.TextAppearance_fontFamily:
                fontFamily = appearance.getString(attr);
                break;

            case com.android.internal.R.styleable.TextAppearance_textStyle:
                styleIndex = appearance.getInt(attr, -1);
                break;

            case com.android.internal.R.styleable.TextAppearance_textAllCaps:
                allCaps = appearance.getBoolean(attr, false);
                break;

            case com.android.internal.R.styleable.TextAppearance_shadowColor:
                shadowcolor = appearance.getInt(attr, 0);
                break;

            case com.android.internal.R.styleable.TextAppearance_shadowDx:
                dx = appearance.getFloat(attr, 0);
                break;

            case com.android.internal.R.styleable.TextAppearance_shadowDy:
                dy = appearance.getFloat(attr, 0);
                break;

            case com.android.internal.R.styleable.TextAppearance_shadowRadius:
                r = appearance.getFloat(attr, 0);
                break;

            case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
                elegant = appearance.getBoolean(attr, false);
                break;

            case com.android.internal.R.styleable.TextAppearance_letterSpacing:
                letterSpacing = appearance.getFloat(attr, 0);
                break;

            case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
                fontFeatureSettings = appearance.getString(attr);
                break;
            }
        }
 ...      

ImageView等,在其構造方法中,也有其屬性的綁定,在此不再列舉。至此,View的建立和View的屬性都已經綁定完成,那布局呢?

ViewGroup的addView問題

生成LayoutParams對象

public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new LayoutParams(getContext(), attrs);
}      

再看一下ViewGroup.LayoutParams的構造方法

public LayoutParams(Context c, AttributeSet attrs) {
        TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
        setBaseAttributes(a,
                R.styleable.ViewGroup_Layout_layout_width,
                R.styleable.ViewGroup_Layout_layout_height);
        a.recycle();
}      

這裡面隻有最基本的寬高屬性。

setBaseAttributes方法隻做了寬高設定:

protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
        width = a.getLayoutDimension(widthAttr, "layout_width");
        height = a.getLayoutDimension(heightAttr, "layout_height");
 }      

那其他的一些屬性呢?我們看一下LinearLayout、RelativeLayout等布局的LayoutParams。它們的LayoutParams都繼承自MarginLayoutParams,解決外邊距的屬性解析。

我們看一下MarginLayoutParams的構造方法看一下:MarginLayoutParams繼承自LayoutParams

public MarginLayoutParams(Context c, AttributeSet attrs) {
        super();

        TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
        setBaseAttributes(a,
                R.styleable.ViewGroup_MarginLayout_layout_width,
                R.styleable.ViewGroup_MarginLayout_layout_height);

        int margin = a.getDimensionPixelSize(
                com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
        if (margin >= 0) {
            leftMargin = margin;
            topMargin = margin;
            rightMargin= margin;
            bottomMargin = margin;
        } else {
            leftMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
                    UNDEFINED_MARGIN);
            if (leftMargin == UNDEFINED_MARGIN) {
                mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
                leftMargin = DEFAULT_MARGIN_RESOLVED;
            }
            rightMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginRight,
                    UNDEFINED_MARGIN);
            if (rightMargin == UNDEFINED_MARGIN) {
                mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
                rightMargin = DEFAULT_MARGIN_RESOLVED;
            }

            topMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginTop,
                    DEFAULT_MARGIN_RESOLVED);
            bottomMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
                    DEFAULT_MARGIN_RESOLVED);

            startMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginStart,
                    DEFAULT_MARGIN_RELATIVE);
            endMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
                    DEFAULT_MARGIN_RELATIVE);

            if (isMarginRelative()) {
               mMarginFlags |= NEED_RESOLUTION_MASK;
            }
        }

        final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
        final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
        if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
            mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
        }

        // Layout direction is LTR by default
        mMarginFlags |= LAYOUT_DIRECTION_LTR;

        a.recycle();
    }      

而LinearLayout線性布局,主要是有兩個私有的自己的屬性:

public LayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);
        TypedArray a =
                c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);

        weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
        gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);

        a.recycle();
    }      

相對布局RelativeLayout的屬性要相對多很多:

public LayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);

        TypedArray a = c.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.RelativeLayout_Layout);

        final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
        mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
                !c.getApplicationInfo().hasRtlSupport());

        final int[] rules = mRules;
        //noinspection MismatchedReadAndWriteOfArray
        final int[] initialRules = mInitialRules;

        final int N = a.getIndexCount();
        for (int i = 0; i < N; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
                    alignWithParent = a.getBoolean(attr, false);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
                    rules[LEFT_OF] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
                    rules[RIGHT_OF] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
                    rules[ABOVE] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:
                    rules[BELOW] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:
                    rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:
                    rules[ALIGN_LEFT] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop:
                    rules[ALIGN_TOP] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight:
                    rules[ALIGN_RIGHT] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom:
                    rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:
                    rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop:
                    rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight:
                    rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom:
                    rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:
                    rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal:
                    rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:
                    rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
                   break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf:
                    rules[START_OF] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf:
                    rules[END_OF] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart:
                    rules[ALIGN_START] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd:
                    rules[ALIGN_END] = a.getResourceId(attr, 0);
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart:
                    rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd:
                    rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
            }
        }
        mRulesChanged = true;
        System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT);

        a.recycle();
    }      

TypedArray從何而來?

我們關心的另外一個問題就是,如何根據AttributeSet獲得TypedArray的,也即是下面代碼的是怎麼執行的

TypedArray a = c.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.RelativeLayout_Layout);      

順着context的方法一路找過去,最終在Resources類的内部類Theme類中查找到的方法

public TypedArray obtainStyledAttributes(AttributeSet set,
            @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        synchronized (mKey) {
            final int len = attrs.length;
            final TypedArray array = TypedArray.obtain(Resources.this, len);

            // XXX note that for now we only work with compiled XML files.
            // To support generic XML files we will need to manually parse
            // out the attributes from the XML file (applying type information
            // contained in the resources and such).
            final XmlBlock.Parser parser = (XmlBlock.Parser) set;
            AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
                    parser != null ? parser.mParseState : 0,
                    attrs, array.mData, array.mIndices);
            array.mTheme = this;
            array.mXml = parser;

            return array;
        }
    }      

XmlBlock

上述方法中,引入了一個重要的類,XmlBlock.Parse。其中XmlBlock這個類對屬性的值的解析,很多都是調用的C層的代碼,比如:

...
    public int getAttributeNameResource(int index) {
        return nativeGetAttributeResource(mParseState, index);
    }

    public int getAttributeListValue(String namespace, String attribute,
            String[] options, int defaultValue) {
        int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
        if (idx >= 0) {
            return getAttributeListValue(idx, options, defaultValue);
        }
        return defaultValue;
    }
    public boolean getAttributeBooleanValue(String namespace, String attribute,
            boolean defaultValue) {
        int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
        if (idx >= 0) {
            return getAttributeBooleanValue(idx, defaultValue);
        }
        return defaultValue;
    }
    public int getAttributeResourceValue(String namespace, String attribute,
            int defaultValue) {
        int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
        if (idx >= 0) {
            return getAttributeResourceValue(idx, defaultValue);
        }
        return defaultValue;
    }
    public int getAttributeIntValue(String namespace, String attribute,
            int defaultValue) {
        int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
        if (idx >= 0) {
            return getAttributeIntValue(idx, defaultValue);
        }
        return defaultValue;
    }
    public int getAttributeUnsignedIntValue(String namespace, String attribute,
                                            int defaultValue)
    {
        int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
        if (idx >= 0) {
            return getAttributeUnsignedIntValue(idx, defaultValue);
        }
        return defaultValue;
    }
    public float getAttributeFloatValue(String namespace, String attribute,
            float defaultValue) {
        int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
        if (idx >= 0) {
            return getAttributeFloatValue(idx, defaultValue);
        }
        return defaultValue;
    }
    ...      

離線解析XML問題