Series index

Android startup process

  1. Source code download and compilation
  2. Overview of the Android system startup process
  3. Init process source parsing
  4. Zygote process source code analysis
  5. SystemServer source code parsing

LayoutInflater source code details

update

【Android/ source code/interview 】LayoutInflater source code details

preface

In this article, we’ll look at LayoutInflater from a source code perspective. LayoutInflater instantiates an XML file as a view object

We’ll see that there are two major sources of time

  1. XmlResourseParser Traversal of XML
  2. Reflect the time taken to create the View object

These, in turn, are positively correlated with the complexity of Xml, and the more complex the Xml, the longer the recursive calls take, leading to what we call the caton problem

Overview of the overall process

Eggs: BlinkLayout

BlinkLayout is an inner class in LayoutInflater. It is itself a subclass of FrameLayout. If the current tag is TAG_1995, create a BlinkLayout that flashes every 0.5 seconds to hold the contents of the LayoutInflater

Source code comments are also very interesting, write Let’s Party like it’s 1995! “Is said to celebrate Easter 1995

LayoutInflater

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 newBlinkLayout(context, attrs); }...return view;
    }
Copy the code

It is also very simple to use

   <blink
        android:layout_below="@id/iv_qr_code"
        android:layout_centerHorizontal="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=Android Institute
            android:textColor="# 157686"
            android:textSize="55sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </blink>
Copy the code

The effect is as follows, which is also suitable for the blinking effect of the cursor in EditText

Scan the above TWO-DIMENSIONAL code to pay attention to the “Android Institute” public number, to obtain more learning materials!

Ps: Those who want to further study are concerned about it, but don’t hurry up and pay attention to a wave?

The creation of a LayoutInflater

An overview of

LayoutInflater is an abstract class that is not created by the App layer. Instead, it is created by calling static functions from() via LAYOUT_INFLATER_SERVICE, We ended up creating a subclass of LayoutInflater, PhoneLayoutInflater

Analysis of important functions

LayoutInflater.from(cxt)

This function is simple. It calls getSystemService() to get the corresponding system service based on the passed Context object and assigns the value to the LayoutInflater

public static LayoutInflater from(Context context) { 
        LayoutInflater LayoutInflater =  //LayoutInflate is a system service that ultimately returns' PhoneLahyoutInflater '
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
Copy the code

Context itself is an abstract class, and its actual instantiation object is ContextImpl. In this class’s getSystemService() function, the class that actually performs the retrieval of system services is SystemServiceRegistry, A ServiceFetcher is encapsulated to fetch the actual system services. All system services are stored in a map called SYSTEM_SERVICE_FETCHERS. This is actually a get method that fetches the corresponding system services from the map

LayoutInflater

@Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

Copy the code

SystemServiceRegistry

/** * Gets a system service from a given context. */
    public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<? > fetcher = SYSTEM_SERVICE_FETCHERS.get(name);returnfetcher ! =null ? fetcher.getService(ctx) : null;
    }
Copy the code

The addition of the corresponding service, which calls the SYSTEM_SERVICE_FETCHERS put function, is done by registerService()

/** * Statically registers a system service with the context. * This method must be called during static initialization only. */
    private static <T> void registerService(String serviceName, Class
       
         serviceClass, ServiceFetcher
        
          serviceFetcher)
        
        {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }
Copy the code

The SystemServiceRegistry class contains a static block of code that registers all services. We only care about how LAYOUT_INFLATER_SERVICE is registered

static{... registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return newPhoneLayoutInflater(ctx.getOuterContext()); }}); . }Copy the code

As we mentioned earlier, this is where the PhoneLayoutInflater is finally created and returned, and the LayoutInflater creation process is finished

thinking

Why leave it to a system service instead of just creating an instance of PhoneLayoutInflater?

Instantiation of LayoutInflater layouts

The whole process

Call layoutInflater’s inflater() function and pass in the resId parameter of XML

Analysis of important functions

inflate

This function is our entry point to instantiate the Xml layout file as a View object

LayoutInflater

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) + ")");
        }

        View view = tryInflatePrecompiled(resource, res, root, attachToRoot); // This code must return null, because the current version of the precompiled Enable is false
        if(view ! =null) {
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource); // Get the xmlblock. Parser object
        try {
            return inflate(parser, root, attachToRoot); 
        } finally{ parser.close(); }}Copy the code

The inflate(Parser, root, attachToRoot) function is called here to parse the Xml layout

