SoFunction
Updated on 2025-04-07

Detailed explanation of Background in Android loading View

For most Android developers, the most frequent operation is to layout the interface, and the loading of background images in the View is the most frequently done. However, we rarely pay attention to this process. This article mainly analyzes the process of loading background images in the view. Understanding the loading of background images in the view (resource loading) allows us to optimize the resource loading process, and in addition, when skinning the entire application is required, we can also be more handy.

The most common thing for loading View images is to set drawable in XML files, and then let the Android system help us complete it, or manually write code to load it into Bitmap, and then load it on the View. This article mainly analyzes when and how Android can help us load background images, so we will start with the (...) method.

Whether the View is initialized from (...) or (...) methods, it will eventually arrive in the method (XmlPullParser parser, ViewGroup root, boolean attachToRoot). Here we mainly focus on the background image loading of View, and let it go as far as how XML is parsed and loaded.

Copy the codeThe code is as follows:

    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final AttributeSet attrs = (parser);
            Context lastContext = (Context)mConstructorArgs[0];
            mConstructorArgs[0] = mContext;
            View result = root;
            try {
                // Look for the root node.
                int type;
                while ((type = ()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(()
                            + ": No start tag found!");
                }
                final String name = ();
                if (DEBUG) {
                    ("**************************");
                    ("Creating root view: "
                            + name);
                    ("**************************");
                }
                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");
                    }
                    rInflate(parser, root, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    View temp;
                    if (TAG_1995.equals(name)) {
                        temp = new BlinkLayout(mContext, attrs);
                    } else {
                        temp = createViewFromTag(root, name, attrs);
                    }
                    params = null;
                    if (root != null) {
                        if (DEBUG) {
                            ("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = (attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            (params);
                        }
                    }
                    if (DEBUG) {
                        ("-----> start inflating children");
                    }
                     // Inflate all children under temp
                    rInflate(parser, temp, attrs, true);
                    if (DEBUG) {
                        ("-----> done inflating children");
                    }
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        (temp, params);
                    }
                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            } catch (XmlPullParserException e) {
                InflateException ex = new InflateException(());
                (e);
                throw ex;
            } catch (IOException e) {
                InflateException ex = new InflateException(
                        ()
                        + ": " + ());
                (e);
                throw ex;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }
            return result;
        }
    }
The above string of code is actually very clear in the idea. It is to parse the XML file, then initialize the View according to each node parsed out of the XML, then set the View's Layout parameter to the View, and then add the View to its parent control.
To understand how the View is loaded, we only need to understand
 temp = createViewFromTag(root, name, attrs);
