Layoutinflator analysis in Android

PS: This article is a reprint of the article. It will be more readable to read the original text. There is a link to the original text at the end of the article

ps: This article is based on Android Api 26

catalogue

1. Layoutinflator create View process

 1,1 LayoutInflater of rInflate(The method has five parameters) method analysis

 1,2 LayoutInflater of parseInclude(The method has four parameters) method analysis

 1,3 LayoutInflater of createViewFromTag Method analysis


1. Layoutinflator create View process

1. 1. Analysis of the layout inflate method (this method has 5 parameters)

Let's continue the analysis of layoutinflator in Android (I). Let's go back to the expand of layoutinflator (xmlpullparser parser, @ nullable ViewGroup root, Boolean attachtoroot);

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {

    synchronized (mConstructorArgs) {
        ......
        try {
            ......
            if (TAG_MERGE.equals(name)) {
                ......
                //18,
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
                //19,
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                ......
                // Inflate all children under temp against its context.
                //23,
                rInflateChildren(parser, temp, attrs, true);
                ......
            }

        } catch (XmlPullParserException e) {
            ......
        } catch (Exception e) {
            ......
        } finally {
            ......
        }

        return result;
    }

}

Remember what the layoutinflator analysis (I) in Android said in the previous article? The code of annotation 18 and the code of annotation 23 are essentially parsing sub tags, but the parameters are different. They all call the rinfrate (xmlpullparser, parser, view parent, context, attributeset, attributers, Boolean finishinfrate) method of layoutingflate. The second parameter root of annotation 18 method is different from the second parameter template of annotation 23 method, When the start tag of the xml file to be parsed is merge, the root tag is root; when the start tag of the xml file to be parsed is not merge, the root tag is temp; Look, the fifth parameter root of the annotation 18 method is different from the fourth parameter temp of the annotation 23 method. When it is true, it means to callback the onfinishtemplate method of the parent element (the parent element is ViewGroup or inherited from ViewGroup), with root as one of the parameters, It was created long before the expand (xmlpullparser, parser, @ nullable ViewGroup root, Boolean attachtoroot) method of layouteinflator was called, so the onfinishexpand method of root was called long ago, so the fifth parameter of the method in note 18 is false, The template was created during the call of the template (xmlpullparser, parser, @ nullable ViewGroup root, Boolean attachtoroot) method of layoutinflator. The template has just been created, so the onfinishtemplate method of the template has not been called, so the fourth parameter of the annotation 23 method is true.

Let's take a look at the rinfflate (xmlpullparser, parser, view parent, context, attributeset, attrs, Boolean finishinfflate) method of layoutinflator;

void rInflate(XmlPullParser parser, View parent, Context context,

              AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    ......
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
        ......
        if (TAG_REQUEST_FOCUS.equals(name)) {
            ......
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
            
            //30,
        } else if (TAG_INCLUDE.equals(name)) {
            
            //31,
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            
            //32,
            parseInclude(parser, context, parent, attrs);
            
            //33,
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            ......
        }
    }
    ......
    //34,
    if (finishInflate) {
        parent.onFinishInflate();
    }

}

Look at the code in note 30 and judge whether the parsed tag is include; The code of note 31 determines whether the depth of the parsed current tag is 0, that is, whether the current tag has a parent tag. If the depth is 0, it means that there is no parent tag, then an exception will be thrown, because the include tag cannot be used as the root tag of an xml file, which is well known; The code of note 32 is a specific method to parse the include tag, which will be discussed later; As we know, the method of R inflate (xmlpullparser, parser, view parent, context, at distributeset attrs, Boolean finishincrease) of layouter is used to parse sub tags. Look at the code of comment 33. If the sub tag is merge, an exception will be thrown; Look at the code in note 34. If finishInflate is true, it will call the onfinishflate method of ViewGroup (as long as it is the parent tag, it must be ViewGroup. There is no doubt about this). It is mentioned above in this article.

1. 2. Analysis of the parseInclude method of layoutinflator (this method has 4 parameters)

Let's look back at the code of comment 32, that is, the parseinclude (xmlpullparser, parser, context, view parent, attributeset, attrs) method of layoutingflate;