Here you see some familiar tags, such as include,merge, handled, see the source code and comments below for details

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {//XmlPullParser is an interface
		// This function does the actual parsing of the XML into a view. Parser is the Parser object fetched based on the XML layout
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final inflateAttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root; // The view object to return

            try {
                advanceToRootNode(parser); // Determine and process START_TAG and END_TAG
                final String name = parser.getName();  // Get the current label

                if (DEBUG) {
                    System.out.println("* * * * * * * * * * * * * * * * * * * * * * * * * *");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("* * * * * * * * * * * * * * * * * * * * * * * * * *");
                }

                if (TAG_MERGE.equals(name)) { // If you use the merge tag
                    if (root == null| |! attachToRoot) {// To use merge tags, you must have a parent layout and rely on the parent layout to load, otherwise an exception will be thrown
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);// Recursive generates layout views
                } else { // If the merge tag is not used, TMP is created as a temporary root node and is eventually assigned to result
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs); //// Creates a view based on the label name

                    ViewGroup.LayoutParams params = null;

                    if(root ! =null) {  // If rootView is not empty
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);  // Generate layoutParams based on rootView
                        if(! attachToRoot) {// If attachToRoot is false
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);  // Set a temporary params}}if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

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

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null| |! attachToRoot) {// If root is empty and attachToRoot is false
                        result = temp; // Assign temp, the View at the root, to result}}}...return result;  // Return the result}}Copy the code

rInflate

As you can also see from the code above, either the merge tag or the non-merge tag ends up calling the rInflate() function, which is used to recursively walk down the XML layout, and the createViewFromTag() function is called to reflect the View object

See the source code and comments below for details

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)) { // If you need the REQUEST_FOCUS tag
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) { // If it is a "tag" tag
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) { // If it is an Include tag
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs); // Parse the include tag
            } else if (TAG_MERGE.equals(name)) { // Merge tag
                throw new InflateException("<merge /> must be the root element"); // Throw an exception directly
            } else { // Other tags
                final View view = createViewFromTag(parent, name, context, attrs); // Create a view based on the Tag and reflection
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true); // Call the rInflate function recursivelyviewGroup.addView(view, params); }}if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

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

createViewFromTag()

Finally, we get to the highlight, which is the function that actually creates the View based on the parsed Tag

LayoutInflater

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null."class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        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); // Try using Factory to create View objects

            if (view == null) { // If tryCreateView returns null
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
					/ / sample: com. Aiwinn. Base. Widget. CameraSurfaceView
                    if (-1 == name.indexOf('. ')) {  // If the current Tag contains ".
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs); }}finally {
                    mConstructorArgs[0] = lastContext; }}returnview; }... }Copy the code

In this function, tryCreateView() is first called to retrieve the View object, and if null, createView() is further called to create the View object

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

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            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);// Merge prefix and name to get the corresponding Class object

                if(mFilter ! =null&& clazz ! =null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if(! allowed) { failNotAllowed(name, prefix, viewContext, attrs); } } constructor = clazz.getConstructor(mConstructorSignature);// Get the constructor object
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else{... } Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = viewContext;
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            try {
                final View view = constructor.newInstance(args); // Create an instantiation object of the View based on the obtained constructor
                if (view instanceof ViewStub) {
                    // Use the same context when inflating ViewStub later.
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                return view;
            } finally {
                mConstructorArgs[0] = lastContext;
            }catch{... }}}Copy the code

The code here is actually quite time-consuming because it is created using reflection, which is generally 3 times slower than creating objects directly, and iReader’s X2C framework is optimized for this

snacks

Creation and acquisition of Resources

The Resources object, mResources, is created by createResources(), and the ResourcesManager class gets the Resources

ContextImpl

private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
            int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
        final String[] splitResDirs;
        final ClassLoader classLoader;
        try {
            splitResDirs = pi.getSplitPaths(splitName);
            classLoader = pi.getSplitClassLoader(splitName);
        } catch (NameNotFoundException e) {
            throw new RuntimeException(e);
        }
        return ResourcesManager.getInstance().getResources(activityToken,
                pi.getResDir(),
                splitResDirs,
                pi.getOverlayDirs(),
                pi.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfig,
                compatInfo,
                classLoader);
    }
Copy the code

ResourcesManager

public @Nullable Resources getResources(@Nullable IBinder activityToken,
            @Nullable String resDir,
            @Nullable String[] splitResDirs,
            @Nullable String[] overlayDirs,
            @Nullable String[] libDirs,
            int displayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
            final ResourcesKey key = newResourcesKey( resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfig ! =null ? new Configuration(overrideConfig) : null.// CopycompatInfo); classLoader = classLoader ! =null ? classLoader : ClassLoader.getSystemClassLoader();
            return getOrCreateResources(activityToken, key, classLoader);
        } finally{ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); }}Copy the code

