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左右。
编译期,aapt工具会对xml布局文件做预处理(pre-processing),查看aapt源码,可以知道,其中的XMLNode类会将资源生成一个ResXMLTree对象,并将其序列化。最终会生成二进制的xml文件,实现对xml的压缩。打包过程中,xml等资源文件存在resource.ap_文件中。
为了提高解析效率,运行时的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
关注点:
这个大家一般都会关注,false的时候,xml的rootView,其属性会被保留。见下面代码片段:
...
temp.setLayoutParams(params);
...
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中抽取出来的?
其实,就生成了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,仍然是直接返回的。
循环对标签进行解析,需要特殊处理的标题主要有,include、merge、tag、requestFocus,除此之外,都是一些View和ViewGroup的标签。主要看else里面的实现:最核心的问题是:
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();
}
首先看View类的拼接
View的name如果不包含点“.”,说明是系统的View,比如TextView,ImageView等,会调用onCreateView,对包名进行拼接
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
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;
}
}
通过反射的方式,生成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 ? "" : 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构造方法的代码片段:
...
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为例:
...
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的属性都已经绑定完成,那布局呢?
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();
}
我们关心的另外一个问题就是,如何根据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.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的整个过程就比较清晰了。
由插件化框架的原理可知,我们可以创建一个AssetsManager对象,然后反射调用其addAssetsPath方法,将xml所在的apk路径添加其中。然后使用AssetsManager对象生成Resources对象。在使用LayoutInflater时,替换其使用的Context的getResources方法即可。再次,不在赘述。