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(" 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(" cannot be the root element");
            }
            parseInclude(parser, parent, attrs, inheritContext);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException(" 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 constructor = sConstructorMap.get(name);
    Class 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 ? "" : 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;
    }
    ...


获取属性时,很多都是调用的native方法,效率会高不少。最终解析xml中属性值的核心代码,就是在XmlBlock的Parse类中调用的,至此,LayoutInflater的整个过程就比较清晰了。

离线解析XML问题

由插件化框架的原理可知,我们可以创建一个AssetsManager对象,然后反射调用其addAssetsPath方法,将xml所在的apk路径添加其中。然后使用AssetsManager对象生成Resources对象。在使用LayoutInflater时,替换其使用的Context的getResources方法即可。再次,不在赘述。

你可能感兴趣的:(【android】)