One of the methods we use a lot in our everyday battles with Android is findViewById(). It lets us find Views from layouts written in XML and returns a reference to their Java objects. And yet we know very little about how this thing works. How do layouts hierarchies written in XML get automatically converted to Java objects and delivered back to us in our Activities?

To understand this, we need to look at Android as a pure Java framework. For anything written in XML, the framework spends extra effort converting that into Java. Layouts are the best example of this and the class responsible for “inflating” them is LayoutInflater, which is precisely what we’re going to explore in this post.

Example usage of LayoutInflater in Fragment#onCreate()

Usages

The entry point to LayoutInflater is its inflate() method and we use it in a lot of places:

  • Fragments,for inflating their layouts.
  • Adapter backed Views,for inflating the layout of each item in a RecyclerViewSpinner, etc.
  • Activities: This is not obvious, but every call to setContentView()internally gets routed to LayoutInflater. The source for this can be seen in PhoneWindow (or AppCompatDelegateImplV7 when extending AppCompatActivity).

Working

LayoutInflater begins with the root ViewGroup of a layout and recursively starts inflating its child Views, forming a tree of View objects. For each View found in the layout hierarchy, it instantiates its equivalent Java object in three steps:

1. Parsing XML using XmlPullParser

The first step involves parsing the View name (e.g., TextView) and its attributes from the XML layout file. This is done using the help of a class known as XmlPullParser, which is an XML parser (just like Moshi for JSON).

2. Constructing attributes using AttributeSet

After reading a View from XML, the second step involves understanding its plain values read by XmlPullParser and constructing a data-set out of them. This includes:

  • Understanding the unit of a value like “24dp”.
  • Following the reference to an external layout in <include layout=@layout/include_toolbar" />.
  • Resolving resource identifiers. That is, figuring out if
    @drawable-v21/button_background.xml should be used over @drawable/button_background.xml depending on the API level.
  • Resolving duplicate attributes from different sources. This usually happens when the same property is defined inline on a View as well as inside a style applied on it.

3. Instantiating View object using reflection

After extracting data in the first two steps, herein lies the most interesting part of LayoutInflater. Using the View’s name and attributes, LayoutInflater attempts to instantiate its Java object in a trial and error manner using reflection.

For those unaware, reflection is like black magic. It lets you examine the type system of a class at runtime, making it possible to access & modify members of a class (fields and methods) that are otherwise private or inaccessible, including invoking the constructors of the class:

Class<?> cl = Class.forName("com.example.SomeClass");
Constructor<?> cons = cl.getConstructor(Param1.class, Param2.class);

LayoutInflater uses this same magic for instantiating the View’s object by passing its fully qualified name to Context#getClassLoader()#loadClass().

For extracting the fully qualified name, LayoutInflater categorizes Views into custom Views and framework Views by using a hack: searching for dots in the name. If dots are found, the View is categorized as a custom View and a framework view otherwise. Beautiful!

LayoutInflater.java, line #748.

a) Framework Views

For framework Views like TextViewImageView, etc., which do not require their fully qualified names in XML, LayoutInflater attempts to guess the package name in a trial and error manner:

PhoneLayoutInflater.java

String[] potentialPackageNames = {
    "android.widget.",
    "android.webkit.",
    "android.app."
};
for (String packageName : potentialPackageNames) {
    try {
        View view = createView(packageName, viewName);
        if (view != null) {
            return view;
        }
    } catch (ClassNotFoundException e) {
    }
}

In each iteration, the createView() method attempts to invoke the constructor:

TextView textView = (TextView) context
        .getClassLoader()
        .loadClass(packageName + ".TextView")
        .getConstructor()
        .newInstance();

b) Custom Views

On the other hand, for custom Views it is impossible for LayoutInflater to guess the location of their sources. That is exactly why Android requires their fully qualified names in XML layouts, which LayoutInflater can directly use for invoking their constructors:

RecyclerView recyclerView = (RecyclerView) getContext()
        .getClassLoader()
        .loadClass("android.support.v7.widget")
        .getConstructor()
        .newInstance();

In case the loadClass() call results in a ClassNotFoundException because an invalid View was found in our XML layout, LayoutInflater re-throws it as an “Error inflating class” exception.

And that’s it.

With each inflation, LayoutInflater links the instantiated View to its parent ViewGroup and its children Views, essentially creating a tree of our View hierarchy. The View then simply traverses these links every time findViewById() gets called.

The use of reflection by LayoutInflater makes it an expensive and slow process, which is one of the reasons we should avoid complex View hierarchies. Not doing so directly affects the startup time for Activities, Fragments or any adapter backed ViewGroup.

There’s another interesting step involved in the inflation of layouts that I decided not to include in this article because of its complexity: Layout Factories. They are classes that can hook into the inflation process for generating their own Views and are used by AppCompat (for backporting material design to API levels below Lollipop) and Calligraphy (for applying custom fonts without using custom Views). Look at the Calligraphy source for more details on how layout factories are used.

Read more