By Anton Krasov

(So far we’ve analyzed a lot of apps and discovered a handful of issues that significantly slow down many apps. Starting from this post, we’ll describe these issues one by one.)

Reflection is, of course, an extremely useful aspect of Java and Android development. Yet it turns out that reflection can very often be the source of significant slowdown within an Android application. Perhaps the most intuitive way of understanding this is going through a couple of real-life examples.

Two Real-world Examples

Our first example involves NYTimes Android app. With the help of NimbleDroid, our friends at NYTimes found out that the reflective type adapters in Gson cost their Android app a 700ms startup delay. They eventually fixed this delay with manually written custom type adapters.

Our second example involves Photobucket, a large photo-sharing platform. Here, reflection again causes a big bottleneck.

660ms for call com.photobucket.api.client.jersey.UserClient constructor

We see that the com.photobucket.api.client.jersey.UserClient constructor takes an entire 660ms to run. Looking further into the icicle graph, we see that the reason for such a lag lies in reflection. Check this out:

lots of reflection calls, like: java.lang.Class.getGenericInterfaces

Note that the getGenericInterfaces() method returns the types of the interfaces that this class directly implements. Here, it is invoked 5 times and takes ~81ms. Sure, on the surface this may not seem like much, but altogether, use of the method is causing ~600ms of start time delay. Let’s take a deeper look at why this is taking so long.

It turns out that this library allows developers to configure a REST client with annotations. The issue is that the library doesn’t process the annotations during build time, but instead parses and creates the REST client during runtime (with the help of reflection). From a performance point of view, this is catastrophic.

Micro-benchmarks

We’ve created a simple test to quantify how slow reflection is.

We will work with the android.app.Activity class and repeat operations 10,000 times, like this:

Class <?> clazz = android . app . Activity . class ; for ( int i = 0 ; i < 10000 ; i ++) { clazz . getFields (); }

We’ve also set up two tests that include creating objects (of the type DummyItem, an empty dummy class) to look at the overhead purely caused by reflection. Here’s an example:

try { for ( int i = 0 ; i < 1_000_000 ; i ++) { DummyItem . class . newInstance (); } } catch ( InstantiationException e ) { e . printStackTrace (); } catch ( IllegalAccessException e ) { e . printStackTrace (); }

And here are our results (all numbers are in ms, measured on our personal devices with real usage to make the results more faithful to real world):

NEXUS 5 (6.0) ART GALAXY S5 (5.0) ART GALAXY S3 mini (4.1.2) Dalvik getFields 1108 1626 27083 getDeclaredFields 347 951 7687 getGenericInterfaces 16 23 2927 getGenericSuperclass 247 298 665 makeAccessible 14 147 449 getObject 21 167 127 setObject 21 201 161 createDummyItems 312 358 774 createDummyItemsWithReflection 1332 6384 2891

It’s evident that reflection in Android is excruciatingly slow - compare the (1332ms, 6384ms, 2891ms) with reflection to the (312ms, 358ms, 774ms) without reflection. Interestingly, Android 5.0 ART on a more powerful device actually makes reflection much slower than Android 4.1 Dalvik on a less powerful device; only in Android 6.0 ART is the overhead reduced, but the overhead is still quite significant.

More real-world examples

ActiveAndroid is another library that uses reflection. Let’s take a look at how it can affect start time by analyzing some real apps on the Play store:

Here’s the Scribd app:

1093ms for call com.activeandroid.ActiveAndroid.initialize

Myntra sees similar problems:

1421ms for call com.activeandroid.ActiveAndroid.initialize

As you can see, the library requires more than a second to initialize. That’s a lot of time, especially taking into consideration that users expect an average app start time of 2s.

To conclude, reflection in Android is really slow. To guarantee you offer users the smoothest experience possible, we recommend the following:

Recommendation: understand what you're getting into when using reflection (or libraries that use reflection). In particular, do not use reflective type adapters to serialize or deserialize Java objects.

(Mar 3, 2016: we updated our recommendation thanks to the discussion with Jake Wharton.)



