Android中将xml布局文件转化为View树的过程分析(下)-- LayoutInflater源码分析
在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的关键代码分析完毕了。