Layoutinflater.inflate () is a frequently used system method that loads THE XML as a View object, to see how it works in the source code.

1. inflate

There are four overloads inflate:

  1. inflate(int resource, ViewGroup root)
  2. inflate(XmlPullParser parser, ViewGroup root)
  3. inflate(int resource, ViewGroup root, boolean attachToRoot)
  4. inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

The first three methods all end up calling the fourth overcompiled method. The difference is that the third method has a tryInflatePrecompiled() method. After reading the resource ID and converting it to XmlResourceParser, the fourth overcompiled method is still called. All the logic is in the fourth overloaded method.

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    returninflate(resource, root, root ! =null);
}

public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
  	returninflate(parser, root, root ! =null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();

    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if(view ! =null) {
      return view;
    }
    XmlResourceParser parser = res.getLayout(resource);
    try {
      return inflate(parser, root, attachToRoot);
    } finally{ parser.close(); }}Copy the code

Enter tryInflatePrecompiled() to see that this method has an if (! MUseCompiledView). Check the variable assignment, the default initialization method is set to false, assign a value to it is only one is true, in setPrecompiledLayoutsEnabledForTesting (), it is a open only for unit testing methods.

So the tryInflatePrecompiled() method generally does not go there.

class LayoutInflater {
    private boolean mUseCompiledView;

    private void initPrecompiledViews(a) {
        // Precompiled layouts are not supported in this release.
        boolean enabled = false;
        initPrecompiledViews(enabled);
    }

    private void initPrecompiledViews(boolean enablePrecompiledViews) { mUseCompiledView = enablePrecompiledViews; . }/ * * *@hide for use by CTS tests
     */
    @TestApi
    public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) {
        initPrecompiledViews(enablePrecompiledLayouts);
    }
    
    
    View tryInflatePrecompiled(int resource, Resources res, ViewGroup root, boolean attachToRoot) {
        if(! mUseCompiledView) {return null; }... }}Copy the code

1.1. XmlResourceParser

The LayoutRes are converted to an XmlResourceParser object by calling res.getLayout(Resource).

XmlResourceParser is an INTERFACE for parsing XML resources. In a nutshell, it is an XML resource parser used for parsing XML resources, which will be widely used in the future. Here is a brief introduction to its methods.

  1. int getEventType()

    Returns the current event type. The main types are:

    • Int START_DOCUMENT = 0: start flag of XML document

    • Int END_DOCUMENT = 1: indicates the end of an XML document

    • Int START_TAG = 2: starts XML tags, such as

    • Int END_TAG = 3: XML tag end flag, such as or />.

  2. String getAttributeValue(String namespace,String name)

    Returns the property value corresponding to the specified property name, passing null as the first argument if no namespace is used.

  3. String getName()

    Returns the name of the current TAG, such as

  4. int next()

The cursor moves to the next TAB.

  1. int getDepth()

    Gets the depth of the current label with a root layout depth of 1, a direct child of the root layout depth of 2, and so on, increasing by 1 for each additional layer of depth.

1.2. The fourth overload method

This is the main logic inflate in this method.

In this method, the merge is first treated specially, with rInflate() called directly to continue the traversal.

Merge is not a ViewGroup or View, it declares some views to be added to another ViewGroup, which can reduce unnecessary layout. ParentTag = parentTag; parentTag = parentTag;

In the case of a non-merge, after creating the root view with the CreateViewFromTag() call, the root view will continue to iterate by calling rInflateChildren() as root. These methods will be introduced later.

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized(mConstructorArgs) {...final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser); // Attribute collection
        View result = root;
        try {
            // Check the START_TAG tag
            advanceToRootNode(parser);
            final String name = parser.getName();

            if (TAG_MERGE.equals(name)) {
                // When using merge, inflate must have root and attachToRoot==true
                if (root == null| |! attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }

                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Create the root view
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                // Inflate, all properties assigned to the root View will be disabled if root is not passed
                if(root ! =null) {
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);
                    if(! attachToRoot) {// Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        // If attachToRoot==false, set param to the root view
                        // If attachToRoot==true, set to the root view via addViewtemp.setLayoutParams(params); }}// Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if(root ! =null && attachToRoot) {
                    root.addView(temp, params);
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                // If root! = null && attachToRoot, which sends back the root view directly, or otherwise the root view
                if (root == null| |! attachToRoot) { result = temp; }}}returnresult; }}Copy the code

