Android LayoutInflater loading layout detailed explanation
For students with certain Android development experience, they must have used() to load layout files, but they may not have studied its principles. For example,
Why can I load the layout file?
2. After loading the layout file, how does it become a View for us to use?
3. When we define a View, if it needs to be used in the layout, we must implement a construction method with AttributeSet parameter. Why is this?
Since this article has been raised, it means that these three issues are inseparable from LayoutInflater. During our analysis, these questions will be answered one by one.
Let's take it step by step. First, when we need to load the View from the layout, this method will be called
(context).inflater(.main_activity,null);
1. How to create a LayoutInflater?
What's worth saying about this? If you open it, you will naturally understand that LayoutInflater is an abstract class, and the abstract class cannot be instantiated directly, which means that the object we create must be a certain implementation class of LayoutInflater.
We can see
public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) (Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
Well, it is the system service you obtained! Is it obtained from the context. I haven't eaten pork and I haven't seen pigs running? When it comes to the context object, it's probably the ContextImpl object. So we go directly to the getSystemService method
@Override public Object getSystemService(String name) { return (this, name); }
Forehead. . . Go to the file again
/** * Gets a system service from a given context. */ public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher != null ? (ctx) : null; }
From the code, we can see that our Service is obtained from the HashMap SYSTEM_SERVICE_FETCHERS. If you look at the code, you will find that this HashMap is assigned in the static module. Many system services are registered here, such as ActivityService, AlarmService, etc., are all in this HashMap. From the method, we can see that we found the Service corresponding to Context.LAYOUT_INFLATER_SERVICE
registerService(Context.LAYOUT_INFLATER_SERVICE, , new CachedServiceFetcher<LayoutInflater>() { @Override public LayoutInflater createService(ContextImpl ctx) { return new PhoneLayoutInflater(()); }});
Okay, the protagonist has finally made its debut - PhoneLayoutInflater. The LayoutInflater we obtained is the object of this class.
So, the result of this part is that we have found the PhoneLayoutInflater. What is the specific function? Let’s talk about it later.
Method Analysis
This is the most important method, because it is this method that converts our layout into a View object. This method is directly defined in the LayoutInflater abstract class
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }
One of the parameters passed is the id of layout, and the other is whether ParentView is specified. We have to look at the real implementation.
public View inflate(@LayoutRes int resource, @Nullable 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 { (); } }
We first get the Resources object from the context, and then use the (resource) method to obtain an xml file parser XmlResourceParser (I won't talk about the xml file parser in Android, so as not to go too far. Students who don't know can find relevant information online to read). This is actually loading the xml file we define layout.
Then, another inflate method is called
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final Context inflaterContext = mContext; //Look, this is the attrs in the View constructor! ! ! final AttributeSet attrs = (parser); //This array is very important, as you can see from the name, this is the parameter to be used by the constructor mConstructorArgs[0] = inflaterContext; View result = root; try { // Find the root node, find the first START_TAG and jump out of the while loop. // For example, <TextView> is START_TAG, and</TextView> is END_TAG int type; while ((type = ()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(() + ": No start tag found!"); } //Get the root node name final String name = (); //Judge whether the merge tag is used 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"); } //Analysis rInflate(parser, root, inflaterContext, attrs, false); } else { // Here you need to call the method in PhoneLayoutInflater to get the View corresponding to the root node final View temp = createViewFromTag(root, name, inflaterContext, attrs); params = null; //If parentView(root) is specified, layoutParams will be generated. //And temp will be added to root later if (root != null) { params = (attrs); if (!attachToRoot) { (params); } } // The root node is parsed above, and the child nodes below the root node are parsed here rInflateChildren(parser, temp, attrs, true); if (root != null && attachToRoot) { (temp, params); } if (root == null || !attachToRoot) { result = temp; } } } catch (Exception e) { } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } return result; } }
This one is a little longer. I slightly removed some logic-independent code and added comments. If I have the patience to read, I should be able to understand it. Here we mainly talk about two parts. First, the rInflateChildren method, which is actually to take out all nodes layer by layer, and then convert them into View object through the createViewFromTag method. So the point is how to convert it into a View object.
We follow up the code layer by layer and we will finally come here.
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { ...... ...... try { ...... if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { //Not including "." It means that it is a control that comes with the system. if (-1 == ('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; } catch (InflateException e) { throw e; ...... } }
For easy understanding, the irrelevant code was removed. We saw that the createView method is actually called to convert from the xml node to the View. If the name does not contain '.', call the onCreateView method, otherwise call the createView method directly.
The onCreateView method is repeated in the above PhoneLayoutInflater, and regardless of whether it is overridden or not, the method will call createView in the end. The only difference should be that the complete class name of the system View is provided by onCreateView, and if it is a custom control, it is originally used in the layout file.
4. createView method
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { //1. Get the constructor of the class by passing it in class name Constructor<? extends View> constructor = (name); Class<? extends View> clazz = null; try { if (constructor == null) { clazz = ().loadClass( prefix != null ? (prefix + name) : name).asSubclass(); if (mFilter != null && clazz != null) { boolean allowed = (clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } constructor = (mConstructorSignature); (true); (name, constructor); } else { if (mFilter != null) { Boolean allowedState = (name); if (allowedState == null) { 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); } } } //2. Create a View instance through the obtained constructor Object[] args = mConstructorArgs; args[1] = attrs; final View view = (args); if (view instanceof ViewStub) { final ViewStub viewStub = (ViewStub) view; (cloneInContext((Context) args[0])); } return view; } catch (NoSuchMethodException e) { ...... } }
This code mainly does two things
First, load the class into memory according to ClassName and then get the specified constructor constructor. The constructor is specified by passing in parameter type and number, here the mConstructorSignature is passed in.
static final Class<?>[] mConstructorSignature = new Class[] { , };
That is, the parameters passed in are Context and AttributeSet. Did you suddenly wake up? ! !This is why when we customize the View, we must rewrite the View (Context context, AttributeSet attrs) to use our View in layout.
Second, use the obtained constructor constructor to create a View instance.
5. Answer the question
Do you still remember the three questions we mentioned above? Now let’s answer one by one:
Why can I load the layout file?
Because LayoutInflater actually loads the xml file through the xml parser, and the format of the layout file is xml, it can be read.
2. After loading the layout file, how does it become a View for us to use?
After LayoutInflater is loaded into the contents of the xml file, the name of each tag is taken out through reflection and the corresponding class name is generated. Then the constructor function of the class is obtained through reflection, with the parameters Context and AttributeSet. Then create the View object through the constructor.
3. When we define a View, if it needs to be used in the layout, we must implement a construction method with AttributeSet parameter. Why is this?
Because when LayoutInflater parses the xml file, it converts the contents in the xml into an AttributeSet object, which contains the attribute values set in the xml file. These attribute values need to be taken out in the constructor and assigned to the attributes of the instance.
Thank you for reading, I hope it can help you. Thank you for your support for this site!