OkLayoutInflater

Anjal Saneen
OkCredit
Published in
5 min readOct 26, 2022

The loading of large XML layouts is one of the major performance bottlenecks in android. Loading XML layout into memory through IO operations and parsing views through reflection can be expensive. This is especially true when the XML file is too large or when initializing the layout view takes a while. We know that when the main thread performs some time-consuming operations, it may cause the page to freeze, and even more, serious ANR may occur. Androidx AsyncLayoutInflater helps to load those layouts asynchronously. However, we found that it has some limitations. The goal of this blog post is to explain how OkLayoutInflater addresses these limitations.

Source code analysis of AndroidX AsyncLayoutInflater

https://android.googlesource.com/platform/frameworks/support/+/89f7eba/v4/java/android/support/v4/view/AsyncLayoutInflater.java

Source code is short and easy to understand with only a few lines of code. In the inflate method, it creates an InflateRequest object and stores variables such as resid, parent, callback, etc. Then it calls enqueue to add the request to the InflateThread queue. The main purpose of InflateThread is to add requests to the blocking queue and perform BasicInflater.inflate operations in order. Regardless of inflating success or failure, the request message will be sent to the main thread for processing.

The BasicInflater Inherited from LayoutInflater. In OnCreateView these prefixes are loaded on the layout. At last, mHandlerCallback implements handleMessage which executes the operation on main thread, and there is a fallback mechanism, that is when the child thread inflates fails. it will continue to inflate on the main thread, and finally, gives a callback to the main thread through the OnInflateFinishedListener interface.

The limitations of AsyncLayoutInflater and how we improve it

  • Single thread to do all the inflate work
    AsyncLayoutInflater has this major limitation. This was the main reason for creating a custom implementation. One of our recycler view items took a long time to inflate. The Androidx AsyncLayoutInflater offloads from the main thread but runs sequentially in the background thread. Therefore, it does not help us save time. The implementation was changed to coroutine with default dispatch, which has the maximum parallelism equal to the number of CPU cores. As a result, the recycler view will load and scroll faster. Here are the systrace images 3 implementations mentioned above.
  • Inflatework is not lifecycle aware.
    The inflate work cannot be controlled once it has been scheduled with andriodx inflate. Inflating callbacks can result in crashes if the user closes the page before the work is complete. Coroutines allow us to control the inflation of work jobs and cancel the context of lifecycle events. Thanks to ItzNotABug for Auto Cancellation support.
  • Does not support LayoutInflater.Factory2.
    This inflater does not support setting a LayoutInflater.Factory nor LayoutInflater.Factory2. We are manually setting Factory2 with custom implementation
  • The default size limit of the cache queue is 10. If it exceeds 10, it will cause the main thread to wait.
    here you can see async inflate thread queue is using ArrayBlockingQueue with size is 10. If the job exceeds 10, the main thread will wait. It has been replaced by a coroutine.

OkLayoutInflater 🎉

We’ve seen the limitations of AsyncLayoutInflater above. This is an enhanced version of AsyncLayoutInflater with a coroutine.

How we use OkLayoutInflater in Fragments

private val okLayoutInflater by lazy { OkLayoutInflater(this) }

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val loadingView = inflater.inflate(R.layout.loader_view, container, false)
okLayoutInflater.inflate(contentLayoutId, container) { inflatedView ->
(loadingView as? ViewGroup)?.addView(inflatedView)
}
return loadingView
}

Inflating layout asynchronously has the caveat that view access can only be granted after inflate is complete. Therefore, we should avoid view access in onResume, onViewCreated, etc. We use the MVI architecture. The data flow on MVI will be unidirectional, and all view access will occur via a single render function and view events for handling side effects. During the inflate process, all render methods are cached until the view is fully inflated. As a result, we were able to implement async inflate seamlessly on all the critical screens and reduce frozen frames by ~30% on some screens. It was a huge return on our investment. The following diagram illustrates how it works on MVI.

How we use OkLayoutInflater in RecyclerView

private val okLayoutInflater by lazy { OkLayoutInflater(this) }

override fun onAttachedToWindow() {
super.onAttachedToWindow()
okLayoutInflater.inflate(R.layout.transaction_view, this) { inflatedView ->
removeAllViews()
addView(inflatedView, LayoutParams.MATCH_PARENT)
}
}

Again, the view access should be avoided until the async inflate is completed.

Results

RecyclerView

How Recyclerview loads before optimisation (The duration is in 90 percentile)
After OkLayoutInflater

Fragments/Activity

The opening of a fragment before optimisation (The duration is in 99 percentile)
After OkLayoutInflater

Final Thoughts

I hope you find our experience useful in helping to load the layout asynchronously and improve rendering performance. We are able to reduce our frozen frame metric by 23% by inflating the layout asynchronously. While using AndroidX, we faced numerous challenges, which we solved by implementing a custom implementation. We are open-sourcing the library for everyone. Feel free to open pull requests and issues. Inflating layout asynchronously comes with a caveat. view access can only be granted after inflate has been completed. Hence, there is a trade-off between performance, stability, and developer experience.

Layout inflate is not a concern with Jetpack compose. Layouts in compose are written in Kotlin and compiled just like the rest of your app. Our experience with Jetpack compose has been positive so far, so we would strongly recommend it. Here is the blog post about Comparing Jetpack compose performance with XML.

--

--