Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Властелин модулей. Белгород

Властелин модулей. Белгород

8f1a6a9f39a0f45343eeff3bafa0bb15?s=128

Alexander Blinov

April 13, 2019
Tweet

Transcript

  1. Фото с bel.ru M ODULES L ORD OF THE THE

    1 Белгород 13.04.2019
  2. Александр БЛИНОВ !2

  3. 1 Обзор проблем 2 Картина “TO BE” 3 Переход от

    “AS IS” к “TO BE” 4 Динамические фиче-модули 5 Подведение итогов Содержание bit.ly/2DdVZTc Слайды 3
  4. Думайте своей головой и никому не доверяйте Disclaimer Photo by

    jesse orrico on Unsplash !4
  5. Обзор проблем и решений

  6. Модуляризация Зачем она нужна? Photo by Evan Dennis on Unsplash

    6
  7. Изоляция фичей Touched by His Noodly Appendage, a parody of

    Michelangelo's The Creation of Adam, is an iconic image of the Flying Spaghetti Monster[1] by Arne Niklas Jansson. 7
  8. Время сборки The Persistence of Memory Painting by Salvador Dali

    8
  9. Связи в приложении Module Module Module Module Module Module Module

    Module Module Module Module Module Module Module Зависимости сборки Иерархия view Время жизни скоупов 9
  10. Циклические зависимости Резюме Отклики Каким резюме откликнулись Список откликов От

    циклических зависимостей не уйти Photo by Bryson Hammer on Unsplash 10
  11. Толстофичи Резюме Отклики Каким резюме откликнулись Список откликов Толстофичи как

    черные дыры Резюме и отклики Photo by Jorge Zapata on Unsplash 11
  12. Photo by Mohit Tomar on Unsplash Картина “TO BE” 12

  13. Разбиение на фиче-модули 1 В фиче доступны все необходимые зависимости

    2 Легко отделить и тиражировать фичу для экспериментов 3 Наличие механизм навигации (диплинки / апплинки) 4 Низкая связность фичей Ожидания от подхода Стабильная работа системы 5 13
  14. Bussines vs Core Business Core •Логическая законченность •Максимальная независимость •Четкие

    внешние зависимости Решает конкретную бизнес задачу Практически не относится к бизнес логике приложения • Авторизация • Профиль • Резюме • Список вакансий • Base UI • List & Pagination • Metrics & Analytics • Network 14
  15. Слои модулей Feature 1 Feature 2 Feature 3 Feature 4

    App 1 App 2 Core Feature 2 Core Feature 3 Core Features Business Features App Core Feature 1 15
  16. External Deps Internal implementation Feature API Устройство business feature !16

  17. External Internal API External Internal API External Internal API Взаимодействие

    через Mediator External Deps Internal implementation Feature API APP Mediator Deps 17
  18. НЕЛЬЗЯ ПРОСТО ЗАПИЛИТЬ МОДУЛИ ТАК Кадр из трилогии “Властелин колец”

    18
  19. Пример работы Feature Position 19

  20. External Internal API APP Mediator External Internal API Deps MainSearchScreen

    PositionScreen Инициализация 20
  21. External Internal API APP Mediator External Internal API Deps interface

    PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) } 21
  22. External Internal API APP Mediator External Internal API Deps interface

    FeatureApi { fun getPositionFragment(): Fragment } interface FeatureApi { fun getPositionFragment(): Fragment //Допустимые внешние зависимости fun getSomeIntent(): SomeIntent fun getSomeInteractor(): SomeInteractor fun getSomeRepository(): SomeRepository // … } 22
  23. External Internal API APP Mediator External Internal API Deps class

    PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” } init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, ROOT_SCOPE) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(ROOT_SCOPE) } val api: FeatureApi = FeatureApiIml(ROOT_SCOPE) } 23
  24. External Internal API APP Mediator External Internal API Deps class

    PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” } init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, ROOT_SCOPE) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(ROOT_SCOPE) } val api: FeatureApi = FeatureApiIml(ROOT_SCOPE) } 24
  25. External Internal API APP Mediator External Internal API Deps class

    PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” } init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, ROOT_SCOPE) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(scopeName) } val api: FeatureApi = FeatureApiIml(ROOT_SCOPE) } 25
  26. PositionMediator PositionComponent API External Internal Component Holder Stub API PositionComponent

    API External Internal External Internal API APP Mediator External Internal API Deps MainSearchScreenMediator Component Holder MainSearchScreenComponent API External Stub API Internal 26
  27. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... } 27
  28. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... } 28
  29. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... } 29
  30. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... } 30
  31. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... } 31
  32. External Internal API APP Mediator External Internal API Deps class

    PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder: SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... } 32
  33. class PositionMediator(private val mediatorManager : MediatorManager) { private val componentHolder:

    SingleComponentHolder<PositionComponent, PositionDependencies> = SingleComponentHolder { deps -> PositionComponent(deps) } private fun provideComponent(): PositionComponent { if(!componentHolder.hasComponent()){ componentHolder.initComponent(object : PositionDependencies { override fun getCurrentPosition(): String { mediatorManager.mainSearchMediator.apiStub.positionInteractor.getPosition() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPosition(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... } PositionMediator External Internal API APP Mediator External Internal API Deps Component Holder PositionComponent API External Stub API Internal apiStub SingleComponentHolder PositionComponent PositionDependencies 33
  34. Dependency Injection Photo by Hush Naidoo on Unsplash !34

  35. Какой Dependency Injection Фреймворк Подойдет лучше всего? Photo by Aziz

    Acharki on Unsplash !35
  36. External Internal API APP Mediator External Internal API Deps Устройство

    DI External Deps Internal implementation Feature API Common Deps 36
  37. Feature Position Some Else Feature AppRootScope RootPositionScope AppScope RootSomeElseScope SomeElsePositionScope

    AbcScope GhiScope JklScope DefScope Глубина скоупов зависимостей External Deps Common Deps 37
  38. class AutosearchFragment : AutosearchView { @InjectPresenter lateinit var presenter: AutosearchPresenter

    @ProvidePresenter fun providePresenter(): AutosearchPresenter { return AutosearchDI.openAutosearchScope().getInstance(AutosearchPresenter::class.java) } DI внутри модуля 38
  39. Скоупы зависимостей в модулях AutosearchDI Other App part Instal Dependencies

    Open Scope Инициализация & хранение 1 0 AutosearchFragment 39
  40. Скоупы зависимостей в модулях AutosearchDI Other App part AutosearchFragment Kill

    static Open Scope Переоткрытие System Force Initializer 1 Need Autosearch Deps 2 Init Autosearch Deps 3 0 4 Instal Dependencies 40
  41. Photo by Alexander Andrews on Unsplash Навигация в приложении !41

  42. Внутри модуля Между модулями Старина Cicerone Smart Router A B

    State Machine C Навигация Через Dependencies Средства Android A B goo.gl/Tnqw2a goo.gl/tDmzEF 42
  43. External Internal API APP Mediator External Internal API Deps interface

    PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) fun onClosePositionScreen() } interface PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) fun onClosePositionScreen() } Закрытие фичи 43
  44. interface OnBackPressedListener { /** * @return true, если команда была

    обработана */ fun onBackPressed(): Boolean } interface OnBackPressable { fun addOnBackPressedListener(listener: OnBackPressedListener) fun removeOnBackPressedListener(listener: OnBackPressedListener) } BaseFragment : Fragment(), OnBackPressable, OnBackPressedListener{} Кнопка Back Обработка нажатия 44
  45. public class Resume { private Collection<SpecializationDto> specializations; private ProfessionalExperienceDto professionalExperience;

    private Set<ResumeCertificateDto> certificates; private AdditionalEducationDto additionalEducation; private BusinessTripReadiness businessTripsReadiness; private ContactInformationDto contactInformation; private RecommendationInfoDto recommendationInfo; private List<UserImageDto> userPortfolio; private EducationHistoryDto educationHistory; private RelocationInfoDto relocationInfo; private ResumeLanguageDto languages; private WorkExperience workExperience; private Set<Employment> employments; private EducationLevel education; private LocalDateTime creationTime; private LocalDateTime lastChangeTime; private Set<Schedule> schedules; private RoadTimeType roadTime; private ResumeStatus resumeStatus; private UserImageDto userImage; private AreaDto area; private AccessDto access; private LocalDate birthdayDate; private AreaDto[] citizenship; private AreaDto[] workTickets; private MoneyDto desirableCompensation; private MetroDto nearestMetro; private Gender gender; private String[] keySkills; private String hash; private String title; private String aboutMe; private int id; private int siteId; private boolean hasVehicle; private String[] driverLicense; //… } Core Applicant Entity Employer Entity Common Entity + Большие объекты 45
  46. Предметная область Applicant Entity Employer Entity Common Entity Общий функционал

    Фиче-модули User Job Position Push Suggestion University … Base UI List Processing Pagination Network Remote Config Custom Views View#1 View#2 View#3 … … Applicant Application Russia Azerbaijan Belarus Employer Application Russia Belarus Итоговая картина 46
  47. Кадр из трилогии “Властелин колец” План перехода 47

  48. 1 Команда 3 человека, которая увеличится до 10 2 2

    приложения на одной кодовой базе в (!) одном модуле 3 Multi-Activity подход и монолитные Activity и Fragment 4 Dagger 2 и пухлый ApplicationScope 5 Отсутствие Event Bus, наличие синглтонов 6 End2end тесты основных сценариев на голом Espresso Контекст 48
  49. overflow.io graphviz.org Карта экранов сценариев Анализ зависимостей Анализ слабых мест

    ШАГ №1 49
  50. Выбивание костылей должно регулярно тестироваться Тесты — это магия Реанимация

    UI тестов ШАГ №2 50
  51. Пилим монолит 51

  52. Application Applicant code Employer code Common code Кодовая база Иерархия

    Applicant code Employer code Common code Кодовая база Applicant Employer Common Иерархия Flavors Modules Разделение проектов ШАГ №3 52
  53. Dagger и гибкость DI фреймворки можно комбинировать Замена Dagger2 на

    Toothpick Подготовка DI ШАГ №4 53
  54. 1 Выносить Core фичи легко и весело 2 Core представляет

    иерархию модулей Вынос Core модулей ШАГ №5 54
  55. goo.gl/8QwRp4 Разные Activity должны связывать только сущности, сохраняемые системой Слезаем

    с Multi Activity ШАГ №6 55
  56. 1 Замена транзакции фрагментов на Cicerone 2 Замена Activity на

    Activity + Fragment Делаем механизм для роутинга (Smart Router) 3 Не занимайтесь внедрением MVP / MVVM / MVI на этом шаге Заменяем Activity + Fragment на Fragment 4 Слезаем с Multi Activity ШАГ №6 56
  57. Feature 1 Feature 2 Feature 3 Feature 4 App 2

    Core Feature 2 Core Feature 3 Core Features Business Features App Core Feature 1 App 1 Отпочкование фичей ШАГ №7 App 1 Feature 5 Feature 6 Feature 7 Feature 8 Feature 9 Feature ∞ 57
  58. class PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val

    ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” private val counter = AtomicInteger() } private val scopeName = ROOT_SCOPE + counter.incrementAndGet() init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, scopeName) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(scopeName) } val api: FeatureApi = FeatureApiIml(scopeName) } Переинициализация зависимостей PositionMediator Component Holder PositionComponent External Stub API API Internal 58
  59. class PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val

    ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” private val counter = AtomicInteger() } private val scopeName = ROOT_SCOPE + counter.incrementAndGet() init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, scopeName) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(scopeName) } val api: FeatureApi = FeatureApiIml(scopeName) } PositionMediator Component Holder PositionComponent External Stub API API Internal 59
  60. class PositionComponent(positionDependencies: PositionDependencies) { companion object { private const val

    ROOT_SCOPE = “${BuildConfig.APPLICATION_ID}_POSITION” private val counter = AtomicInteger() } private val scopeName = ROOT_SCOPE + counter.incrementAndGet() init { Toothpick.openScopes(APP_ROOT_SCOPE, APP_SCOPE, scopeName) .installModules(DependenciesModule(positionDependencies)) } fun destroyComponent() { Toothpick.closeScope(scopeName) } val api: FeatureApi = FeatureApiIml(scopeName) } PositionMediator Component Holder PositionComponent External Stub API API Internal 60
  61. class PositionScreenMediator() { private val componentHolder: MultiComponentHolder<PositionComponent, PositionDependencies> = MultiComponentHolder

    { deps -> PositionComponent(deps) } fun initComponent(key: String, deps: PositionDependencies) { componentHolder.initComponent(key, deps) } fun provideApiStub(key: String): FeatureApi { return componentHolder.provideComponent(key).api } // ... } PositionMediator Component Holder PositionComponent External Stub API API Internal 61
  62. class PositionScreenMediator() { private val componentHolder: MultiComponentHolder<PositionComponent, PositionDependencies> = MultiComponentHolder

    { deps -> PositionComponent(deps) } fun initComponent(key: String, deps: PositionDependencies) { componentHolder.initComponent(key, deps) } fun provideApiStub(key: String): FeatureApi { return componentHolder.provideComponent(key).api } // ... } PositionMediator Component Holder PositionComponent External Stub API API Internal 62
  63. class PositionScreenMediator() { private val componentHolder: MultiComponentHolder<PositionComponent, PositionDependencies> = MultiComponentHolder

    { deps -> PositionComponent(deps) } fun initComponent(key: String, deps: PositionDependencies) { componentHolder.initComponent(key, deps) } fun provideApiStub(key: String): FeatureApi { return componentHolder.provideComponent(key).api } // ... } PositionMediator Component Holder PositionComponent External Stub API API Internal 63
  64. PositionMediator Component Holder PositionComponent External Stub API API Internal class

    PositionScreenMediator() { private val componentHolder: MultiComponentHolder<PositionComponent, PositionDependencies> = MultiComponentHolder { deps -> PositionComponent(deps) } fun initComponent(key: String, deps: PositionDependencies) { componentHolder.initComponent(key, deps) } fun provideApiStub(key: String): FeatureApi { return componentHolder.provideComponent(key).api } // ... } Initializer 64
  65. Mediator Mediator Mediator Mediator Mediator Mediator Mediator Mediator Формирование медиаторов

    ШАГ №8 Неиерархичная связь 65
  66. Mediator Mediator Mediator Mediator Mediator Mediator Mediator Mediator Формирование медиаторов

    ШАГ №8 Иерархичная связь 66
  67. Mediator Mediator Mediator Mediator Mediator Mediator Mediator Mediator Формирование медиаторов

    ШАГ №8 Древовидная связь 67
  68. Feature 6 Feature 5 Feature 1 Feature 2 Feature 3

    Feature 4 App 1 App 2 Core Feature 2 Core Feature 3 Core Features Business Features App Core Feature 1 Mediator Формирование медиаторов ШАГ №8 Иерархичная связь 68
  69. Формирование медиаторов Отпочкование фичей Слезаем с Multi Activity Вынос Core

    модулей Подготовка DI Разделение проектов Реанимация UI тестов Анализ слабых мест №8 №7 №1 №2 №3 №4 №5 №6 Photo by Sven Mieke on Unsplash План перехода на модульную архитектуру 69
  70. Мысли и инсайды Кадр из трилогии “Властелин колец” 70

  71. Мысли и инсайды Создание тестовых App модулей для фичи Описание

    архитектуры в вики / статье для понимания Проведение экспериментов на Console проекте Время сборки и борьба с IDE 71
  72. gradlew headhunter-applicant:assembleHhruDebug / headhunter-employer:assembleHhruDebug / runAllUnitTests 72

  73. Динамические фиче-модули 73

  74. Причины использования Основной функционал 5 mb 40mb VR модуль 100%

    10% Частота использования Размер https://developer.android.com/guide/app-bundle/ https://developer.android.com/studio/projects/dynamic-delivery 74
  75. Feature 1 Feature 2 Feature 3 Feature 4 App 1

    App 2 Core Feature 1 Core Feature 2 Dynamic Delivery Feature 1 Новая иерархия модулей 75
  76. Dynamic Delivery Feature External Internal API Deps APP Передача зависимостей

    Динамические фичи 76
  77. Dynamic Delivery Feature Runner External API ? - Activity -

    Broadcast Receiver - Service - Content Provider APP Deps Dynamic Delivery Feature Internal Provider Передача зависимостей Динамические фичи 77
  78. Dynamic Delivery Feature APP Deps Dynamic Delivery Feature Runner External

    API Internal Provider Runner Manager Передача зависимостей Динамические фичи 78
  79. class SplitFeatureRunner(private val context: Context) { private val splitInstallManager: SplitInstallManager

    = SplitInstallManagerFactory.create(context).also { it.registerListener { status -> doSomething(status) } } fun installModule() { val request = SplitInstallRequest .newBuilder() .addModule(“dynamic_feature_vr”) .build(); splitInstallManager .startInstall(request) .addOnSuccessListener { sessionId -> Intent().setClassName(packageName, serviceClassName) .also { startService(it) } } .addOnFailureListener { exception -> // something on fail } } } Dynamic Delivery Feature Dynamic Delivery Feature Runner External API Runner Internal Provider Manager Загрузка модулей 79
  80. Dynamic Delivery Feature Dynamic Delivery Feature Runner External API Runner

    Internal Provider Manager class SplitFeatureRunner(private val context: Context) { private val splitInstallManager: SplitInstallManager = SplitInstallManagerFactory.create(context).also { it.registerListener { status -> doSomething(status) } } fun installModule() { val request = SplitInstallRequest .newBuilder() .addModule(“dynamic_feature_vr”) .build(); splitInstallManager .startInstall(request) .addOnSuccessListener { sessionId -> Intent().setClassName(packageName, serviceClassName) .also { startService(it) } } .addOnFailureListener { exception -> // something on fail } } } Загрузка модулей 80
  81. class SplitFeatureRunner(private val context: Context) { private val splitInstallManager: SplitInstallManager

    = SplitInstallManagerFactory.create(context).also { it.registerListener { status -> doSomething(status) } } fun installModule() { val request = SplitInstallRequest .newBuilder() .addModule(“dynamic_feature_vr”) .build(); splitInstallManager .startInstall(request) .addOnSuccessListener { sessionId -> Intent().setClassName(packageName, serviceClassName) .also { sartService(it) } } .addOnFailureListener { exception -> // something on fail } } } Dynamic Delivery Feature Dynamic Delivery Feature Runner External API Runner Internal Provider Manager Загрузка модулей 81
  82. class SplitFeatureRunner(private val context: Context) { private val splitInstallManager: SplitInstallManager

    = SplitInstallManagerFactory.create(context).also { it.registerListener { status -> doSomething(status) } } fun installModule() { val request = SplitInstallRequest .newBuilder() .addModule(“dynamic_feature_vr”) .build(); splitInstallManager .startInstall(request) .addOnSuccessListener { sessionId -> Intent().setClassName(packageName, serviceClassName) .also { startService(it) } } .addOnFailureListener { exception -> // something on fail } } } Dynamic Delivery Feature Dynamic Delivery Feature Runner External API Runner Internal Provider Manager Загрузка модулей 82
  83. Dynamic Delivery Feature Dynamic Delivery Feature Runner External API Runner

    Internal Provider Manager object DynamicFeatureModuleManager { private var listenerSet: MutableSet<CheckInstallListener> = mutableSetOf() fun setLauncher(launcher: DynamicFeatureLauncher) { for (listener in listenerSet) { listener.onCheckInstalled(launcher) } } } fun addListener(checkInstallListener: CheckInstallListener) { listenerSet.add(checkInstallListener) } // . . . } open class LaunchModuleService : IntentService("LaunchModuleService") { override fun onHandleIntent(intent: Intent?) { DynamicFeatureModuleManager.setLauncher(DynamicFeatureLauncherImpl()) } } Загрузка модулей 83
  84. Dynamic Delivery Feature Dynamic Delivery Feature Runner External API Runner

    Internal Provider Manager object DynamicFeatureModuleManager { private var listenerSet: MutableSet<CheckInstallListener> = mutableSetOf() fun setLauncher(launcher: DynamicFeatureLauncher) { for (listener in listenerSet) { listener.onCheckInstalled(launcher) } } } fun addListener(checkInstallListener: CheckInstallListener) { listenerSet.add(checkInstallListener) } // . . . } open class LaunchModuleService : IntentService("LaunchModuleService") { override fun onHandleIntent(intent: Intent?) { DynamicFeatureModuleManager.setLauncher(DynamicFeatureLauncherImpl()) } } Загрузка модулей 84
  85. 1 Android App Bundles 2 Конфликтует с sideloading 3 Конфликтует

    с инструментами меняющими таблицу ресурсов 4 Для работы необходимы Android 5.0 (API level 21) и последняя версия приложения Play Store 5 Прочие баги, ограничения и недоработки https://developer.android.com/guide/app-bundle/#known_issues Ограничения С сайта Android Developers 85
  86. 1 Moxy 2 Фреймворки, которые работают аналогичным образом Ограничения Совместимость

    с фреймворками 86
  87. Feature 1 Feature 2 Feature 3 Feature 4 App 1

    App 2 Core Feature 1 Core Feature 2 Dynamic Delivery Feature 1 Dynamic Delivery Feature Runner 1 50± kb 40mb Динамические фиче-модули Выводы 87
  88. Подведение итогов 88

  89. Подход решает поставленные задачи Концепция сложная и подойдет для зрелых

    проектов Выводы Экспериментируйте. В экспериментах рождается истина 89
  90. Ч Т О Е Щ Ё !90

  91. Mobius 2018 Академия Яндекса habr.com Mobius 2018 Mobius 2018 Mobius

    2019 91
  92. Выводы Подход решает поставленные задачи Концепция сложная и подойдет для

    зрелых проектов Экспериментируйте. В экспериментах рождается истина Слайды Обзор проблем Картина “TO BE” Подведение итогов Переход от “AS IS” к “TO BE” Динамические фиче-модули О чем мы говорили 92
  93. БЛИНОВ Александр @xanderblinov @xanderblinov !93