private void parseInclude(XmlPullParser parser, Context context, View parent,

                          AttributeSet attrs) throws XmlPullParserException, IOException {
    int type;

    if (parent instanceof ViewGroup) {
        // Apply a theme wrapper, if requested. This is sort of a weird
        // edge case, since developers think the <include> overwrites
        // values in the AttributeSet of the included View. So, if the
        // included View has a theme attribute, we'll need to ignore it.
        //35,
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        ......
        // If the layout is pointing to a theme attribute, we have to
        // massage the value to get a resource identifier out of it.
        //36,
        int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
        ......
        //37,
        if (layout == 0) {
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            throw new InflateException("You must specify a valid layout "
                    + "reference. The layout ID " + value + " is not valid.");
        } else {
            
            //38,
            final XmlResourceParser childParser = context.getResources().getLayout(layout);

            try {
                ......
                //39,
                if (TAG_MERGE.equals(childName)) {
                    // The <merge> tag doesn't support android:theme, so
                    // nothing special to do here.
                    rInflate(childParser, parent, context, childAttrs, false);
                } else {
                    final View view = createViewFromTag(parent, childName,
                            context, childAttrs, hasThemeOverride);
                    ......
                }
            } finally {
                childParser.close();
            }
        }
    } else {
        throw new InflateException("<include /> can only be used inside of a ViewGroup");
    }

    LayoutInflater.consumeChildElements(parser);

}

The code in note 35 indicates that the thme attribute of include is extracted. If the them attribute is set, the theme set by the View of the include package is invalid; The code of note 36 indicates that the id of a sub layout xml file is obtained through the layout attribute of the include tag; The code of note 37, if the id of layout is 0, it proves that the layout attribute is not set for the include tag, then an exception will be thrown; The code of comment 38 indicates that an XmlResourceParser object is created through the id of the sub layout xml; Look at the code in note 39. If the root tag of the sub layout xml file is merge, then call the rinfflate (xmlpullparser, parser, View parent, context, attributeset, attrs, Boolean finishinfflate) method of layoutingflate, In this way, the label can be continuously parsed through the R inflate (xmlpullparser, parser, View parent, context, attributeset, attrs, Boolean finishincrease) method of layouteinflator.

1. 3. Analysis of createViewFromTag method of layoutinflator

Well, the process of parsing tags is described above. Here, let's analyze the creation process of View and look back to the code in note 19, that is, the createviewfromtag (View parent, string name, context, attributeset, attributers) method of layoutingflate;

picture

