Привет, Хабр. Для будущих студентов курса "Android Developer. Professional" подготовили традиционный перевод материала.
Также приглашаем всех желающих на вебинар по теме «Профилируем и ускоряем Gradle сборки». На занятии участники вместе с экспертом:
— научатся искать узкие места в сборках с помощью gradle-profiler, scan и visualVM;
— научатся правильно конфигурировать Gradle;
— рассмотрят другие возможности для оптимизации и ускорения сборок на большом проекте.
![](https://habrastorage.org/getpro/habr/upload_files/417/d84/0d9/417d840d928fcfe240dd57dc04175907.png)
Dagger и Koin, без сомнения, являются двумя самыми популярными фреймворками для внедрения зависимостей на Android. Обе эти библиотеки служат одной цели и кажутся очень похожими, но работают они по-разному.
А при чем здесь Hilt? Hilt — это библиотека, которая использует Dagger под капотом и просто упрощает работу с ним, поэтому все, что я говорю здесь о Dagger, применимо и к Hilt.
В этой статье я не буду подталкивать вас к решению, какую из этих библиотек выбрать. Вместо этого я хочу показать вам, чем они отличаются внутри, и каковы могут быть последствия от этих различий для вашего приложения.
Dagger
Если мы хотим, чтобы Dagger предоставил экземпляр какого-либо класса, все, что нам нужно сделать, это добавить аннотацию @Inject
к конструктору.
![](https://habrastorage.org/getpro/habr/upload_files/db9/8ee/e0f/db98eee0ff810f65b247fa98008cddd6.png)
Добавление этой аннотации приведет к тому, что Dagger сгенерирует фабрику (Factory) для этого класса во время компоновки. В данном случае, поскольку имя класса — CompositeAdapter
, он сгенерирует класс с именем CompositeAdapter_Factory
.
Этот класс содержит всю информацию, необходимую для создания экземпляра класса CompositeAdapter
.
![Фрагмент кода, сгенерированного Dagger Фрагмент кода, сгенерированного Dagger](https://habrastorage.org/getpro/habr/upload_files/689/8a3/d79/6898a3d799aa5c432226add21c48504c.png)
Как видите, фабрика реализует метод get()
, который возвращает новый экземпляр класса CompositeAdapter
. Фактически это метод, указанный в интерфейсе Provider, который реализует этот класс. Другие классы могут использовать интерфейс Provider для получения экземпляра класса.
![](https://habrastorage.org/getpro/habr/upload_files/748/622/629/748622629df3862bbe321e12027bad5e.png)
Что, если мы будем использовать Hilt вместо Dagger?
В этом примере особой разницы вы бы не заметили. Hilt — это библиотека, которая использует Dagger внутри себя, а класс, который я вам показал, генерируется Dagger. Если вы используете Hilt, он создает для нас пару дополнительных классов, которые упрощают использование Dagger и сокращают количество шаблонного кода, который нам нужно написать. Но основная часть остается прежней.
![](https://habrastorage.org/getpro/habr/upload_files/a56/0d2/910/a560d29109a769ec6e19a5ca1768c050.png)
Koin
У Koin совершенно другой подход к управлению зависимостями, чем у Dagger и, конечно, чем у Hilt. Чтобы зарегистрировать зависимость в Koin, мы не используем никаких аннотаций, поскольку Koin не генерирует никакого кода. Вместо этого мы должны предоставить модули с фабриками, которые будут использоваться для создания экземпляров каждого класса, который понадобится в нашем проекте.
Ссылка на эти фабрики добавляется Koin в класс InstancesRegistry
, который содержит ссылки на все фабрики, которые мы написали.
![](https://habrastorage.org/getpro/habr/upload_files/31f/350/2f3/31f3502f36185fb378de8788f4ea05b8.png)
Ключ в этой map — это полное имя класса или имя, которое мы предоставили, используя именованный параметр. Значение — это написанная нами фабрика, которая будет использоваться для создания экземпляра класса.
Чтобы получить зависимость, все, что нам нужно сделать, это вызвать get()
(например, в фабрике) или вызвать делегированное свойство inject()
в активити или фрагментах, которые под капотом лениво вызывают get()
. Метод get()
найдет фабрику, зарегистрированную для класса данного типа, и внедрит ее туда.
![](https://habrastorage.org/getpro/habr/upload_files/0ae/2d1/4d6/0ae2d14d61753397a94345b870a3fefd.png)
Какие последствия для приложения?
Есть некоторые последствия того факта, что Dagger генерирует код для предоставления зависимостей, а Koin — нет.
1. Обработка ошибок
Поскольку Dagger — это среда внедрения зависимостей во время компиляции, если мы забыли предоставить какую-либо зависимость, мы узнаем о нашей ошибке практически мгновенно, потому что наш проект не будет собран.
Например, если мы забыли добавить аннотацию @Inject
к конструктору CompositeAdapter
и попытаемся внедрить его во фрагмент, сборка завершится неудачно с соответствующей ошибкой, которая показывает нам, что именно пошло не так.
![Вывод сборки Dagger с отсутствующей аннотацией @Inject Вывод сборки Dagger с отсутствующей аннотацией @Inject](https://habrastorage.org/getpro/habr/upload_files/421/57f/ea0/42157fea0a7250dd059c179eb5291f78.png)
С Koin ситуация иная. Поскольку он не генерирует никакого кода, если мы забыли добавить фабрику для класса CompositeAdapter
, приложение будет собрано, но закрашится с RuntimeException
, как только мы запросим экземпляр этого класса. Это может произойти при запуске приложения, поэтому мы можем заметить это сразу, но это также может произойти позже, на каком-то второстепенном экране или когда пользователь выполняет какое-то конкретное действие.
![Koin выдает исключение, когда отсутствует фабрика для CompositeAdapter Koin выдает исключение, когда отсутствует фабрика для CompositeAdapter](https://habrastorage.org/getpro/habr/upload_files/719/76d/1e4/71976d1e4dbb18956f2ad873cefabad9.png)
2. Влияние на время сборки
В том, что Koin не генерирует никакого кода, есть некоторое преимущество: это оказывает гораздо меньшее влияние на время сборки. Dagger необходимо использовать процессор аннотаций для сканирования нашего кода и создания соответствующих классов. Это может занять некоторое время и замедлить нашу сборку.
3. Влияние на производительность во время выполнения
С другой стороны, поскольку Koin разрешает зависимости во время выполнения, это результирует в немного более худшей производительности во время выполнения.
![](https://habrastorage.org/getpro/habr/upload_files/9f3/1f5/5c0/9f31f55c031db9eb58ef6d78deaeb743.png)
На сколько? Чтобы оценить разницу в производительности, мы можем взглянуть на этот репозиторий, где Рафа Васкес измерил и сравнил производительность этих двух библиотек на разных устройствах. Тестовые данные были подготовлены таким образом, чтобы имитировать несколько уровней транзитивных зависимостей, т.е. это не просто приложение-болванка с 4 классами.
![Источник: https://github.com/Sloy/android-dependency-injection-performance Источник: https://github.com/Sloy/android-dependency-injection-performance](https://habrastorage.org/getpro/habr/upload_files/3e9/ddb/da4/3e9ddbda4c0d7a78696f94b881a2adb6.png)
Как видите, Dagger практически не влияет на производительность при запуске. С другой стороны, в Koin мы видим, что настройка занимает значительное время. Внедрение зависимостей в Dagger также немного быстрее, чем в Koin.
Резюме
Как я сказала в начале этой статьи, моя цель не в том, чтобы указать вам, какую из этих библиотек использовать. Я использовала Koin и Dagger в двух разных, довольно больших проектах. Честно говоря, я думаю, что решение, какой из них выбрать, Dagger или Koin, гораздо менее важно, чем буквально что-угодно, что позволяет вам писать чистый, простой и легкий для модульного тестирования код. И я думаю, что все эти библиотеки: Koin, Dagger и Hilt действительно служат этой цели.
У всех этих библиотек есть свои сильные стороны, и я надеюсь, что знание того, как они работают, поможет вам принять решение, какая из них лучше всего подойдет именно для вашего приложения.
- Узнать подробнее о курсе "Android Developer. Professional".
- Узнать подробнее о курсе "Android Developer. Basic".Смотреть вебинар по теме «Профилируем и ускоряем Gradle сборки».