ContextImpl

@Override
    public Resources getResources(a) {
        return mResources;
    }
Copy the code

From this we can also infer that LayoutInflaters are created by services because they need to obtain certain resources that system services can obtain

XmlBlock

The inflate() function is also involved in an important class, XmlResourceParser, which is responsible for iterating through XML tags, and whose real implementation class is the internal XmlBlock class xmlBlock. Parser, The functions that actually perform XML traversal are implemented by XmlBlock. For efficiency, these functions are implemented by JNI calling native functions at android_util_xmlblock-cpp

XmlBlock.java

@FastNative
    /*package*/ static final native int nativeNext(long state);
    @FastNative
    private static final native int nativeGetNamespace(long state);
    @FastNative
    /*package*/ static final native int nativeGetName(long state);
    @FastNative
    private static final native int nativeGetText(long state);
    @FastNative
    private static final native int nativeGetLineNumber(long state); .Copy the code

“android_util_XmlBlock.cpp`

static jint android_content_XmlBlock_nativeNext(JNIEnv* env, jobject clazz, jlong token)
{
    ResXMLParser* st = reinterpret_cast<ResXMLParser*>(token);
    if (st == NULL) {
        return ResXMLParser::END_DOCUMENT;
    }

    do {
        ResXMLParser::event_code_t code = st->next();
        switch (code) {
            case ResXMLParser::START_TAG:
                return 2;
            case ResXMLParser::END_TAG:
                return 3;
            case ResXMLParser::TEXT:
                return 4;
            case ResXMLParser::START_DOCUMENT:
                return 0;
            case ResXMLParser::END_DOCUMENT:
                return 1;
            case ResXMLParser::BAD_DOCUMENT:
                goto bad;
            default:
                break; }}while (true);

bad:
    jniThrowException(env, "org/xmlpull/v1/XmlPullParserException"."Corrupt XML binary file");
    return ResXMLParser::BAD_DOCUMENT;
}
Copy the code

tryInflatePrecompiled

This function is a new function added to Android10’s source code. It is an optimization to use reflection to generate a View from the dex generated by XML precompilation to reduce the time it takes the XmlPullParser to parse THE XML. Reflection retrieves the View directly to the pre-compiled View object without recursively calling the rInflate

This is basically a real and complete solution to the complex layout caused by the lag problem

View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
        boolean attachToRoot) {
        if(! mUseCompiledView) {return null;
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");

        // Try to inflate using a precompiled layout.
        String pkg = res.getResourcePackageName(resource);
        String layout = res.getResourceEntryName(resource);
		
		// View objects are reflected from the mPrecompiledClassLoader
        try {
            Class clazz = Class.forName("" + pkg + ".CompiledView".false, mPrecompiledClassLoader); // Get the Class of the precompiled view object
            Method inflater = clazz.getMethod(layout, Context.class, int.class);
            View view = (View) inflater.invoke(null, mContext, resource);

            if(view ! =null&& root ! =null) {
                // We were able to use the precompiled inflater, but now we need to do some work to
                // attach the view to the root correctly.
                XmlResourceParser parser = res.getLayout(resource);
                try {
                    AttributeSet attrs = Xml.asAttributeSet(parser);
                    advanceToRootNode(parser);
                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);

                    if (attachToRoot) {
                        root.addView(view, params);
                    } else{ view.setLayoutParams(params); }}finally{ parser.close(); }}return view;
        } catch (Throwable e) {
            if (DEBUG) {
                Log.e(TAG, "Failed to use precompiled view", e); }}finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        return null;
    }
Copy the code

Write in the last

In the next article, we’ll come up with some optimizations to solve (or slow down) the lag caused by complex layouts. Stay tuned!

Reference article:

1. https://www.reddit.com/r/androiddev/comments/3sekn8/lets_party_like_its_1995_from_the_layoutinflater/
2. https://www.cnblogs.com/liyilin-jack/p/10282385.html
3. https://blog.csdn.net/axi295309066/article/details/60128009
4. https://github.com/RTFSC-Android/RTFSC/blob/master/BlinkLayout.md
5. https://juejin.cn/post/6844903785219751944
Copy the code

Solemnly declare

The copyright of this article belongs to Android Institute. It is prohibited to be reproduced without permission.