You can also see from this method that you normally use several features inflate:

  • mergeIt’s not a view, any label that’s set to it is invalid
  • If the root layout ismerge, the use ofinflateWhen loading,rootCannot be empty andattchToRootCan’t befalse.
  • ifrootIs null, all tags in the root view will be invalidated, the reason should be based onrootType to create differentlayoutParam.
  • ifroot ! = null && attachToRoot, the root view will be added directly torootAnd,rootWill be the return value, otherwise the root view will be the return value.

1.3. rInflate

Further down, rInflateChildren() still calls the rInflateChildren() method, and the difference between the two is that the context is not retrieved the same way.

RInflate () is a large loop first, see the loop condition ((type = parser.next())! = XmlPullParser. END_TAG | | parser. GetDepth () > the depth), only both unsatisfied (namely go to the END tag, and depth is not greater than the depth of the parent) will withdraw from circulation, in general, Go to parent’s END_TAG tag to exit the loop.

Inside the loop, requestFocus, tag, and include tags are handled in a special way. Otherwise, createViewFromTag() is used to create the current View. And call rInflateChildren() recursively. If it is not a ViewGroup, it will read the END_TAG of the current view in the next recursion, which means it will not enter the loop.

So you can be sure that only the parent’s direct child View is created with each rInflate() call. The resulting child view will be added to the parent.

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
                            boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}


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;

    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)) {
            //requestFocus:parent gets focus
            pendingRequestFocus = true;
            consumeChildElements(parser); // Skip to the parent level
        } else if (TAG_TAG.equals(name)) {
            //tag: Call parent-.setTag (key, value) to set the tag tag
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            // Add the include tag
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            // Merge can only be the root label
            throw new InflateException("<merge /> must be the root element");
        } else {
            // createViewFromTag creates the view and then continues the deep traversal
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); }}if (pendingRequestFocus) {
        parent.restoreDefaultFocus();
    }

    if(finishInflate) { parent.onFinishInflate(); }}Copy the code

1.4. createViewFromTag

In the previous method, the control was created multiple times by calling createViewFromTag().

In this method, we first treat the view tag in particular, reading the class path as name. The topic is then read.

The attempt was made in two steps, starting with a call to tryCreateView(). If you return NULL, then you determine the number of decimal points in the pathname, and you go through different methods. Recall that labels without decimal points, which are the views that come with the system, don’t need to enter the full path in XML, you just need to enter the name.

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                       boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        // Lower case view, you can directly pass in the class path, reset the name
        name = attrs.getAttributeValue(null."class");
    }

    // Read the topic
    if(! ignoreThemeAttr) {final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0.0);
        if(themeResId ! =0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }

    try {
        View view = tryCreateView(parent, name, context, attrs);
        if (view == null) {...try {
                if (-1 == name.indexOf('. ')) {
                    view = onCreateView(context, parent, name, attrs);
                } else {
                    view = createView(context, name, null, attrs); }}}returnview; }... }Copy the code

1.4.1. tryCreateView

Let’s see how tryCreateView() tries to create it.

Let’s party like it’s 1995!

Later, calls to mFactory, mFactory2, and mPrivateFactory proxy the method created.

public final View tryCreateView(@Nullable View parent, @NonNull String name,
                                @NonNull Context context,
                                @NonNull AttributeSet attrs) {
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    View view;
    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);
    }

    return view;
}
Copy the code

To quickly find out where these factory implementations are, just hit a break point and look.

As you can see, the main call to mFactory2 is the implementation AppCompatDelegateImpl and the implementation Activity of mPrivateFactory.

The mPrivateFactory implementation Activity logic is centered in the FragmentActivity, which does special work with the fragment tag and can be created by proxy through the Activity itself. Instead of looking in, look at the implementation of mFactory2, AppCompatDelegateImpl.

AppCompatDelegateImpl layer upon layer, in the final in AppCompatDelegateImpl. CreateView () agent gave AppCompatViewInflater implementation.

In AppCompatViewInflater. CreateView (), you can see that the label for the TextView create returned to AppCompatTextView directly.

This is not the place for controls such as AppCompatXXX to be converted! Serendipity. This makes it clear why the XML automatically converts to the corresponding AppCompatXXX, whereas custom views need to inherit AppCompatXXX directly.

If there is no interception here, it usually returns NULL and leaves it to LayoutInflater to create.

//AppCompatDelegateImpl
public View createView(View parent, final String name, @NonNull Context context,
                       @NonNull AttributeSet attrs) {...if (mAppCompatViewInflater == null) {
        mAppCompatViewInflater = newAppCompatViewInflater(); . }return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
            IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
            true./* Read read app:theme as a fallback at all times for legacy reasons */
            VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
    );
}

