Кама 2

Накрая пристигна третата част от поредица статии за Dagger 2!

Преди да прочетете допълнително, силно препоръчвам да се запознаете с първата и втората част.

Като цяло библиотеката съществува от прилично време, но документацията все още е изключително отвратителна. За разработчик, който тепърва започва познанството си с Кинжал, дори бих посъветвал да не разглежда официалната документация в началото, за да не бъде разочарован от този труден и несправедлив свят.

Има, разбира се, моменти, които са горе-долу планирани. Но тук всякакви нови функции са описани по такъв начин, че чрез проби и грешки, влизайки в генерирания код, аз самият разбера как работи всичко. За щастие добрите хора пишат добри статии, но дори понякога не дават ясен и ясен отговор веднага.

Така че, спрете да се заяждате и продължете към нови знания!

Квалификационна анотация

Често се случва, че трябва да предоставим няколко обекта от един и същи тип. Например искаме да имаме два Executor в системата: единият с резба, а другият с CachedThreadPool. В този случай на помощ идва „анотация на квалификатора“. Това е персонализирана анотация, в която има анотация @Qualifier. Звучи малко като масло, но примерът е много по-опростен.

Като цяло Dagger2 ни предоставя една готова „анотация на квалификатора“, което може би е напълно достатъчно в ежедневието:

Сега нека видим как изглежда всичко в битка:

В резултат на това имаме два различни екземпляра (единичен изпълнител, мултиизпълнител) от един и същи клас (изпълнител). Какво ни трябва!
Имайте предвид, че обекти от един и същи клас с @Named анотация могат да бъдат предоставени както от напълно различни и независими компоненти, така и от зависими един от друг.

Мързелива инициализация

Един от най-често срещаните ни проблеми в развитието е дългият старт на приложението. Обикновено причината е една - зареждаме твърде много и инициализираме при стартиране. Освен това Dagger2 изгражда графика на зависимост върху основната нишка. И често не всички обекти, проектирани от Dagger, са необходими веднага. Следователно библиотеката ни дава възможност да отложим инициализацията на обекта до първото извикване, използвайки интерфейсите Provider <> и Lazy <> .
Нека веднага насочим вниманието си към пример:

Нека започнем с доставчика singleExecutorProvider. Преди първият извикване на singleExecutorProvider.get () Кинжал не инициализира съответния изпълнител. Но със всяко следващо обаждане singleExecutorProvider.get () ще бъде създаден нов екземпляр. Така че singleExecutor и singleExecutor2 са два различни обекта. Това поведение по същество е идентично с поведението необхванат обект.

Сега Lazy multiExecutorLazy и Lazy multiExecutorLazyCopy. Dagger2 инициализира съответния Изпълнител само когато първо извикване на multiExecutorLazy.get () и multiExecutorLazyCopy.get (). След това Dagger кешира инициализираните стойности за всеки Мързелив <> и при второто извикване на multiExecutorLazy.get () и multiExecutorLazyCopy.get () произвежда кеширани обекти.
По този начин multiExecutor и multiExecutor2 се отнасят за един обект, а multiExecutor3 към втори обект.
Но ако добавим анотацията @Singleton към метода provideMultiThreadExecutor () в AppModule, тогава обектът ще бъде кеширан за цялото дърво на зависимостите, а multiExecutor, multiExecutor2, multiExecutor3 ще се позове на един предмет.
Бъди внимателен.

Асинхронно натоварване

Разгледайте @Singleton и мързеливия интерфейс в AppModule. Lazy просто гарантира, че обектът с тежка категория ще бъде инициализиран, когато поискаме и след това кеширан.

Но какво, ако искаме всеки път да получаваме нов екземпляр на този „тежък“ обект? Тогава си струва да промените малко AppModule:

За метода provideHeavyExternalLibrary () премахнахме обхват, и в provideHeavyExternalLibraryObservable (окончателен доставчик heavyExternalLibraryLazy) използва Provider вместо Lazy. По този начин heavyExternalLibrary и heavyExternalLibraryCopy в MainActivity са различни обекти.

