Насколько медленно работает рефлексия в Android?
- English |
- Русский |
- 中文
(На данный момент мы проанализировали довольно много приложений и нашли большое количество проблем, которые значительно ухудшают их производительность. Начиная с этого поста, мы будем описывать их одну за другой. )
Естественно, рефлексия - это очень полезная вещь в Java и Android разработке. Даже несмотря на то, что она часто может быть причиной значительных проблем с производительностью в Android приложениях. Давайте посмотрим на несколько реальных примеров, чтобы понять как это может на них повлиять.
Два примера из реальной жизни
Наш первый пример - NYTimes Android app. С помощью NimbleDroid, наши друзья в NYTimes нашли, что “reflective type adapters” в Gson замедляют их приложение на 700ms. В итоге они починили это с помощью написания custom type adapters.
Наш второй пример - Photobucket, большая платформа для обмена фотографиями. Тут, рефлексия снова причина больших проблем.

Мы видим, что время выполнения конструктора com.photobucket.api.client.jersey.UserClient
- это целых 660ms. На iricle graph мы можем увидеть, что причина такой большой задержки - рефлексия. Давайте посмотрим:

Напомним, что метод getGenericInterfaces() возвращает типы интерфейсов, которые реализуют данный класс. Тут он вызывается 5 раз и это занимает ~81ms. Конечно, изначально это не выглядит слишком много, но все эти вызовы вместе занимают около 600ms. Давайте копнем поглубже и попытаемся понять, почему это занимает столько времени.
Похоже, что эта библиотека позволяет разработчикам настроить REST клиент с помощью аннотаций. Проблема в том, что она не обрабатывает аннотации во время сборки, но создает REST клиент в runtime (с помощью рефлексии). С точки зрения производительности - это катастрофа.
Micro-benchmarks
Мы создали простой тест чтобы посмотреть, насколько медленно работает рефлексия.
Мы будем работать в android.app.Activity и повторять опериции по 10.000 раз, например:
Class<?> clazz = android.app.Activity.class;
for (int i = 0; i < 10000; i++) {
clazz.getFields();
}
Мы также добавили два теста, которые включают создания обьектов (типа DummyItem - полностью пустой класс), чтобы посмотреть на замедления только со стороны рефлексии. Вот пример:
try {
for (int i = 0; i < 1_000_000; i++) {
DummyItem.class.newInstance();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
И вот наши результаты (все числа в миллисекундах, измерено на наших реальных устройствах, используемых каждый день, чтобы сделать тест более приближенным к реальности):
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 |
Очевидно, что рефлексия на Android - это довольно медленно: сравните данные с рефлексией (1332ms, 6384ms, 2891ms) и данные без нее (312ms, 358ms, 774ms). Интересно, что на Android 5.0 ART рефлексия исполняется медленнее, чем на Android 4.1 Dalvik.
Больше примеров
ActiveAndroid - еще одна библиотека, использующая рефлексию. Давайте посмотрим, как она влияет на время запуска, проанализировав несколько приложений из Play store:
Вот Scribd:

У Myntra похожие проблемы:

Как видите, библиотеке нужно больше секунды для инициализации. Это довольно много, учитывая, что пользователи ожидают время запуска приложения около 2 секунд..
В заключение хочется сказать, что рефлексия на Android работает довольно медленно. Чтобы улучшить UX ваших пользователей, мы советуем:
Рекомендация: избегайте использования рефлексии (или библиотек, которые используют рефлексию) вовсе, и в частности не используйте "reflective type adapters" для сериализации Java объектов.