Performance comparison: building Android Layout with XML vs By Code vs Jetpack Compose

Mustafa Basim
5 min readMay 13, 2020

--

Photo by Brad Neathery

NOTE: This article is outdated and needs updated metrics to calculate the performance differences, it will be updated soon.

Intro

I have always wondered what is the difference between writing your activity layout in XML or by code programmatically, then jetpack compose came along, so I did a little experiment to test the time it takes each method to create the activity, and I would like to share the results with you.

Benchmark

I didn’t exactly know how to calculate the views creating time so I added multiple stop points to achieve it, as follow:

I took the start time at the very beginning of the activity.

private var start = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
start = System.currentTimeMillis()
.
.
.

Then added multiple stop points, the first one at the end of the onCreate function.

override fun onCreate(savedInstanceState: Bundle?) {
.
.
.
val end = System.currentTimeMillis()
val result = end - start
Numbers.XML.onCreate.add(result) // 1
}

The second one on the start of the onResume function.

override fun onResume() {
super.onResume()
val end = System.currentTimeMillis()
val result = end - start
Numbers.XML.onResume.add(result) // 2
}

The third stop point was recorded on the ViewTreeObserver GlobalLayoutListener to make sure all views have done their measurements.

Note: For Compose this was not possible because we are not dealing with views anymore.

binding.constraintLayout.rootView.viewTreeObserver.addOnGlobalLayoutListener(
object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.constraintLayout.rootView.viewTreeObserver.removeOnGlobalLayoutListener(this)
val end2 = System.currentTimeMillis()
val result2 = end2 - start
Numbers.XML.viewTreeObserver.add(result2)// 3
}
})

The fourth stop point was an extra check out of curiosity, took it on Window DevoceView.

window.decorView.post {
val end2 = System.currentTimeMillis()
val result2 = end2 - start
Numbers.XML.decorView.add(result2) // 4
}

The last stop point was when the activity receives the focus.

private var windowFocusChanged = true
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (windowFocusChanged && hasFocus) {
windowFocusChanged = false
val end = System.currentTimeMillis()
val result = end - start
Numbers.Compose.onWindowFocusChanged.add(result) // 5
}
}

Results

For this, I tested each method alone for 10 runs then close the app entirely and test the other one, no recent apps in the background, no wifi ( in case some app used it, and made the CPU busy a bit ).

The environment :

  • Android Studio Arctic Fox 2020.3.1 beta 3
  • Compose version : 1.0.0-rc01 ( Updated thanks to Callmepeanut contribution )
  • Tested on Galaxy S8 ( SM-G950F, Android 9 ) and Huawei Y5 Prime 2018 ( HUAWEI DRA-LX2, Android 8.1 ).

Note: All the numbers are in milliseconds.

Benchmarks for Galaxy S8 ( SM-G950F ) :

.----------------------.-----.-----.---------.
| XML | Min | Max | Average |
:----------------------+-----+-----+---------:
| onCreate | 21 | 70 | 32 |
:----------------------+-----+-----+---------:
| onResume | 26 | 82 | 36 |
:----------------------+-----+-----+---------:
| viewTreeObserver | 134 | 326 | 185 |
:----------------------+-----+-----+---------:
| window.decorView | 141 | 337 | 191 |
:----------------------+-----+-----+---------:
| onWindowFocusChanged | 142 | 402 | 200 |
'----------------------'-----'-----'---------'
.----------------------.-------.-------.-----------.
| By Code | Min | Max | Average |
:----------------------+-------+-------+-----------:
| onCreate | 23 | 78 | 30 |
:----------------------+-------+-------+-----------:
| onResume | 27 | 83 | 36 |
:----------------------+-------+-------+-----------:
| viewTreeObserver | 126 | 289 | 174 |
:----------------------+-------+-------+-----------:
| window.decorView | 132 | 297 | 180 |
:----------------------+-------+-------+-----------:
| onWindowFocusChanged | 134 | 345 | 187 |
'----------------------'-------'-------'-----------'

.----------------------.--------.---------.-----------.
| Jetback Compose | Min | Max | Average |
:----------------------+--------+---------+-----------:
| onCreate | 10 | 36 | 14 |
:----------------------+--------+---------+-----------:
| onResume | 14 | 45 | 18 |
:----------------------+--------+---------+-----------:
| viewTreeObserver | null | null | null |
:----------------------+--------+---------+-----------:
| window.decorView | 140 | 1000 | 286 |
:----------------------+--------+---------+-----------:
| onWindowFocusChanged | 199 | 1013 | 342 |
'----------------------'--------'---------'-----------'

Benchmarks for Huawei Y5 Prime 2018 ( HUAWEI DRA-LX2 ) :

.----------------------.-------.-------.-----------.
| XML | Min | Max | Average |
:----------------------+-------+-------+-----------:
| onCreate | 71 | 152 | 100 |
:----------------------+-------+-------+-----------:
| onResume | 88 | 179 | 121 |
:----------------------+-------+-------+-----------:
| viewTreeObserver | 314 | 833 | 497 |
:----------------------+-------+-------+-----------:
| window.decorView | 333 | 933 | 529 |
:----------------------+-------+-------+-----------:
| onWindowFocusChanged | 334 | 935 | 530 |
'----------------------'-------'-------'-----------'
.----------------------.-------.-------.-----------.
| By Code | Min | Max | Average |
:----------------------+-------+-------+-----------:
| onCreate | 57 | 134 | 75 |
:----------------------+-------+-------+-----------:
| onResume | 72 | 153 | 91 |
:----------------------+-------+-------+-----------:
| viewTreeObserver | 228 | 565 | 323 |
:----------------------+-------+-------+-----------:
| window.decorView | 244 | 588 | 343 |
:----------------------+-------+-------+-----------:
| onWindowFocusChanged | 245 | 661 | 351 |
'----------------------'-------'-------'-----------'
.----------------------.--------.---------.-----------.
| Jetback Compose | Min | Max | Average |
:----------------------+--------+---------+-----------:
| onCreate | 22 | 41 | 27 |
:----------------------+--------+---------+-----------:
| onResume | 35 | 67 | 43 |
:----------------------+--------+---------+-----------:
| viewTreeObserver | null | null | null |
:----------------------+--------+---------+-----------:
| window.decorView | 352 | 1830 | 648 |
:----------------------+--------+---------+-----------:
| onWindowFocusChanged | 427 | 1833 | 735 |
'----------------------'--------'---------'-----------'

Note: All done with the same identical UI:

Conclusion

Looking at the numbers there is no that big of a difference between XML and Code only layouts, compose on the other hand has some delays.

Putting into consideration the time I took to make the layouts, the XML took about 10~15 minutes to finish the whole thing, By code took me approximately 1 hour since this is my first time creating a layout from scratch programmatically, in time and some extension functions the process will be faster, for Jetpack compose it was a piece of cake, done the whole thing in a couple of lines, no adapter, no constraint to match, I see it as the winner in this category, I hope it gets better in term of performance as well.

Source code can be found HERE

--

--