Или дори можете да преместите целия процес на инициализиране на дървото на зависимостите на заден план. Как питате? Много лесно. Първо, нека да разгледаме как беше:

Сега нека да разгледаме актуализирания метод на void setupActivityComponent () (с моите редакции в RxJava):

Нови интересни функции

Кинжал има нови интересни функции, които обещават да улеснят живота ни. Но да разберем как всичко работи и какво ни дава всичко не беше лесна задача.
Е, да започнем!

@ Многократна употреба

Интересна анотация. Позволява ви да спестявате памет, но в същото време всъщност не се ограничава до никакъв обхват, което го прави много удобен за повторно използване на зависимости във всякакви компоненти. Тоест, това е кръстоска между обхвата и отмяната .
На доковете те пишат много важна точка, която по някакъв начин не удря окото от първия път: "За всеки компонент, който използва зависимостта @Reusable, тази зависимост се кешира отделно". И моето допълнение: "За разлика от анотациите на обхвата, когато обектът се кешира при създаването и неговият екземпляр се използва от дъщерни и зависими компоненти"
А сега само пример за разбиране на всичко:

Нашият основен компонент.

AppComponent има два подкомпонента. Забелязали ли сте тази конструкция - FirstComponent.Builder? Ще поговорим за нея малко по-късно.
Сега нека разгледаме UtilsModule .

NumberUtils с @Reusable анотация и оставете StringUtils без обхват .
След това имаме два подкомпонента .

Както виждаме, FirstComponent инжектира само в MainActivity, а SecondComponent - във SecondActivity и ThirdActivity .
Да видим кода.

Накратко за навигацията. От MainActivity стигаме до SecondActivity и след това до ThirdActivity. Сега въпрос. Когато вече сме на третия екран, колко обекта NumberUtils и StringUtils ще бъдат създадени?
Тъй като StringUtils не е обхванат, ще бъдат създадени три екземпляра, т.е. нов обект се създава с всяка инжекция. Ние го знаем.
Но ще има два обекта NumberUtils - единият за FirstComponent, а другият за SecondComponent. И тук отново ще дам основната идея за @Reusable от документацията: "За всеки компонент, който използва зависимостта @Reusable, тази зависимост се кешира отделно! ", За разлика от анотацията на обхвата, където обектът се кешира при създаване и неговият екземпляр се използва от дъщерни и зависими компоненти
Но самите служители на Google предупреждават, че ако имате нужда от уникален обект, който също може да бъде променлив, използвайте само пояснения с обхват.
Също така ще дам линк към въпроса за сравнението на @Singleton и @Reusable със SO.

@ Subcomponent.Builder

Функция, която прави кода по-хубав.
Преди това, за да създадем @Subcomponent, трябваше да напишем нещо подобно:

Това, което не ми хареса в този подход, беше, че родителският компонент беше зареден с ненужни знания за модулите, които използват дъщерните подкомпоненти. Е, плюс предаването на голям брой аргументи не изглежда много хубаво, защото има модел за това Строител.
Сега е по-красиво:

Създаването на FirstComponent сега изглежда така:

Сега имаме възможността да правим така:

AuthManager идва от друг модул като Singleton .
Сега нека да разгледаме набързо генерирания код AuthModule_CurrentUserFactory (в студиото просто поставете курсора върху currentUser и натиснете Ctrl + B):

И ако добавите статичен към currentUser:

Имайте предвид, че в статичната опция няма AuthModel. Така че статичният метод се дърпа от компонента директно, заобикаляйки модула. И ако в модула има само един статичен метод, тогава екземплярът на модула дори не е създаден.
Спестявания и минус ненужни разговори. Всъщност имаме подобрение в производителността.
Те също така пишат, че извикването на статичен метод е с 15–20% по-бързо от извикването на подобен нестатичен метод. Ако греша, iamironz ще ме поправи. Той знае със сигурност и ако се наложи, ще мери.

@Binds + Конструктор за инжектиране

