This paper mainly introduces the implementation of layout filler in android, that is, the process of parsing Xml layout file into View. The following source code is extracted from android 8.0
Catalog
- LayoutInflater.from()
- inflate()
- summary
1. LayoutInflater.from()
This method will eventually get an instance of PhoneLayoutInflate, which inherits the LayoutInflate abstract class
1. Source code analysis
LayoutInflater abstract class
public static LayoutInflater from(Context context) { //Assuming that the Activity is passed in, the context initialized after the Activity starts is ContextImpl, the activation process of the Activity //I will not elaborate on it here, but rather call the getSystem Service of ContextImpl. LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
ContextImpl class
public Object getSystemService(String name) { //The getSystemService Method of Direct Tuning System Service Registry return SystemServiceRegistry.getSystemService(this, name); }
SystemServiceRegistry class
public static Object getSystemService(ContextImpl ctx, String name) { //SYSTEM_SERVICE_FETCHERS is a HashMap, Service Fetcher<T> is an interface, and there is only one getService interface method inside. //This name is LAYOUT_INFLATER_SERVICE, so let's see when it was added to HashMap, and we'll find it in this class. //There will be a static block of code ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher != null ? fetcher.getService(ctx) : null; }
static{ ... //This method adds services to HashMap registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>() { @Override public LayoutInflater createService(ContextImpl ctx) { //As you can see from this, what we get is actually an instance of PhoneLayoutInflater return new PhoneLayoutInflater(ctx.getOuterContext()); } }); ... }
private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) { SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName); //Adding services to HashMap SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); }
2. Call procedure

2. inflate()
As you can see from the above, what we get is actually an instance of PhoneLayoutInflater, which inherits the abstract class of LayoutInflater, and actually calls the inflate method in LayoutInflater. We assume that we call the inflate(R.layout.activity_main, null) method.
1. Source code analysis
LayoutInflater abstract class
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { //Another way to adjust the overload return inflate(resource, root, root != null); }
//The first parameter is the layout file id, the second parameter is null, and the third parameter is false. 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) + ")"); } //Get the xml parser, interface XmlResourceParser extends XmlPullParser final XmlResourceParser parser = res.getLayout(resource); try { //Tuning another overloaded inflate method return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { ... View result = root; // Look for the root node. int type; //Find the first start tag while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } //Get the label name final String name = parser.getName(); 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"); } //If the first tag is merge, call rInflate parsing rInflate(parser, root, inflaterContext, attrs, false); } else { //The first tag is not merge, so call createViewFromTag, which parses a tag into the corresponding View. //Let's assume that the first tag is Relative Layout. final View temp = createViewFromTag(root, name, inflaterContext, attrs); ... //Resolve the label under Relative Layout, which, like merge, will be called to the rInflate method rInflateChildren(parser, temp, attrs, true); ... if (root == null || !attachToRoot) { //Return the parsed temp directly result = temp; } } ...ellipsis try-catch... return result; } }
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { //Direct tuning rInflate method rInflate(parser, parent, parent.getContext(), attrs, finishInflate); }
The whole process is to determine whether the merge tag is merge tag or merge tag is rInflate method. Otherwise, we first go to createViewFromTag method to get the view of the first tag object, then go to rInflate method, and finally return to a View tree. So here we just need to focus on the two methods of createViewFromTag and InfrLatViewFromTag. Now let's see how the createViewFromTag method can be used as a marker. The signature is parsed into the corresponding View's
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) { //Method of directly adjusting five parameters for overloading return createViewFromTag(parent, name, context, attrs, false); }
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { ... if (name.equals(TAG_1995)) { //TAG_1995 is a blink string, that is, if it is blink tag, //Then go straight back. The BlinkLayout class is the inner class of LayoutInflater. return new BlinkLayout(context, attrs); } View view; //By default, these factories are null 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); } //Start parsing if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { //System control, no point, system control is finally prefixed with android.widget //Last but not least is the creation view method of LayoutInFlater. view = onCreateView(parent, name, attrs); } else { //Custom control, a little, without prefix view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; ...ellipsis try-catch... }
protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException { //Because the real example is Phone Layout Inflater, and Phone Layout Inflate is rewritten //The onCreateView method, so the onCreateView in PhoneLayoutInflater is tuned here return onCreateView(name, attrs); }
PhoneLayoutInflate class
private static final String[] sClassPrefixList = { "android.widget.", "android.webkit.", "android.app." }; @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { for (String prefix : sClassPrefixList) { try { //createView Method for Tuning LayoutInflater //This method prefixes the system control with android.widget. View view = createView(name, prefix, attrs); if (view != null) { return view; } } catch (ClassNotFoundException e) { // In this case we want to let the base class take a crack // at it. } } return super.onCreateView(name, attrs); }
Whether it's a custom control or a system control, it will eventually be called to LayoutInflater's createView method
LayoutInflate class
//The prefix of the system control is android.widget, and the prefix of the custom control is null, that is, no prefix. public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { //Getting constructs from caches Constructor<? extends View> constructor = sConstructorMap.get(name); if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Class<? extends View> clazz = null; Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); if (constructor == null) { //Create one if it is not in the cache, and the system control will be added with android.widget 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); } } //Get constructs from Class and add them to the cache constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { ... } Object lastContext = mConstructorArgs[0]; if (mConstructorArgs[0] == null) { // Fill in the context if not already within inflation. mConstructorArgs[0] = mContext; } Object[] args = mConstructorArgs; args[1] = attrs; //Reflection Creates View 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])); } mConstructorArgs[0] = lastContext; return view; ...ellipsis try-catch... }
As you can see from the above, the process from tag to response View is achieved by reflection, and the constructed acquisition uses the hedonic mode. Let's look at another rInflate method.
LayoutInflate class
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; boolean pendingRequestFocus = false; //Notice the cycle. 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)) { pendingRequestFocus = true; consumeChildElements(parser); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } //Parse include parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { //The merge tag must be the root tag. There should be no merge here, throwing an exception directly. throw new InflateException("<merge /> must be the root element"); } else { //As mentioned above, this method parses a single tag into a corresponding View final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); //The rInflateChildren method directly drops the current rInflate method, recursive parsing, and depth first. rInflateChildren(parser, view, attrs, true); //Add to parent View viewGroup.addView(view, params); } } ... }
2. Call procedure

Three, summary
1. xml layout file parsing is through xml Pull Parser, that is, android built-in pull parsing, as for the difference between Dom parsing, Sax parsing, Pull parsing and the use of Baidu
2. Each label in the layout is divided into system control and custom control. They are distinguished by points, some are custom control, and no point is system control. System control will eventually be prefixed with android.widget to form a complete path name.
3. Eventually, each tag is created by reflection to get the corresponding control instance. Eventually, a View tree is formed by depth-first order parsing.