Разработка
Сравнение Koin и Dagger Hilt в современной Android-разработке
Бенчмарки подчеркивают, что Koin — это надежная и современная альтернатива для разработки под Android, которая не уступает Hilt по производительности и при этом обладает собственными уникальными преимуществами.
При выборе фреймворка для инъекции зависимостей для разработки на Kotlin для Android производительность часто является ключевым фактором. В этой статье рассматривается производительность Koin в его последней версии (4.0.1-Beta1) и проводится сравнение с Dagger Hilt (2.52). Вместо того чтобы полагаться на упрощенные бенчмарки или ограниченные сценарии выполнения кода, мы ориентируемся на разработчиков: понимание производительности в реальном мире, при повседневном использовании. Кроме того, эта статья призвана успокоить тех, кто, возможно, колеблется в выборе Koin из-за проблем с производительностью.
Что сравнивать?
Бенчмаркинг таких фреймворков ставит перед нами серьезную задачу: обеспечить справедливое сравнение и сосредоточиться на эквивалентном поведении и возможностях.
Чтобы сделать это упражнение значимым, я выбрал подход, ориентированный на пользователя: оценка времени, которое требуется для создания компонента, запрашиваемого из пользовательского интерфейса (например, ViewModel и так далее…). Чтобы убедиться, что наш тестовый контекст достаточно силен, нам нужно достаточно сложное приложение (не базовое приложение «Hello World» или «Список дел»).
Для этой цели я решил использовать приложение Google Now in Android — отличное приложение с открытым исходным кодом, которое достаточно сложное и охватывает проблемы реальной разработки, а команда Android демонстрирует лучшие практики (модульность, Jetpack Compose, внедрение зависимостей…).
Оценивая Koin и Dagger Hilt в этой среде, мы стремимся получить сведения, которые действительно важны для разработчиков Android.
Исходники доступны тут: https://github.com/InsertKoinIO/nowinandroid
там ыы найдете следующие ветки:
- perfs_koin — это ветка Now in Android мигрированная на Koin, с измерением производительности
- perfs_hilt — ветка Hilt по умолчанию, с измерением производительности
И не забудьте про официальную документацию по Koin. Итак, давайте погрузимся в детали!
Service Locator или Dependency Injection? Koin может и то, и другое!
Прежде чем погрузиться в бенчмарки, давайте разберемся с распространенным вопросом о Koin: является ли он локатором служб или фреймворком инъекции зависимостей? Ответ — и то, и другое.
- Сервисный локатор извлекает зависимости динамически через централизованный реестр.
- Инъекция зависимостей предоставляет зависимости в явном виде при инстанцировании, что повышает тестируемость и удобство сопровождения.
Koin соединяет эти два подхода, предлагая динамический поиск через get()
или inject()
и одновременно поддерживая такие возможности DI, как инъекция конструктора и скопинг.
Динамическое поведение Koin зависит от жизненного цикла Android, что исторически делало внедрение конструкторов сложной задачей. Хотя современные функции Android теперь поддерживают инъекцию конструктора, Koin остается гибким, позволяя разработчикам выбирать наилучший подход для своих нужд.
По своей сути Koin — это DI-фреймворк. Он избегает накладных расходов на отражение, использует Kotlin DSL для графов зависимостей и поддерживает масштабируемые жизненные циклы. Однако его способность функционировать как локатор служб добавляет универсальности, особенно для более простых или унаследованных проектов.
Это краткое описание, но на этой странице документации по проекту Koin есть больше деталей, если вам нужно углубиться в подробности.
Почему стоит выбрать Koin?
- Простота и удобство для разработчиков: чистый DSL Koin, отсутствие накладных расходов во время компиляции, минимальная настройка и простота тестирования позволяют сосредоточиться на создании приложений.
- Масштабируется вместе с вашим приложением: от небольших приложений до сложных проектов, Koin легко масштабируется в соответствии с вашими потребностями.
- Развивающаяся безопасность во время компиляции: благодаря таким функциям, как проверка модулей (Verify API), аннотациям Koin (KSP для безопасности конфигурации) и готовящемуся плагину Koin IDE, Koin упрощает программирование, повышая при этом безопасность.
- Готовность к Kotlin Multiplatform: Koin легко управляет зависимостями в iOS, Android, Desktop и Web, что делает его лучшим DI-фреймворком для кросс-платформенной разработки.
- Идеально подходит для Compose Multiplatform: Koin легко интегрируется с Compose Multiplatform, поддерживая общую логику и DI для компонентов пользовательского интерфейса — даже ViewModel.
Если вам интересно узнать о внутреннем устройстве и дизайне Koin, дайте мне знать — я с удовольствием расскажу об этом в одной из следующих статей. А пока давайте погрузимся в бенчмарки!
Отслеживание производительности
Отслеживание производительности компонентов в течение сессий сложнее, чем кажется на первый взгляд. Хотя такие инструменты, как Baseline Profiles Macrobenchmark и аналогичные инструменты глубокого погружения, предлагают отличный анализ, они не позволяют мне легко извлекать тестовые значения в кастомном использовании. В качестве альтернативы можно использовать подключенные платформы для разработчиков, такие как Firebase Crashlytics или Kotzilla Platform, которые предлагают удобные решения для сбора и анализа показателей производительности.
Моя цель — оставаться простым и легким: я хочу измерить, сколько времени уходит на создание определенного компонента, например, на создание экземпляра ViewModel с помощью инъекции зависимостей. Мне не нужен сложный фреймворк для этой задачи, но я не против ручного инструментария, если он будет простым и легким.
Для этого я написал несколько функций для перехвата времени вызова функций из DI-фреймворков (все находится в файле Measure.kt). Эта утилита использует функцию Kotlin measureTimedValue
— элегантный и эффективный способ измерения времени выполнения кода, поэтому она отлично подходит для легкого ручного инструментария. Расширив Android Context, я создал простой способ регистрации длительности любого вызова функции (или операции инъекции зависимостей) непосредственно в лог-файл.
inline fun <reified T> Context.measureTimeLazy(tag : String, code : () -> Lazy<T>) : Lazy<T>{ val result = measureTimedValue(code) val timeInMs = result.duration.inWholeMicroseconds / 1000.0 logBenchmark(tag,timeInMs) return result.value }
В итоге мы сохраняем все результаты в локальном файле (функция logBenchmark
). Этот файл будет извлечен, чтобы можно было рассчитать среднее время.
Now in Android
Теперь давайте посмотрим, как эти функции отслеживания применяются в нашем реальном сценарии. Для этого бенчмарка мы измерим производительность следующих компонентов: MainActivityViewModel, ForYouViewModel и время запуска.
Эти ViewModel являются первыми двумя, используемыми в приложении, что делает их идеальными кандидатами для оценки производительности DI-фреймворков на этапе начальной загрузки приложения.
В реализации Koin отслеживание производительности этих компонентов осуществляется следующим образом:
Примечание: мы используем последнюю функцию Koin AndroidX Startup для улучшения времени запуска.
Для реализации Hilt отслеживание применяется аналогичным образом:
Чтобы засечь время запуска, мы используем функцию onWindowFocusChanged
в MainActivity. Она измеряет время, необходимое приложению для отрисовки первого кадра после получения фокуса, что дает четкое представление о производительности запуска приложения. Мы отслеживаем время от класса Application до первой активити:
Выполнение, извлечение и результаты
Для автоматического сбора показателей производительности мы запустили сценарий оболочки benchmark.sh. Этот скрипт автоматизирует последовательность действий по установке, запуску, ожиданию в течение нескольких секунд и остановке приложений для имитации реалистичных моделей использования. После выполнения всех действий он извлекает файл benchmark_log.txt, содержащий все записанные времена. Это 25 итераций запуска, ожидания и остановки приложения (демонстрационная сборка).
Используя собранные данные, Python-скрипт stats.py обрабатывает лог, чтобы вычислить ключевую статистику: минимальное, максимальное и среднее время для каждого компонента бенчмарка.
В терминале можно просто выполнить команду: benchmark.sh ; python3 stats.py (из папки /app).
Лучше всего запустить его на реальном устройстве Android. На моем OnePlus Nord (Android 12) я получил следующие результаты:
В этом тесте, помимо среднего, минимума и максимума, мы показываем «стандартную ошибку»: она измеряет надежность среднего, показывая, насколько сильно оно может отличаться от истинного среднего значения популяции. Меньшие значения означают более стабильные и точные результаты. Это также помогает сравнить результаты стабильности между Koin и Dagger Hilt.
Бенчмарки подчеркивают, что Koin — это надежная и современная альтернатива для разработки под Android, которая не уступает Hilt по производительности и при этом обладает собственными уникальными преимуществами.
Тем не менее, бенчмарки — это лишь часть истории. Результаты могут отличаться в зависимости от вашего приложения, но тенденции очевидны: Koin эффективен для решения реальных задач. От Android до Kotlin Multiplatform и Compose Multiplatform приложений.
Я всегда открыт для обратной связи — если у вас есть мысли или идеи, давайте пообщаемся!
Почему бы не попробовать Koin? Пусть Koin станет частью вашего путешествия!