Мега удобен пакет, който значително намалява стандартния код. В зората на изучаването на Кинжал не разбрах защо са необходими инжекции с конструктор. Какво идва от къде. И тогава имаше @Binds. Но всъщност е доста просто. Благодаря за помощта Владимир Тагаков и тази статия.
Нека разгледаме типична ситуация. Има интерфейс на Presenter и неговото изпълнение:

Ние като бели хора ще предоставим цялото нещо в модула и ще инжектираме презентационния интерфейс в дейността:

Да кажем, че нашият FirstPresenter се нуждае от помощни класове, на които той делегира част от работата. За да направите това, трябва да създадете още два метода в модула, които ще предоставят нови класове, след това да промените конструктора FirstPresenter и следователно да актуализирате съответния метод в модула.
Модулът ще бъде по следния начин:

И така всеки път, ако трябва да добавите клас и да го „споделите“ с други. Модулът се замърсява много бързо. И някак твърде много код, не мислите ли? Но има решение, което значително намалява кода.
Първо, ако трябва да създадем зависимост и да дадем готов клас, а не интерфейс (HelperClass1 и HelperClass2), можем да прибегнем до инжектиране на конструктор.
Ще изглежда така:

Обърнете внимание, че анотацията @FirstScope е добавена към класовете, така че Dagger разбира към кое дърво на зависимости да присвои тези класове.
Сега можем безопасно да премахнем HelperClass1 и HelperClass2 от модула:

Как можете допълнително да намалите кода в модула? Нека приложим @Binds тук:

И в FirstPresenter, нека инжектираме конструктора:

Какви са иновациите тук?
HelperModule стана абстрактен за нас, точно като метода provideFirstPresenter. Анотацията @Provide е премахната от provideFirstPresenter, но е добавена @Binds. И в аргументите предаваме ненужни зависимости, но конкретно изпълнение!
FirstPresenter добави анотация на обхвата - @FirstScope, чрез която Dagger разбира къде да присвои този клас. Също така, анотацията @Inject е добавена към конструктора. .
Много по-чист и лесен за добавяне на нови зависимости!

Това, което още не е казано

Достъпни референции. Ако паметта наистина е проблем. С помощта на подходящи анотации маркираме предметите, които можем да жертваме, когато липсва памет. Ето хак.
В документите (подраздел Разрешими препратки) всичко е ясно описано, колкото и да е странно.

Тестване. Разбира се, Dagger не е необходим за Unit тестване. Но за функционални, интеграционни и потребителски тестове, възможността за заместване на определени модули може да бъде полезна. Artem_zin покрива тази тема много хладно в своята статия и пример. Документацията има подчертан раздел за тестване. Но отново, служителите на Google не могат да опишат правилно как да заменят компонент. Как правилно да създавате фалшиви модули и да ги замествате. За да заместя компонент (отделни модули), използвам метода на Артем. Да, бих искал да бъде възможно да се създаде тестов компонент в отделен клас и тестови модули в отделни класове и да се свърже всичко това красиво в тестовия файл на приложението. Може би някой знае?

@BindsOptionalOf. Работи заедно с Optional от Java 8 или Guava, което затруднява тази функция за нас. Ако се интересувате, можете да намерите описание в края на документацията.

  • @BindsInstance. За съжаление, в кинжал 2.8 тази функция не беше достъпна за мен. Основното му послание е, че е достатъчно да се предадат всякакви обекти през конструктора на модула. Много често срещан пример, когато глобалният контекст се предава през конструктора на AppComponent. Така че с тази анотация това няма да е необходимо. Има пример в края на документацията.

  • Това е! Изглежда, че успяхме да подчертаем всички моменти. Ако сте пропуснали нещо или не сте го описали достатъчно, пишете! Нека да го оправим. Също така препоръчвам групата Dagger2 в Telegram, където вашите въпроси няма да останат без отговор.
    Също така, правилното използване на библиотеката е много свързано с изчистената архитектура. И така, ето вашата архитектурна група. И да, скоро за AndroidDevPodcast се планира издание с тематична кинжал. следете новините!