Android code can be slow. Profiling will often reveal the cause as"View inflation", which just means "constructing a
View", e.g. calling
new TextView(Context), or
new ImageView(Context), or
new LinearLayout(Context). These constructors often do a lot of work, slowly (for reasons out of scope to this post), and they block the UI thread.
When people find UI-thread-blocking jank, they naturally want to speed it up. In Android, the usual advice is to move janky slow stuff to a background thread. But it's unsafe to move View inflation to a background thread. You may make your code faster, but you risk undefined behaviour: very annoying, impossible-to-debug failures that will waste your team's time.
Android is pretty clear on this:
the Android UI toolkit is not thread-safe. So, you must not manipulate your UI from a worker thread—you must do all manipulation to your user interface from the UI thread.
View constructors access their
Context, their parent
View, and static variables. All of this data is mutable, and written and read without synchronization. And if you do these reads and writes from different threads, you have data races, you may see both old and new data. You'll get undefined behaviour.
Here is a (non-exhaustive) list of examples:
- Mutable Context.
ContextImplbehind the scenes) is very mutable. Some data is protected by mutexes, but not all. For example,
ContextImpl.mDisplayis non-final and mutated. Without synchronization, there's no safe publication across threads. Views may access the Display to decide what UI to show.
- Parent LayoutParams. Views often access their parent's
LayoutParamsto decide how to render. The parent could be mutated from the UI thread (perhaps in the middle of a layout pass) while you're reading their params.
- Static Variables. Android often puts data in mutable static datastructures, to save allocations. If you're read these concurrently with a write, you'll see corrupted data.
For example, every View subclass must eventually call the super-constructor
android.view.View(Context), which calls
ViewConfiguration.get(Context), which reads and writes a static
SparseMap cache variable:
SparseArray is a compound data structure, backed by an
Object and an
int length. Interleaved calls to this method from different threads are a data race, and could (for example) result in an
int length longer than the
This is just one data race, which affects all Views. There are probably many more races. Custom Views can have arbitrary logic.
What about AsyncLayoutInflater?
AsyncLayoutInflater notes some requirements about where it should be used:
For a layout to be inflated asynchronously it needs to have a parent whose generateLayoutParams is thread-safe and all the Views being constructed as part of inflation must not create any Handlers or otherwise call myLooper.
This addresses problem #2 above... but not #1 and #3.
So is AsyncLayoutInflater safe? I don't think so.