//AppCompatViewInflater
final View createView(View parent, final String name, @NonNull Context context,
                      @NonNull AttributeSet attrs, boolean inheritContext,
                      boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {... View view =null;

    // We need to 'inject' our tint aware Views in place of the standard framework versions
    switch (name) {
        case "TextView":
            view = createTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "ImageView":
            view = createImageView(context, attrs);
            verifyNotNull(view, name);
            break; . }return view;
}

//AppCompatViewInflater
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
    return new AppCompatTextView(context, attrs);
}
Copy the code

1.4.2. createView

TryCreateView (), if not successfully created, will go down and enter onCreateView() or createView() depending on whether there is a decimal point. First look at how onCreateView() handles system controls.

1.4.2.1. onCreateView

In LayoutInflater, onCreateView() is also passed through layers, and createView is called directly, just prefixed with the package name android.view. As the perFix attribute goes into createView(), you can see that you don’t need to write the full package name because it is automatically supplemented.

View is not the only package name in the system. When I look it up, I see that LayoutInflater has an implementation class called PhoneLayoutInflater that overrides the onCreateView() method. We experimented with android.widget, Android. webKit, and Android. app package names. In other words, the LayoutInflater objects we use are actually PhoneLayoutInflater objects.

//LayoutInflater
protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

public class PhoneLayoutInflater extends android.view.LayoutInflater {
    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 {
                View view = createView(name, prefix, attrs);
                if(view ! =null) {
                    returnview; }}}return super.onCreateView(name, attrs); }}Copy the code

1.4.2.2. createView

CreateView () finally sees the creation logic and first looks in the cache for a creator.

If not, try to load the CLASS object by its package name and get it cached by its loader using prefix + name, which is the full package name prefix added to oncreateView().

NewInstance () is then called to create the Object. The Object[] parameter is passed. The first parameter is Context and the second parameter is AttributeSet. This corresponds to the View(Context Context, AttributeSet Attrs) constructor. And that’s the same thing here. If a custom View just implements the Context constructor, you can create it dynamically in your code, but you can’t use it in XML.

Another point here is that reflection does not change the access check, so you need to set the CLASS to public in JAVA (the default in Kotlin is public=_=).

public final View createView(NonNull Context viewContext, NonNull String name,
                             @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {... Constructor<? extends View> constructor = sConstructorMap.get(name);if(constructor ! =null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;

    try {
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add itclazz = Class.forName(prefix ! =null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        }
      
        Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = viewContext;
        Object[] args = mConstructorArgs;
        args[1] = attrs;
      
        try {
            final View view = constructor.newInstance(args);

            return view;
        } finally {
            mConstructorArgs[0] = lastContext; }}... }Copy the code

1.5. parseInclude

As you’ve seen, special treatment is done for include tags.

The logic and inflate are similar. Once the layout ID has been read, tryInflatePrecompiled() is called. This method was introduced earlier just for testing purposes.

Depending on whether or not the merge label is used, you may go a different branch into rInflate(), entering the create process.

private void parseInclude(XmlPullParser parser, Context context, View parent, AttributeSet attrs) throws XmlPullParserException, IOException {
    int type;
    
    int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0); .final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
            (ViewGroup) parent, /*attachToRoot=*/true);
    if (precompiled == null) {
        final XmlResourceParser childParser = context.getResources().getLayout(layout);

        try{...if (TAG_MERGE.equals(childName)) {
                
                rInflate(childParser, parent, context, childAttrs, false);
            } else {
                final View view = createViewFromTag(parent, childName,
                        context, childAttrs, hasThemeOverride);
                final ViewGroup group = (ViewGroup) parent;
                // Inflate all children.
                rInflateChildren(childParser, view, childAttrs, true); . group.addView(view); }}finally{ childParser.close(); }}}Copy the code

2. Inflate

  1. Obtain it from the system service

    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    Copy the code
  2. Get from

    Layoutinflaters are obtained by static methods, and also by system services.

    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;
    }
    Copy the code
  3. Get from the activity

    The activity is obtained from PhoneWindow, which is also obtained from PhoneWindow.

    public LayoutInflater getLayoutInflater(a) {
        return getWindow().getLayoutInflater();
    }
    
    public class PhoneWindow extends Window implements MenuBuilder.Callback {
        private LayoutInflater mLayoutInflater;
    
        public PhoneWindow(Context context) {
            super(context);
            mLayoutInflater = LayoutInflater.from(context);
        }
    
        public LayoutInflater getLayoutInflater(a) {
            returnmLayoutInflater; }}Copy the code
  4. Get it from View

    The View has the inflate method, and you get a LayoutInflater from.

    public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }
    Copy the code

If you think this article is good, please support it with a like.