android生成xml布局代码工具,Android加载xml布局源码分析

通常,我们在Activity的onCreate调用setContentView(R.layout.activity_main);来初始化界面,今天我们一起来看看这句话背后发生的事:

@Override

public void setContentView(int resId) {

ensureSubDecor();

ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);

contentParent.removeAllViews();

LayoutInflater.from(mContext).inflate(resId, contentParent);

mOriginalWindowCallback.onContentChanged();

}

这里Activity是继承自AppCompatActivity,所以与继承自Activity有点不一样。但是流程差不多,也是需要获取android.R.id.content然后加载布局。

先看下如何获取LayoutInflater对象:

/**

* Obtains the LayoutInflater from the given context.

*/

public static LayoutInflater from(Context context) {

LayoutInflater LayoutInflater =

(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

if (LayoutInflater == null) {

throw new AssertionError("LayoutInflater not found.");

}

return LayoutInflater; // 这命名方式。。忽略

}

可以看到,LayoutInflater.from(mContext)其实也是通过context.getSystemService方式获取到的,这种方式更简洁一些,所以我们需要使用LayoutInflater时就用LayoutInflater.from(mContext)。

接着看inflate方法

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {

return inflate(resource, root, root != null);

}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {

final Resources res = getContext().getResources();

if (DEBUG) {

Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("

+ Integer.toHexString(resource) + ")");

}

final XmlResourceParser parser = res.getLayout(resource);

try {

return inflate(parser, root, attachToRoot);

} finally {

parser.close();

}

}

先将layoutID转换为XmlResourceParser对象,parser里面已经包含layout里面的内容,然后继续往下调inflate,这里暂不展开parser的获取。由于inflate方法体较长,我把主要的部分拿出来分析一下:

final AttributeSet attrs = Xml.asAttributeSet(parser);

View result = root;

···

final String name = parser.getName();

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, inflaterContext, attrs, false);

} else {

// Temp is the root view that was found in the xml

final View temp = createViewFromTag(root, name, inflaterContext, attrs);

···

// Inflate all children under temp against its context.

rInflate(parser, temp, inflaterContext, attrs, true);

// 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;

}

}

return result;

先从parser里获取属性存储到attrs,并将结果result指向root,此时的root可能为空。获取根节点的名字,若根节点是merge,则需要判断root是否为空,因为merge必须要要添加到一个容器里面,rInflate(parser, root, inflaterContext, attrs, false);将parser里的内容加入到root里之后,直接就返回了,因为上面已经把result指向root了; 若根节点不是merger,则需要通过name创建一个view,并将result指向该view。这里仅仅是处理了root节点,内部的节点遍历需要继续往下看rInflate。

private static final String TAG_MERGE = "merge";

private static final String TAG_INCLUDE = "include";

private static final String TAG_1995 = "blink";

private static final String TAG_REQUEST_FOCUS = "requestFocus";

private static final String TAG_TAG = "tag";

void rInflate(XmlPullParser parser, View parent, Context context,

AttributeSet attrs, boolean finishInflate) 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, context, parent, attrs);

} else if (TAG_MERGE.equals(name)) {

throw new InflateException(" must be the root element");

} else {

final View view = createViewFromTag(parent, name, context, attrs);

final ViewGroup viewGroup = (ViewGroup) parent;

final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);

rInflateChildren(parser, view, attrs, true);

viewGroup.addView(view, params);

}

}

if (finishInflate) {

parent.onFinishInflate();

}

}

while循环遍历parser,然后根据节点的name进行处理,其他的几个分支比较少见,这里可能主要看else分支。

final View view = createViewFromTag(parent, name, context, attrs);

final ViewGroup viewGroup = (ViewGroup) parent;

final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);

rInflateChildren(parser, view, attrs, true);

viewGroup.addView(view, params);

createViewFromTag通过节点name生成view,再将attrs里的属性取出来,然后递归遍历该view,可以看出来是采用的深度优先算法。

