Привет, Хабр. Для будущих студентов курса "Android Developer. Professional" подготовили традиционный перевод материала.

Также приглашаем всех желающих на вебинар по теме
«Профилируем и ускоряем Gradle сборки». На занятии участники вместе с экспертом:
— научатся искать узкие места в сборках с помощью gradle-profiler, scan и visualVM;
— научатся правильно конфигурировать Gradle;
— рассмотрят другие возможности для оптимизации и ускорения сборок на большом проекте.


Dagger и Koin, без сомнения, являются двумя самыми популярными фреймворками для внедрения зависимостей на Android. Обе эти библиотеки служат одной цели и кажутся очень похожими, но работают они по-разному.

А при чем здесь Hilt? Hilt — это библиотека, которая использует Dagger под капотом и просто упрощает работу с ним, поэтому все, что я говорю здесь о Dagger, применимо и к Hilt.

В этой статье я не буду подталкивать вас к решению, какую из этих библиотек выбрать. Вместо этого я хочу показать вам, чем они отличаются внутри, и каковы могут быть последствия от этих различий для вашего приложения.

Dagger

Если мы хотим, чтобы Dagger предоставил экземпляр какого-либо класса, все, что нам нужно сделать, это добавить аннотацию @Inject к конструктору.

Добавление этой аннотации приведет к тому, что Dagger сгенерирует фабрику (Factory) для этого класса во время компоновки. В данном случае, поскольку имя класса — CompositeAdapter, он сгенерирует класс с именем CompositeAdapter_Factory.

Этот класс содержит всю информацию, необходимую для создания экземпляра класса CompositeAdapter.

Фрагмент кода, сгенерированного Dagger
Фрагмент кода, сгенерированного Dagger

Как видите, фабрика реализует метод get(), который возвращает новый экземпляр класса CompositeAdapter. Фактически это метод, указанный в интерфейсе Provider, который реализует этот класс. Другие классы могут использовать интерфейс Provider для получения экземпляра класса.

Что, если мы будем использовать Hilt вместо Dagger?

В этом примере особой разницы вы бы не заметили. Hilt — это библиотека, которая использует Dagger внутри себя, а класс, который я вам показал, генерируется Dagger. Если вы используете Hilt, он создает для нас пару дополнительных классов, которые упрощают использование Dagger и сокращают количество шаблонного кода, который нам нужно написать. Но основная часть остается прежней.

Koin

У Koin совершенно другой подход к управлению зависимостями, чем у Dagger и, конечно, чем у Hilt. Чтобы зарегистрировать зависимость в Koin, мы не используем никаких аннотаций, поскольку Koin не генерирует никакого кода. Вместо этого мы должны предоставить модули с фабриками, которые будут использоваться для создания экземпляров каждого класса, который понадобится в нашем проекте.

Ссылка на эти фабрики добавляется Koin в класс InstancesRegistry, который содержит ссылки на все фабрики, которые мы написали.

Ключ в этой map — это полное имя класса или имя, которое мы предоставили, используя именованный параметр. Значение — это написанная нами фабрика, которая будет использоваться для создания экземпляра класса.

Чтобы получить зависимость, все, что нам нужно сделать, это вызвать get() (например, в фабрике) или вызвать делегированное свойство inject() в активити или фрагментах, которые под капотом лениво вызывают get(). Метод get() найдет фабрику, зарегистрированную для класса данного типа, и внедрит ее туда.

Какие последствия для приложения?

Есть некоторые последствия того факта, что Dagger генерирует код для предоставления зависимостей, а Koin — нет.

1. Обработка ошибок

Поскольку Dagger — это среда внедрения зависимостей во время компиляции, если мы забыли предоставить какую-либо зависимость, мы узнаем о нашей ошибке практически мгновенно, потому что наш проект не будет собран.

Например, если мы забыли добавить аннотацию @Inject к конструктору CompositeAdapter и попытаемся внедрить его во фрагмент, сборка завершится неудачно с соответствующей ошибкой, которая показывает нам, что именно пошло не так.

Вывод сборки Dagger с отсутствующей аннотацией @Inject
Вывод сборки Dagger с отсутствующей аннотацией @Inject

С Koin ситуация иная. Поскольку он не генерирует никакого кода, если мы забыли добавить фабрику для класса CompositeAdapter, приложение будет собрано, но закрашится с RuntimeException, как только мы запросим экземпляр этого класса. Это может произойти при запуске приложения, поэтому мы можем заметить это сразу, но это также может произойти позже, на каком-то второстепенном экране или когда пользователь выполняет какое-то конкретное действие.

Koin выдает исключение, когда отсутствует фабрика для CompositeAdapter
Koin выдает исключение, когда отсутствует фабрика для CompositeAdapter

2. Влияние на время сборки

В том, что Koin не генерирует никакого кода, есть некоторое преимущество: это оказывает гораздо меньшее влияние на время сборки. Dagger необходимо использовать процессор аннотаций для сканирования нашего кода и создания соответствующих классов. Это может занять некоторое время и замедлить нашу сборку.

3. Влияние на производительность во время выполнения

С другой стороны, поскольку Koin разрешает зависимости во время выполнения, это результирует в немного более худшей производительности во время выполнения.

На сколько? Чтобы оценить разницу в производительности, мы можем взглянуть на этот репозиторий, где Рафа Васкес измерил и сравнил производительность этих двух библиотек на разных устройствах. Тестовые данные были подготовлены таким образом, чтобы имитировать несколько уровней транзитивных зависимостей, т.е. это не просто приложение-болванка с 4 классами.

Источник: https://github.com/Sloy/android-dependency-injection-performance
Источник: https://github.com/Sloy/android-dependency-injection-performance

Как видите, Dagger практически не влияет на производительность при запуске. С другой стороны, в Koin мы видим, что настройка занимает значительное время. Внедрение зависимостей в Dagger также немного быстрее, чем в Koin.

Резюме

Как я сказала в начале этой статьи, моя цель не в том, чтобы указать вам, какую из этих библиотек использовать. Я использовала Koin и Dagger в двух разных, довольно больших проектах. Честно говоря, я думаю, что решение, какой из них выбрать, Dagger или Koin, гораздо менее важно, чем буквально что-угодно, что позволяет вам писать чистый, простой и легкий для модульного тестирования код. И я думаю, что все эти библиотеки: Koin, Dagger и Hilt действительно служат этой цели.

У всех этих библиотек есть свои сильные стороны, и я надеюсь, что знание того, как они работают, поможет вам принять решение, какая из них лучше всего подойдет именно для вашего приложения.


- Узнать подробнее о курсе "Android Developer. Professional".
- Узнать подробнее о курсе
"Android Developer. Basic".

Смотреть вебинар по теме «Профилируем и ускоряем Gradle сборки».