Look at the code in note 40. The createViewFromTag(View parent, String name, Context context, AttributeSet attrs) method of layoutingflate calls the createviewfromtag (view parent, string name, context, attributeset attrs, Boolean ignorethemeattr) method of layoutingflate;

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,

                       boolean ignoreThemeAttr) {
    ......
    try {
        View view;
        
        //41,
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
            
            //42,
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        
        //43,
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {
            ......
            try {
                if (-1 == name.indexOf('.')) {
                    
                    //44,
                    view = onCreateView(parent, name, attrs);
                } else {
                    
                    //45,
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        return view;
    } catch (InflateException e) {
      ......
    } catch (ClassNotFoundException e) {
      ......
    } catch (Exception e) {
      ......
    }

}

I won't analyze the code of notes 42, 43, 44 and 45. I analyze a class presenting the interface that directly inherits AppCompatActivity instead of directly inheriting Activity, so I will eventually follow the code of note 41. What is the implementation class of mFactory2 object? You can see the article of UI stealing of AppCompatActivity in Android, The UI of AppCompatActivity in Android is analyzed by AppCompatDelegateI-mplV9, while we use API 26 and AppCompatDelegateImplN. Isn't that different? In fact, the process of instantiating and assigning values to mFactory2 is the same. AppCompatDelegateImplN indirectly inherits from AppCompatDelegateImplV9, and appcompatdelegateimp ln does not override oncreateview (View parent, string name, context, attributeset attributes) method, Oncreateview (View parent, string name, context, attributeset, attrs) method is still implemented in AppCompatDelegateI-mplV9; Note that here I analyze the idea of creating a View with Activity as the context. Don't make a mistake. In order to prevent confusion, I list the code of creating a View with Activity as the context;

//46,
View view = LayoutInflater.from(activity).inflate(R.layout.item_1,null,false);

Layoutinflator in note 46 The from (Activity) code will eventually call the cloneInContext(Context newContext) method in the phonelayouteinflator object of the Activity. You can trace it in the article layouteinflator analysis (I) in Android, Then build a new phonelayouteinflator object through the cloneincontext (CO ntext newcontext) method of the phonelayouteinflator object of the Activity, and assign mFactory2 in the phonelayouteinflator object of the Activity to mFactory2 in the new phonelayouteinflator.

Let's look back at the code under comment 41 if statement, that is, the oncreateview (view parent, string name, context, attributeset, attrs) method of AppCompatDelegateImplV9;

picture

Look at the code in note 47. The onCreateView(Vie-w parent, String name, Context context, AttributeSet attrs) method of AppCompatDelegateImplV9 calls the createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs) method of AppCompatDelegateImplV9;

@Override
public View createView(View parent, final String name, @NonNull Context context,
                       @NonNull AttributeSet attrs) {
    ......
    //48,
    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 */
    );
}

Look at the code in note 48. Mappcompatviewinflator is an object of appcompatviewinfla ter type. Here, call the createview (view parent, final string name, @ nonnull context context, @ nonnull attributeset attributes, Boolean inheritcontext, Boolean readandroidtheme, Boolean readapptheme, Boolean wrapcontext) method of appcompatviewinflator;

public 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
    //49,
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
            break;
        ......
    }

    if (view == null && originalContext != context) {
        // If the original context does not equal our themed context, then we need to manually
        // inflate it using the name so that android:theme takes effect.
        //50,
        view = createViewFromTag(context, name, attrs);
    }
    ......
    return view;

}

Look at the code in note 49. If it is a system label, create a View object directly through the new keyword; If it is not a system tag, go to the code of comment 50, that is, the createviewfromtag (context, context, string name, attributeset, attrs) method of appco mpatviewinflator;

private View createViewFromTag(Context context, String name, AttributeSet attrs) {

    ......
    try {
        ......
        if (-1 == name.indexOf('.')) {
            for (int i = 0; i < sClassPrefixList.length; i++) {
                
                //51,
                final View view = createView(context, name, sClassPrefixList[i]);
                if (view != null) {
                    return view;
                }
            }
            return null;
        } else {
            
            //52,
            return createView(context, name, null);
        }
    } catch (Exception e) {
        ......
    } finally {
        ......
    }

}

The code in note 52 creates a View through a custom label. The third parameter null indicates that the label does not need to be prefixed, because the custom label originally has a prefix, so it does not need to be added; The code in note 51 indicates that the View is created through the system tag. When writing the xml layout, the system tag does not have a prefix, so the tag prefix should be added before creating the View. Let's take a look at the data stored in the prefix set classprefixlist of the commonly used system tag;

private static final String[] sClassPrefixList = {

        "android.widget.",
        "android.view.",
        "android.webkit."

};

The commonly used system tags are placed under the three packages of widget, view and webkit; Let's look at the codes of notes 51 and 52, that is, the createview (c-context context, string name, string prefix) method of appcompatviewinflator;

private View createView(Context context, String name, String prefix)

        throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);

    try {
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            Class<? extends View> clazz = context.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);

            constructor = clazz.getConstructor(sConstructorSignature);
            sConstructorMap.put(name, constructor);
        }
        constructor.setAccessible(true);
        return constructor.newInstance(mConstructorArgs);
    } catch (Exception e) {
        // We do not want to catch these, lets return null and let the actual LayoutInflater
        // try
        return null;
    }

}

The createview (context, context, string name, string prefix) method of appcompatviewinflator finally creates a View object through the reflection mechanism; If you write a non-existent tag (not a system tag or a custom tag) in the xml layout file, an empty View object will be returned.

Keywords: Java Android

Added by Thunderfunk on Mon, 31 Jan 2022 13:16:47 +0200