Layout: XML vs Code
Any developer who has built UIs knows that writing layouts in XML is dramatically more productive than hand-writing code. Before Anko (a Kotlin library) gained traction, XML was the go-to choice. Performance hiccups happened occasionally, but they were generally tolerable. Anko’s rise, however, got us thinking: is there a way to enjoy the productivity of XML while getting Anko-level performance?
XML vs Code
XML layouts are parsed at runtime. XmlPullParser reads binary XML files and reflectively constructs View nodes on the fly. A typical app’s home screen consists of many XML layouts, resulting in repeated “load-parse” cycles. People say hand-written code performs better – but how much better? Here is a comparison between Anko and XML:
| Device | Specs | Anko | XML | Diff |
|---|---|---|---|---|
| Alcatel One Touch | Mediatek MT6572Dual-core 1.3GHz Cortex-A7512MB RAM | 169 ms | 608ms | 359% |
| Huawei Y300 | Qualcomm MSM 8225Dual-core 1.0GHz Cortex-A5512MB RAM | 593 ms | 3435ms | 578% |
| Huawei Y330 | Mediatek MT6572Dual-core 1.3GHz Cortex-A7512MB RAM | 162 ms | 984ms | 606% |
| Samsung Galaxy S2 | Exynos 4210Dual-core 1.2GHz Cortex-A91GB RAM | 207 ms | 753ms | 363% |
Data source: https://android.jlelse.eu/400-faster-layouts-with-anko-da17f32c45dd
Having It Both Ways
Since Booster is purpose-built for performance optimization, you can probably guess the solution: transpile XML into bytecode. You might ask: does transpiling to bytecode cause compatibility issues? That depends on the implementation. There are two approaches:
- The hand-written code approach
After parsing the XML, call the corresponding APIs based on the XML element attributes:
1 | RelativeLayout root = new RelativeLayout(context); |
- The runtime XML parsing approach
After parsing the XML, construct an AttributeSet from the XML element attributes:
1 | AttributeSet attr = new AttributeSet(...); |
Comparing the two approaches:
- Approach 1 requires adapting every Layout. For custom Layouts or Views, the generated code may not match the XML rendering.
- Approach 2 only needs to ensure the AttributeSet is correct to guarantee the resulting UI matches the XML rendering.
Therefore, Booster chose Approach 2. For details on the implementation and underlying principles, see this sample project.
Layout Inflater
The Android SDK provides LayoutInflater to convert XML into Views. If you have read the AOSP source code, you may have noticed that LayoutInflater is a system service. Why make it a system service instead of a utility class? I believe the primary reason is performance.
“Wait – system services involve IPC calls. How could that possibly be for performance?”
This goes back to XML parsing. XML layouts can reference three types of resources:
- System resources
- The current app’s resources
- Other apps’ resources
For system resources:
- If every inflate call had to load system resources from scratch, ANR would be inevitable.
- If a layout references resources from another package, the current app normally cannot access them.
For these reasons, a higher-level resource management mechanism is needed, making a system service entirely reasonable.
Layout Library
To analyze how LayoutInflater constructs Views, we turned to Android Studio’s visual layout designer, which is the origin of this sample project. It allows step-by-step debugging of the XML rendering process in the IDE.
Layout Library consists of two parts:
- layoutlib-api: The interface layer for layoutlib, shipped with Android Studio.
- layoutlib: The implementation layer for layoutlib-api, shipped with the Android SDK.
This design is primarily for backward compatibility.
Another highlight of Layout Library is its use of a Robolectric Shadow-like approach to replace APIs in the Android SDK. This way, APIs that originally called native code are implemented in pure Java within Layout Library:
1 |
|
Feel free to step through the sample project in a debugger.
Summary
Now that you understand how LayoutInflater and Layout Library work, you should have a clear picture of how Booster transpiles XML to bytecode. The process is:
- Load system resources from the platform corresponding to the compileSdk in the Android SDK (used in step 2).
- After the mergeRes task, load the app’s resources.
- Parse layout XMLs from the project and generate corresponding code based on the parsing results.
- During compilation, include the generated code in the build.
- During the transform pass, scan classes for the following method calls and replace them with calls to the code generated in step 3:
LayoutInflater.inflate(...)Activity.setContentView(int)Dialog.setContentView(int)- ……
To be continued…
- Blog Link: https://johnsonlee.io/2019/07/13/booster-xml-layout-to-code.en/
- Copyright Declaration: 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
