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;
}
...