Follow in and take a look.
    /*
     * default visibility so the BridgeInflater can override it.
     */
    View createViewFromTag(View parent, String name, AttributeSet attrs) {
        if (("view")) {
            name = (null, "class");
        }
        if (DEBUG) ("******** Creating view: " + name);
        try {
            View view;
            if (mFactory2 != null) view = (parent, name, mContext, attrs);
            else if (mFactory != null) view = (name, mContext, attrs);
            else view = null;
            if (view == null && mPrivateFactory != null) {
                view = (parent, name, mContext, attrs);
            }
            if (view == null) {
                if (-1 == ('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            }
            if (DEBUG) ("Created view is: " + view);
            return view;
        } catch (InflateException e) {
            throw e;
        } catch (ClassNotFoundException e) {
            InflateException ie = new InflateException(()
                    + ": Error inflating class " + name);
            (e);
            throw ie;
        } catch (Exception e) {
            InflateException ie = new InflateException(()
                    + ": Error inflating class " + name);
            (e);
            throw ie;
        }
    }

The focus of the above code is on the contents of try...Catch. The thing that the try package is to initialize the View. I noticed that there are several Factory in the above code, which can be initialized in the View, which means that we can actually interfere with the initialization of the View here. From the above code, we can know that if we customize a Factory, the View currently to be initialized will be initialized by our custom Factory first, rather than through the system's default Factory. So if we want to customize the Factory, where should we define it? It is easy to think that Factory must be customized before resource loading, so we should set it before onCreate(...).

  getLayoutInflater().setFactory(factory);
Next we see the above function

Copy the codeThe code is as follows:

  if (-1 == ('.')) {
        view = onCreateView(parent, name, attrs);
    } else {
        view = createView(name, null, attrs);
    }

This function initializes the View. There are two situations. One is the View that comes with the system.

 if (-1 == ('.'))
Initialization is performed here, because if it is a view that comes with the system, it is generally not equipped with the system's prefix "." Another branch initializes our custom View. Let's follow onCreateView to see.

Copy the codeThe code is as follows:

  protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, ".", attrs);
    }
    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = (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 it
                clazz = ().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass();
                if (mFilter != null && clazz != null) {
                    boolean allowed = (clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = (mConstructorSignature);
                (name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = (name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        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);
                    }
                }
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;
            final View view = (args);
            if (view instanceof ViewStub) {
                // always use ourselves when inflating ViewStub later
                final ViewStub viewStub = (ViewStub) view;
                (this);
            }
            return view;
        } catch (NoSuchMethodException e) {
            InflateException ie = new InflateException(()
                    + ": Error inflating class "
                    + (prefix != null ? (prefix + name) : name));
            (e);
            throw ie;
        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            InflateException ie = new InflateException(()
                    + ": Class is not a View "
                    + (prefix != null ? (prefix + name) : name));
            (e);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            InflateException ie = new InflateException(()
                    + ": Error inflating class "
                    + (clazz == null ? "<unknown>" : ()));
            (e);
            throw ie;
        }
    }

From onCreateView(...), we know that in fact, the initialization of the View in createViewFromTag(...) is ultimately initialized through the createView(...) function. The only difference is that the system control needs to be prefixed by onCreateView(...) so that the class loader (ClassLoader) can correctly initialize the class through the package where the class is located. The idea of ​​the createView(...) function is very clear. If you don't look at the content in the catch, the two branches in the try are used to extract the class constructors you want to use. The Android system will cache the used class constructors, because commonly used controls such as TextView may be used many times. Next, the View is initialized through the class constructor. We noticed that the mConstructorArgs passed in the constructor is an array containing two elements.

 final Object[] mConstructorArgs = new Object[2];
Then we are very clear that it is to call the constructor corresponding to two parameters in the system control. For convenience, we analyze from the most basic View.

Copy the codeThe code is as follows:

  public View(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public View(Context context, AttributeSet attrs, int defStyle) {
     this(context);
     TypedArray a = (attrs, ,
             defStyle, 0);
     Drawable background = null;
     int leftPadding = -1;
     int topPadding = -1;
     int rightPadding = -1;
     int bottomPadding = -1;
     int startPadding = UNDEFINED_PADDING;
     int endPadding = UNDEFINED_PADDING;
     int padding = -1;
     int viewFlagValues = 0;
     int viewFlagMasks = 0;
     boolean setScrollContainer = false;
     int x = 0;
     int y = 0;
     float tx = 0;
     float ty = 0;
     float rotation = 0;
     float rotationX = 0;
     float rotationY = 0;
     float sx = 1f;
     float sy = 1f;
     boolean transformSet = false;
     int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
     int overScrollMode = mOverScrollMode;
     boolean initializeScrollbars = false;
     boolean leftPaddingDefined = false;
     boolean rightPaddingDefined = false;
     boolean startPaddingDefined = false;
     boolean endPaddingDefined = false;
     final int targetSdkVersion = ().targetSdkVersion;
     final int N = ();
      for (int i = 0; i < N; i++) {
          int attr = (i);
          switch (attr) {
              case .View_background:
                  background = (attr);
                  break;
              case .View_padding:
                  padding = (attr, -1);
                  mUserPaddingLeftInitial = padding;
                  mUserPaddingRightInitial = padding;
                  leftPaddingDefined = true;
                  rightPaddingDefined = true;
                  break;
//Omit a large number of irrelevant functions
 }

Since we only focus on how the background image is loaded in the View, note that this function is actually traversing the AttributeSet attrs thing and then initializing the various properties of the View. We go directly

 background = (attr);
Let’s take a look at ().

Copy the codeThe code is as follows:

    public Drawable getDrawable(int index) {
        final TypedValue value = mValue;
        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
            if (false) {
                ("******************************************************************");
                ("Got drawable resource: type="
                                   +
                                   + " str=" +
                                   + " int=0x" + ()
                                   + " cookie=" + );
                ("******************************************************************");
            }
            return (value, );
        }
        return null;
    }

We found it called (...), go in and take a look.

Copy the codeThe code is as follows:

    /*package*/ Drawable loadDrawable(TypedValue value, int id)
            throws NotFoundException {
        if (TRACE_FOR_PRELOAD) {
            // Log only framework resources
            if ((id >>> 24) == 0x1) {
                final String name = getResourceName(id);
                if (name != null) ("PreloadDrawable", name);
            }
        }
        boolean isColorDrawable = false;
        if ( >= TypedValue.TYPE_FIRST_COLOR_INT &&
                <= TypedValue.TYPE_LAST_COLOR_INT) {
            isColorDrawable = true;
        }
        final long key = isColorDrawable ? :
                (((long) ) << 32) | ;
        Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);
        if (dr != null) {
            return dr;
        }
        cs = isColorDrawable
                ? (key)
                : (sPreloadedDensity ==
                        ? (key) : null);
        if (cs != null) {
            dr = (this);
        } else {
            if (isColorDrawable) {
                dr = new ColorDrawable();
            }
            if (dr == null) {
                if ( == null) {
                    throw new NotFoundException(
                            "Resource is not a Drawable (color or path): " + value);
                }
                String file = ();
                if (TRACE_FOR_MISS_PRELOAD) {
                    // Log only framework resources
                    if ((id >>> 24) == 0x1) {
                        final String name = getResourceName(id);
                        if (name != null) (TAG, "Loading framework drawable #"
                                + (id) + ": " + name
                                + " at " + file);
                    }
                }
                if (DEBUG_LOAD) (TAG, "Loading drawable for cookie "
                        + + ": " + file);
                if ((".xml")) {
                    try {
                        XmlResourceParser rp = loadXmlResourceParser(
                                file, id, , "drawable");
                        dr = (this, rp);
                        ();
                    } catch (Exception e) {
                        NotFoundException rnf = new NotFoundException(
                            "File " + file + " from drawable resource ID #0x"
                            + (id));
                        (e);
                        throw rnf;
                    }
                } else {
                    try {
                        InputStream is = (
                                , file, AssetManager.ACCESS_STREAMING);
        //                ("Opened file " + file + ": " + is);
                        dr = (this, value, is,
                                file, null);
                        ();
        //                ("Created stream: " + dr);
                    } catch (Exception e) {
                        NotFoundException rnf = new NotFoundException(
                            "File " + file + " from drawable resource ID #0x"
                            + (id));
                        (e);
                        throw rnf;
                    }
                }
            }
        }
        if (dr != null) {
            ();
            cs = ();
            if (cs != null) {
                if (mPreloading) {
                    if (verifyPreloadConfig(value, "drawable")) {
                        if (isColorDrawable) {
                            (key, cs);
                        } else {
                            (key, cs);
                        }
                    }
                } else {
                    synchronized (mTmpValue) {
                        //(TAG, "Saving cached drawable @ #" +
                        //        (())
                        //        + " in " + this + ": " + cs);
                        if (isColorDrawable) {
                            (key, new WeakReference<>(cs));
                        } else {
                            (key, new WeakReference<>(cs));
                        }
                    }
                }
            }
        }
        return dr;
    }

It's this function, and all the backgrounds of the View are loaded here. The logic of this function is more complicated. Generally speaking, it is based on the type of background (pure color, defined in XML file, or a static background). If there is one in the cache, use it directly in the cache.

To sum up, after the above analysis, we know that Android loads resource files for us in (...). If it is precisely to specific functions, the loading of resource files is loaded in the constructor of each initialized View.

The above is the entire content of this article, and I hope it will be helpful to everyone.