在Android开发中为了inflate一个布局文件,大体有2种方式,如下所示:
// 1. get a instance of LayoutInflater, then do whatever you want
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // 2. you're in some View class, then just call View's static inflate method
View.inflate(context, R.layout.xxx_xml, someViewGroup/null);
我们来看看这2种方式的具体源码:
<!-- View.java -->
/**
* Inflate a view from an XML resource. This convenience method wraps the {@link
* LayoutInflater} class, which provides a full range of options for view inflation.
*
* @param context The Context object for your activity or application.
* @param resource The resource ID to inflate
* @param root A view group that will be the parent. Used to properly inflate the
* layout_* parameters.
* @see LayoutInflater
*/
public static View inflate(Context context, int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
} <!-- LayoutInflater.java -->
/**
* 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;
}
现在我们看到实质上都是方法1中的做法,View.inflate只是个helper方法而已(少敲几行代码)。那么我们就先来看看
Context.getSystemService的具体实现,这里我们直接去ContextImpl.java文件中的相关代码:
/**
* Override this class when the system service constructor needs a
* ContextImpl. Else, use StaticServiceFetcher below.
*/
/*package*/ static class ServiceFetcher {
int mContextCacheIndex = -1; /**
* Main entrypoint; only override if you don't need caching.
*/
public Object getService(ContextImpl ctx) {
ArrayList<Object> cache = ctx.mServiceCache;
Object service;
synchronized (cache) {
if (cache.size() == 0) {
// Initialize the cache vector on first access.
// At this point sNextPerContextServiceCacheIndex
// is the number of potential services that are
// cached per-Context.
for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
cache.add(null);
}
} else {
service = cache.get(mContextCacheIndex); // 先从cache中找,
if (service != null) { // 如果已经存在了直接返回
return service;
}
}
service = createService(ctx); // 否则创建并加入到cache中,只会调用1次
cache.set(mContextCacheIndex, service);
return service;
}
} /**
* Override this to create a new per-Context instance of the
* service. getService() will handle locking and caching.
*/
public Object createService(ContextImpl ctx) {
throw new RuntimeException("Not implemented");
}
} /**
* Override this class for services to be cached process-wide.
*/
abstract static class StaticServiceFetcher extends ServiceFetcher {
private Object mCachedInstance; @Override
public final Object getService(ContextImpl unused) {
synchronized (StaticServiceFetcher.this) {
Object service = mCachedInstance;
if (service != null) {
return service;
}
return mCachedInstance = createStaticService();
}
} public abstract Object createStaticService(); // 它不需要ContextImpl参数
} private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
new HashMap<String, ServiceFetcher>(); // 全局system service的map private static int sNextPerContextServiceCacheIndex = 0;
private static void registerService(String serviceName, ServiceFetcher fetcher) {
if (!(fetcher instanceof StaticServiceFetcher)) {
fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
}
SYSTEM_SERVICE_MAP.put(serviceName, fetcher); // 放到全局的静态map中
}
// 还有很多registerService的调用,这里都省略了,我们现在只关心LAYOUT_INFLATER_SERVICE
registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) { // 我们前一篇文章中提到过会new一个
return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext()); // PhoneLayoutInflater的对象返回
}});
到这里我们就清楚了Context.getSystemService方法的具体实现了,接下来我们将注意力转移到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;
} /**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy.
* @return The root View of the inflated hierarchy. If root was supplied,
* this is the root View; otherwise it is the root of the inflated
* XML file.
*/
public View inflate(int resource, ViewGroup root) { // 实际上调用3个参数的版本,从这里我们可以看出客户端代码
return inflate(resource, root, root != null); // 没必要这样写(root!= null):inflate(resource, root, true);
} /**
* Inflate a new view hierarchy from the specified xml node. Throws
* {@link InflateException} if there is an error. *
* <p>
* <em><strong>Important</strong></em> For performance
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
*
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy.
* @return The root View of the inflated hierarchy. If root was supplied,
* this is the root View; otherwise it is the root of the inflated
* XML file.
*/
public View inflate(XmlPullParser parser, ViewGroup root) { // 不太常用,我们一般使用layout文件的版本,但实质都一样
return inflate(parser, root, root != null); // 下面的代码中inflate一个include tag时调用了此版本
} /**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
if (DEBUG) System.out.println("INFLATING from resource: " + resource);
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
} /**
* Inflate a new view hierarchy from the specified XML node. Throws
* {@link InflateException} if there is an error.
* <p>
* <em><strong>Important</strong></em> For performance
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
*
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { // 这是最终调用的版本
synchronized (mConstructorArgs) { // 进入同步块
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
View result = root; // 此方法最后的返回值,初始化为传入的root try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
// 能走到这里,说明type是START_TAG 或 END_DOCUMENT
if (type != XmlPullParser.START_TAG) { // 如果一开始就是END_DOCUMENT,那说明xml文件有问题
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
// 能到这里,那type一定是START_TAG,也就是xml文件里的root node
final String name = parser.getName(); // 获得当前start tag的name if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
} if (TAG_MERGE.equals(name)) { // 处理merge tag的情况
if (root == null || !attachToRoot) { // root必须非空且attachToRoot为true,否则抛异常结束
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true"); // 因为merge的xml并不代表某个具体的view,只是将它
} // 包起来的其他xml的内容加到某个上层ViewGroup中 rInflate(parser, root, attrs, false); // 递归的inflate
} else {
// Temp is the root view that was found in the xml
View temp; // xml文件中的root view
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs); // 根据tag节点创建view对象
} 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); // 根据root生成合适的LayoutParams实例
if (!attachToRoot) { // 如果不attach的话就调用view的setLayoutParams方法
// 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); // 递归inflate剩下的所有children
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非空且指定了要attachToRoot
root.addView(temp, params); // 将xml文件的root view 加到用户提供的root里
} // Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp; // 否则我们将返回xml里发现的root view:temp,而不是方法中传递进来的root对象
}
} } 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;
} Trace.traceEnd(Trace.TRACE_TAG_VIEW); return result; // 返回参数root或xml文件里的root view
}
}
接下来我们看看inflate各种不同节点的方法:
/**
* Recursive method used to descend down the xml hierarchy and instantiate
* views, instantiate their children, and then call onFinishInflate().
*/
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException { // 深度优先inflate,所有才能保证你在onFinish
// Inflate()里可以通过findViewById找到已经创建完毕的孩子view
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;
}
// 确保是一个START_TAG node
final String name = parser.getName(); // 拿到tagName if (TAG_REQUEST_FOCUS.equals(name)) { // 处理REQUEST_FOCUS tag
parseRequestFocus(parser, parent);
} else if (TAG_INCLUDE.equals(name)) { // 处理include tag
if (parser.getDepth() == 0) { // include节点不能是根节点,否则就抛异常了。。。
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, parent, attrs);
} else if (TAG_MERGE.equals(name)) { // merge节点必须是xml文件里的根节点,也就是说到这里的时候不应该再出现merge节点了
throw new InflateException("<merge /> must be the root element");
} else if (TAG_1995.equals(name)) {
final View view = new BlinkLayout(mContext, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
} else { // 一般情况,各种Android view、widget或用户自定义的view节点
final View view = createViewFromTag(parent, name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
}
} if (finishInflate) parent.onFinishInflate(); // parent的所有孩子节点都inflate完毕的时候,调用onFinishInflate回调
} private void parseRequestFocus(XmlPullParser parser, View parent)
throws XmlPullParserException, IOException {
int type;
parent.requestFocus();
final int currentDepth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG || // 忽略此节点剩下的所有内容,直到下一个新的START_TAG
parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
// Empty
}
} private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs)
throws XmlPullParserException, IOException { int type; if (parent instanceof ViewGroup) {
final int layout = attrs.getAttributeResourceValue(null, "layout", 0); // include节点中必须指定layout属性的值
if (layout == 0) {
final String value = attrs.getAttributeValue(null, "layout");
if (value == null) {
throw new InflateException("You must specifiy a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
} else {
throw new InflateException("You must specifiy a valid layout "
+ "reference. The layout ID " + value + " is not valid.");
}
} else {
final XmlResourceParser childParser =
getContext().getResources().getLayout(layout); try {
final AttributeSet childAttrs = Xml.asAttributeSet(childParser); while ((type = childParser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty.
} if (type != XmlPullParser.START_TAG) {
throw new InflateException(childParser.getPositionDescription() +
": No start tag found!");
} final String childName = childParser.getName(); if (TAG_MERGE.equals(childName)) { // 处理include xml里包含merge的情况
// Inflate all children.
rInflate(childParser, parent, childAttrs, false);
} else { // 处理一般的include layout文件,创建此xml文件的root view
final View view = createViewFromTag(parent, childName, childAttrs);
final ViewGroup group = (ViewGroup) parent; // We try to load the layout params set in the <include /> tag. If
// they don't exist, we will rely on the layout params set in the
// included XML file.
// During a layoutparams generation, a runtime exception is thrown
// if either layout_width or layout_height is missing. We catch
// this exception and set localParams accordingly: true means we
// successfully loaded layout params from the <include /> tag,
// false means we need to rely on the included layout params.
ViewGroup.LayoutParams params = null;
try {
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
params = group.generateLayoutParams(childAttrs);
} finally {
if (params != null) {
view.setLayoutParams(params); // 设置其layoutParams
}
} // Inflate all children.
rInflate(childParser, view, childAttrs, true); // 递归inflate剩下的节点 // Attempt to override the included layout's android:id with the
// one set on the <include /> tag itself.
TypedArray a = mContext.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.View, 0, 0);
int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
// While we're at it, let's try to override android:visibility.
int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
a.recycle(); if (id != View.NO_ID) {
view.setId(id); // override id,如果include节点提供了
} switch (visibility) { // 同样的,override visibility,如果include节点提供了
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
} group.addView(view); // 将include的xml文件里的root view加到上层group中
}
} finally {
childParser.close();
}
}
} else { // include节点必须是某个ViewGroup的子节点
throw new InflateException("<include /> can only be used inside of a ViewGroup");
} final int currentDepth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG || // skip掉include节点剩下的内容
parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
// Empty
}
}
最后我们看看根据节点创建对应View的相关方法:
/*
* default visibility so the BridgeInflater can override it.
*/
View createViewFromTag(View parent, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
} if (DEBUG) System.out.println("******** Creating view: " + name); try {
View view;
// 这里我们忽略掉了各种factory的onCreateView,有兴趣的读者可自行研究
if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);
else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);
else view = null; if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
} if (view == null) {
if (-1 == name.indexOf('.')) { // 创建android.view.*里的任何view,如TextView,ImageView等等
view = onCreateView(parent, name, attrs); // 其子类PhoneLayoutInflater override了此方法用来
} else { // 创建android.widget.*/android.webkit.*里的任何对象
view = createView(name, null, attrs); // 创建用户自定义的各种View(如com.xiaoweiz.browser.MyCustomView)
}
} 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;
}
} /**
* Low-level function for instantiating a view by name. This attempts to
* instantiate a view class of the given <var>name</var> found in this
* LayoutInflater's ClassLoader.
*
* <p>
* There are two things that can happen in an error case: either the
* exception describing the error will be thrown, or a null will be
* returned. You must deal with both possibilities -- the former will happen
* the first time createView() is called for a class of a particular name,
* the latter every time there-after for that class name.
*
* @param name The full name of the class to be instantiated.
* @param attrs The XML attributes supplied for this instance.
*
* @return View The newly instantiated view, or null.
*/
public final View createView(String name, String prefix, AttributeSet attrs) // 用户自定义的view不需要prefix,因为
throws ClassNotFoundException, InflateException { // name中已经有所有需要的信息了;系统的prefix则是android.view.
Constructor<? extends View> constructor = sConstructorMap.get(name); // 或android.widget. 或 android.webkit.
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( // 加载class文件
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); // 拿到此类型的ctor
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; // 需要2个参数Context,AttributeSet的版本,所以如果你不打算动态inflate
args[1] = attrs; // 你的view,则没必要提供此版本的ctor。 final View view = constructor.newInstance(args); // new一个View(可能是其子类)的对象,可能为null
if (view instanceof ViewStub) { // 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 {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
至此我们已经将LayoutInflater.inflate的关键代码分析完毕了。