LayoutInflater parsing
Preface:
In Android, if you are a junior player, you may not be familiar with LayoutInflater, and maybe you have just used it in the onCreateView() of Fragment. But if someone with a little work experience knows how important this class is. It is a bridge connecting layout XML and Java code. We often wonder why Android supports XML writing layouts?
What we think of is that Android can help us parse xml files, and LayoutInflater helps us do this work.
First of all, LayoutInflater is a system service, we can see this from the from method
/** * Obtains the LayoutInflater from the given context. */ public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) (Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
Usually, after we get the LayoutInflater object, we will call its inflate method to load the layout. Inflate is an overloaded method.
public View inflate(int resource, ViewGroup root) { return inflate(resource, root, root != null); }
It can be seen that when we call the method with 2 parameters, it is added to the parent layout by default (the parent layout is generally not empty)
public View inflate(int resource, ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { (TAG, "INFLATING from resource: \"" + (resource) + "\" (" + (resource) + ")"); } final XmlResourceParser parser = (resource); try { return inflate(parser, root, attachToRoot); } finally { (); } }
In this method, the resource ID is actually used to restore the resource ID to the XMlResourceParser object, and then call the inflate (XmlPullParser parser, ViewGroup root, boolean attachToRoot) method. The specific steps for parsing the layout are implemented in this method
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { (Trace.TRACE_TAG_VIEW, "inflate"); final AttributeSet attrs = (parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; View result = root; try { // Look for the root node. //1. Looping to find the root node is actually the process of node pointer traversal int type; while ((type = ()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(() + ": No start tag found!"); } //2. Get the name of the node, which is used to judge the node final String name = (); if (DEBUG) { ("**************************"); ("Creating root view: " + name); ("**************************"); } //3. Make judgments on the node name, and then merge adds it to the parent layout (it must be added to the parent layout according to the Merge characteristics) 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 { //4. Create a View based on the node // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, attrs, false); params = null; if (root != null) { if (DEBUG) { ("Creating params from root: " + root); } // Create layout params that match root, if supplied //5. Generate layout parameters based on attrs params = (attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) //6. If the View is not added to the parent layout, then set layout parameters for it itself (params); } } if (DEBUG) { ("-----> start inflating children"); } // Inflate all children under temp // 7. Load all child Views under this node rInflate(parser, temp, attrs, true, true); if (DEBUG) { ("-----> done inflating children"); } // We are supposed to attach all the views we found (int temp) // to root. Do that now. //8. If added to the parent layout, addView directly if (root != null && attachToRoot) { (temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. //9. If not added to the parent layout, then return it itself if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(()); (e); throw ex; } catch (IOException e) { InflateException ex = new InflateException( () + ": " + ()); (e); throw ex; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } (Trace.TRACE_TAG_VIEW); return result; } }
I have added comments on the key steps, core
1. Find the root layout tag
2. Create the view corresponding to the root node
3. Create its child View
We can see from this that the analysis of subView is actually rInflate method. If there is a root layout in the xml, then call createViewFromTag to create the root View in the layout. We can also understand the original merge, because it directly calls rInflate to add it to the parent View. When we see the difference between the second parameter of rInflate(parser, root, attrs, false, false) and rInflate(parser, temp, attrs, true, true) we will understand.
Next, let's take a look at how rInflate creates multiple layouts
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, boolean finishInflate, boolean inheritContext) throws XmlPullParserException, IOException { //Get the node where the current parser pointer is located is at the layout level final int depth = (); int type; //Do the depth-first traversal of the tree (if a node has children, it will enter rInflate again, otherwise continue the loop) while (((type = ()) != XmlPullParser.END_TAG || () > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = (); //If there is a request_focus tag, set focus for this node View if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); //If there is a tag tag, set tag(key, value) for this node View } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { //If there is an include tag, if the include tag is if (() == 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 { //Create the View represented by this node and add it to the parent view, and traverse the child nodes in addition final View view = createViewFromTag(parent, name, attrs, inheritContext); final ViewGroup viewGroup = (ViewGroup) parent; final params = (attrs); rInflate(parser, view, attrs, true, true); (view, params); } } //Represents that a node has its child node traversal ends if (finishInflate) (); }
As can be seen from the above, so the creation of View will be handed over to createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext). We can see how this method creates View
View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) { if (("view")) { name = (null, "class"); } Context viewContext; if (parent != null && inheritContext) { viewContext = (); } else { viewContext = mContext; } // Apply a theme wrapper, if requested. final TypedArray ta = (attrs, ATTRS_THEME); final int themeResId = (0, 0); if (themeResId != 0) { viewContext = new ContextThemeWrapper(viewContext, themeResId); } (); if ((TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(viewContext, attrs); } if (DEBUG) ("******** Creating view: " + name); try { View view; if (mFactory2 != null) { view = (parent, name, viewContext, attrs); } else if (mFactory != null) { view = (name, viewContext, attrs); } else { view = null; } if (view == null && mPrivateFactory != null) { view = (parent, name, viewContext, attrs); } if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = viewContext; try { if (-1 == ('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } if (DEBUG) ("Created view is: " + view); return view; } catch (InflateException e) { throw e; } catch (ClassNotFoundException e) { InflateException ie = new InflateException(() + ": Error inflating class " + name); (e); throw ie; } catch (Exception e) { InflateException ie = new InflateException(() + ": Error inflating class " + name); (e); throw ie; } }
It's actually very simple, it's just 4 downgrades
if(factory2!=null){ (); }else if(factory!=null){ (); }else if(mPrivateFactory!=null){ (); }else{ onCreateView() }
If we don't set other onCreateViews, we will look at our own onCreateView(). In fact, this method will call createView().
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { //Get the required constructor from the constructor Map (cache) Constructor<? extends View> constructor = (name); Class<? extends View> clazz = null; try { (Trace.TRACE_TAG_VIEW, name); if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it //If there is no required constructor in the cache, then load the required class through ClassLoader clazz = ().loadClass( prefix != null ? (prefix + name) : name).asSubclass(); if (mFilter != null && clazz != null) { boolean allowed = (clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } //Cache used constructor constructor = (mConstructorSignature); (name, constructor); } else { // If we have a filter, apply it to cached constructor if (mFilter != null) { // Have we seen this name before? Boolean allowedState = (name); if (allowedState == null) { // New class -- remember whether it is allowed clazz = ().loadClass( prefix != null ? (prefix + name) : name).asSubclass(); boolean allowed = clazz != null && (clazz); (name, allowed); if (!allowed) { failNotAllowed(name, prefix, attrs); } } else if (()) { failNotAllowed(name, prefix, attrs); } } } Object[] args = mConstructorArgs; args[1] = attrs; (true); //Get the required instance object through reflection final View view = (args); if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; //ViewStub will create its own LayoutInflater, because it needs to inflate at different times (cloneInContext((Context) args[0])); } return view; } catch (NoSuchMethodException e) { InflateException ie = new InflateException(() + ": Error inflating class " + (prefix != null ? (prefix + name) : name)); (e); throw ie; } catch (ClassCastException e) { // If loaded class is not a View subclass InflateException ie = new InflateException(() + ": Class is not a View " + (prefix != null ? (prefix + name) : name)); (e); throw ie; } catch (ClassNotFoundException e) { // If loadClass fails, we should propagate the exception. throw e; } catch (Exception e) { InflateException ie = new InflateException(() + ": Error inflating class " + (clazz == null ? "<unknown>" : ())); (e); throw ie; } finally { (Trace.TRACE_TAG_VIEW); } }
The general step is,
1. Get a specific View constructor from the cache. If not, the corresponding class will be loaded and the constructor will be cached.
2. Use the constructor to reflect the corresponding View
3. If it is a ViewStub, copy a LayoutInflater object and pass it
Thank you for reading, I hope it can help you. Thank you for your support for this site!