加载完view之后,根据finishInflate判断是否需要回调onFinishInflate()。主要的加载逻辑已经整理清楚了,接下来我们看看一些加载细节,例如createViewFromTag:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {

···

View view;

if (mFactory2 != null) {

view = mFactory2.onCreateView(parent, name, context, attrs);

} else if (mFactory != null) {

view = mFactory.onCreateView(name, context, attrs);

} else {

view = null;

}

if (view == null && mPrivateFactory != null) {

view = mPrivateFactory.onCreateView(parent, name, context, attrs);

}

if (view == null) {

final Object lastContext = mConstructorArgs[0];

mConstructorArgs[0] = context;

try {

if (-1 == name.indexOf('.')) {

view = onCreateView(parent, name, attrs);

} else {

view = createView(name, null, attrs);

}

} finally {

mConstructorArgs[0] = lastContext;

}

}

return view;

}

这个方法里面涉及到了好几种加载view的方式mFactory2、mFactory、mPrivateFactory、onCreateView,在这里我们看看onCreateView里面是如何创建view的:

// prefix = "android.view."

public final View createView(String name, String prefix, AttributeSet attrs)

throws ClassNotFoundException, InflateException {

Constructor extends View> constructor = sConstructorMap.get(name);

if (constructor != null && !verifyClassLoader(constructor)) {

constructor = null;

sConstructorMap.remove(name);

}

Class extends View> clazz = null;

try {

Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

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);

constructor.setAccessible(true);

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;

final View view = constructor.newInstance(args);

if (view instanceof ViewStub) {

// Use the same context when inflating ViewStub later.

final ViewStub viewStub = (ViewStub) view;

viewStub.setLayoutInflater(cloneInContext((Context) args[0]));

}

return view;

} catch (NoSuchMethodException e) {

final InflateException ie = new InflateException(attrs.getPositionDescription()

+ ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);

ie.setStackTrace(EMPTY_STACK_TRACE);

throw ie;

} catch (ClassCastException e) {

// If loaded class is not a View subclass

final InflateException ie = new InflateException(attrs.getPositionDescription()

+ ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);

ie.setStackTrace(EMPTY_STACK_TRACE);

throw ie;

} catch (ClassNotFoundException e) {

// If loadClass fails, we should propagate the exception.

throw e;

} catch (Exception e) {

final InflateException ie = new InflateException(

attrs.getPositionDescription() + ": Error inflating class "

+ (clazz == null ? "" : clazz.getName()), e);

ie.setStackTrace(EMPTY_STACK_TRACE);

throw ie;

} finally {

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

}

}

这里就是通过节点的name反射得到view实例,在catch里可以看到一些常见的Exception,一般自定义view时包名输出错误就容易在这里出现异常。

上面还跳过了一个步骤,就是如何通过layoutID获取parser对象,现在回过去看看:

// Resources.java

XmlResourceParser loadXmlResourceParser(@AnyRes int resId, @NonNull String type)

throws NotFoundException {

final TypedValue value = obtainTempTypedValue();

try {

final ResourcesImpl impl = mResourcesImpl;

impl.getValue(id, value, true);

if (value.type == TypedValue.TYPE_STRING) {

return impl.loadXmlResourceParser(value.string.toString(), id,

value.assetCookie, type);

}

throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)

+ " type #0x" + Integer.toHexString(value.type) + " is not valid");

} finally {

releaseTempTypedValue(value);

}

}

// ResourcesImpl.java

XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie, String type)

throws NotFoundException {

final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;

final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;

final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;

// First see if this block is in our cache.

final int num = cachedXmlBlockFiles.length;

for (int i = 0; i < num; i++) {

if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null

&& cachedXmlBlockFiles[i].equals(file)) {

return cachedXmlBlocks[i].newParser();

}

}

// Not in the cache, create a new block and put it at

// the next slot in the cache.

final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);

if (block != null) {

final int pos = (mLastCachedXmlBlockIndex + 1) % num;

mLastCachedXmlBlockIndex = pos;

final XmlBlock oldBlock = cachedXmlBlocks[pos];

if (oldBlock != null) {

oldBlock.close();

}

cachedXmlBlockCookies[pos] = assetCookie;

cachedXmlBlockFiles[pos] = file;

cachedXmlBlocks[pos] = block;

return block.newParser();

}

throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"

+ Integer.toHexString(id));

}

这里先从缓存的XmlBlock中找已存在的parser,有则直接返回,没有就mAssets.openXmlBlockAsset(assetCookie, file);得到一个block,然后block.newParser()得到XmlResourceParser对象。

LayoutInflater加载xml布局文件主要的流程已分析完,总结几点如下:

采用深度优先遍历;

解析是有缓存的;

标签的处理;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值