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

Властелин модулей (Mobius)

8f1a6a9f39a0f45343eeff3bafa0bb15?s=47 Alexander Blinov
December 08, 2018
1.4k

Властелин модулей (Mobius)

8f1a6a9f39a0f45343eeff3bafa0bb15?s=128

Alexander Blinov

December 08, 2018
Tweet

Transcript

  1. #Mobius #hh_ru L ORD M ODULES OF THE THE

  2. Обо мне Александр Блинов Руководитель Android направления

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

    “AS IS” к “TO BE” 4 Динамические фиче-модули 5 Подведение итогов Слайды Содержание 3 goo.gl/sQMfda
  4. Обзор проблем и решений 4

  5. Модуляризация и зачем она нужна 1 Изоляция фичей 5 2

    Скорость сборки
  6. Связи в приложении 6 Module Module Module Module Module Module

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

    От циклических зависимостей не уйти
  8. Резюме и отклики Резюме Отклики Каким резюме откликнулись Список откликов

    Толстофичи 8 Толстофичи как черные дыры
  9. Картина “TO BE” 9

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

    2 Код фичи легко отделить и тиражировать для экспериментов 3 Наличие механизм навигации (диплинки / апплинки) 4 10 Низкая связность фичей Ожидания от подхода
  11. Что такое Feature? 11 Business Feature Core Feature Характеристика Программного

    модуля • Логически законченный • Максимально независимый • Имеет четко обозначенные внешние зависимости • Решает конкретную бизнес задачу • Логически законченный • Максимально независимый • Имеет четко обозначенные внешние зависимости • Практически не относится к бизнес логике приложения Пример фичи • Авторизация • Профиль • Резюме • Base UI • List & Pagination • Metrics & Analytics • Network Логические слои Включает все слои (по clean architecture) логики Произвольные части Программный модуль со следующими характеристиками:
  12. Слои модулей Feature 1 Feature 2 Feature 3 Feature 4

    App 1 App 2 Core Feature 2 Core Feature 3 Core Features Business Features App 12 Core Feature 1
  13. External Deps Internal implementation Feature API Business Feature 13 Устройство

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

    через Mediator External Deps Internal implementation Feature API APP Mediator Deps 14 Business Feature
  15. Архтиктура готова? Нельзя так просто запилить модули!!! Архитектура готова? 15

  16. Устройство фичи Пример работы Feature Position 16

  17. Инициализация фичи 17 External Internal API APP Mediator External Internal

    API Deps MainSearchScreen PositionScreen
  18. Инициализация фичи 18 External Internal API APP Mediator External Internal

    API Deps interface PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) }
  19. Инициализация фичи 19 External Internal API APP Mediator External Internal

    API Deps goo.gl/BQHXEM goo.gl/9c61dz goo.gl/rej6kN 1 Dagger 2 2 Toothpick 3 . . .
  20. Feature Position Some Else Feature Глубина скоупов зависимостей 20 AppRootScope

    PositionDepsScope AppScope SomeElseScope SomeElsePositionScope AbcScope GhiScope JklScope DefScope
  21. Инициализация фичи 21 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. Инициализация фичи 22 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. Инициализация фичи 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) }
  24. Инициализация фичи 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(scopeName) } val api: FeatureApi = FeatureApiIml(ROOT_SCOPE) }
  25. Инициализация фичи 25 External Internal API APP Mediator External Internal

    API Deps MainSearchScreenMediator Component Holder MainSearchScreenComponent API External PositionMediator Component Holder Stub API Stub API Internal PositionComponent API External Internal PositionComponent API External Internal
  26. Инициализация фичи 26 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.getPositionFragment() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPositionFragment(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  27. Инициализация фичи 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.getPositionFragment() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPositionFragment(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  28. Инициализация фичи 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.getPositionFragment() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPositionFragment(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  29. Инициализация фичи 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.getPositionFragment() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPositionFragment(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  30. Инициализация фичи 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.getPositionFragment() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPositionFragment(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  31. Инициализация фичи 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.getPositionFragment() } override fun setCurrentPosition(p: String) { mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPositionFragment(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... }
  32. 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.getPositionFragment() } override fun setCurrentPosition(p: String){ mediatorManager.mainSearchMediator.apiStub.positionInteractor.setPositionFragment(p) } }) } return componentHolder.provideComponent(deps) } val apiStub = object : FeatureApi { override fun getPositionFragment(): Fragment { return provideComponent().api.getPositionFragment() } } // ... } Инициализация фичи PositionMediator Component Holder PositionComponent API External Stub API Internal 32 External Internal API APP Mediator External Internal API Deps apiStub SingleComponentHolder PositionComponent PositionDependencies
  33. Навигация 1 Навигация в фичи и App модулях 2 Навигация

    за предел фичи через Deps Старина Cicerone Smart Router A B 33 Основные принципы goo.gl/Tnqw2a State Machine C goo.gl/tDmzEF
  34. interface PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) fun

    onClosePositionScreen() } interface PositionDependencies { fun getCurrentPosition(): String fun setCurrentPosition(position: String) fun onClosePositionScreen() } 34 Навигация External Internal API APP Mediator External Internal API Deps Закрытие фичи
  35. Навигация 35 Туда и обратно interface OnBackPressedListener { /** *

    @return true, если команда была обработана */ fun onBackPressed(): Boolean } interface OnBackPressable { fun addOnBackPressedListener(listener: OnBackPressedListener) fun removeOnBackPressedListener(listener: OnBackPressedListener) } BaseFragment : Fragment(), OnBackPressable, OnBackPressedListener{}
  36. Навигация 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; //… } Большие объекты 36 Core Applicant Entity Employer Entity Common Entity +
  37. Итоговая картина Предметная область 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 37
  38. Переход от “AS IS” к “TO BE” 38

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

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

    Анализ слабых мест
  41. Шаг 0. Реанимация тестов 41 Выбивание костылей должно регулярно тестироваться

    Тесты — это магия
  42. 42 Разделяем монолит

  43. Шаг 1. Отделение Applicant и Employer 43 Application Applicant code

    Employer code Common code Кодовая база Иерархия Applicant code Employer code Common code Кодовая база Applicant Employer Common Иерархия Flavors Modules
  44. Шаг 2. Подготовка DI Dagger и гибкость 44 DI фреймворки

    можно комбинировать Временная(?) Замена Dagger2 на Toothpick
  45. Шаг 3. Вынос Core модулей 45 1 Выносить Core фичи

    легко и весело 2 Core представляет иерархию модулей
  46. 46 goo.gl/8QwRp4 Разные Activity должны связывать только сущности, сохраняемые системой

    Шаг 4. Слезаем с Multi Activity Зачем?
  47. 47 Шаг 4. Слезаем с Multi Activity Действия 1 Замена

    транзакции фрагментов на Cicerone 2 Замена Activity на Activity + Fragment Делаем механизм для роутинга (Smart Router) 3 Не занимайтесь внедрением MVP / MVVM / MVI на этом шаге Заменяем Activity + Fragment на Fragment 4
  48. Шаг 5. Отпочкование фичей Feature 1 Feature 2 Feature 3

    Feature 4 App 2 Core Feature 2 Core Feature 3 Core Features Business Features App 48 Core Feature 1 App 1 App 1 Feature 5 Feature 6 Feature 7 Feature 8 Feature 9 Feature ∞ Толстяк App модуль
  49. 49 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) } Переинициализация зависимостей Шаг 5. Отпочкование фичей Переиспользование PositionMediator Component Holder PositionComponent External Stub API API Internal
  50. 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) } 50 Шаг 5. Отпочкование фичей Переиспользование PositionMediator Component Holder PositionComponent External Stub API API Internal
  51. 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 51 Шаг 5. Отпочкование фичей Переиспользование
  52. 52 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 } // ... } Шаг 5. Отпочкование фичей Переиспользование PositionMediator Component Holder PositionComponent External Stub API API Internal
  53. 53 Шаг 5. Отпочкование фичей Переиспользование 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 } // ... }
  54. 54 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 } // ... } Шаг 5. Отпочкование фичей Переиспользование PositionMediator Component Holder PositionComponent External Stub API API Internal
  55. 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 } // ... } 55 Шаг 5. Отпочкование фичей Переиспользование PositionMediator Component Holder PositionComponent External Stub API API Internal Initializer
  56. Шаг 6. Формирование медиаторов 56 Зависимости Mediator Mediator Mediator Mediator

    Mediator Mediator Mediator Mediator
  57. Mediator Mediator Mediator Mediator Шаг 6. Формирование медиаторов 57 Зависимости

    (Возможная картина) Mediator Mediator Mediator Mediator
  58. Feature 5 Feature 6 Шаг 6. Формирование медиаторов 58 Возможная

    (!) иерархия фичей 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
  59. Мысли и инсайды 59

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

    Описание архитектуры в вики / статье для понимания Проведение экспериментов на Console проекте Время сборки и борьба с IDE
  61. Динамические фиче-модули 61

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

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

    App 1 App 2 Core Feature 1 Core Feature 2 Dynamic Delivery Feature 1 Всё пропало, шеф
  64. 64 Dynamic Delivery Feature External Internal API APP Deps Взаимодействие

    dynamic feature
  65. ? - Activity - Broadcast Receiver - Service - Content

    Provider 65 Dynamic Delivery Feature APP Deps Dynamic Delivery Feature Runner External API Internal Взаимодействие dynamic feature
  66. 66 Dynamic Delivery Feature APP Deps Dynamic Delivery Feature Runner

    External API Internal Provider Взаимодействие dynamic feature Runner Manager
  67. Загрузка модулей 67 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
  68. Dynamic Delivery Feature Dynamic Delivery Feature Runner External API Runner

    Internal Provider Manager Загрузка модулей 68 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 } } }
  69. Загрузка модулей 69 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
  70. Dynamic Delivery Feature Dynamic Delivery Feature Runner External API Runner

    Internal Provider Manager Загрузка модулей 70 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 } } }
  71. Dynamic Delivery Feature Dynamic Delivery Feature Runner External API Runner

    Internal Provider Manager 71 Взаимодействие dynamic feature 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()) } }
  72. Dynamic Delivery Feature Dynamic Delivery Feature Runner External API Runner

    Internal Provider Manager 72 Взаимодействие dynamic feature 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()) } }
  73. Ограничения 73 1 Android App Bundles 2 Не поддерживает “APK

    expansion files” и APK > 100 mb 3 Конфликтует с инструментами меняющими таблицу ресурсов 4 Для работы необходимы Android 5.0 (API level 21) и последняя версия приложения Play Store 5 Прочие баги, ограничения и недоработки На сайте Android Developers https://developer.android.com/guide/app-bundle/#known_issues
  74. Ограничения 74 1 Moxy 2 Фреймворки, которые работают аналогичным образом

    Что еще
  75. Динамические фиче-модули 75 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 Выводы
  76. 76 Подведение итогов

  77. Самое сложное - выстроить связи модулей. Выстроили = Победили Концепция

    сложная и подойдет для зрелых проектов Экспериментируйте. В экспериментах рождается истина Выводы 77
  78. Выводы Самое сложное - выстроить связи модулей. Выстроили = Победили

    Концепция сложная и подойдет для зрелых проектов Экспериментируйте. В экспериментах рождается истина 78 Обзор проблем Картина “TO BE” Подведение итогов Слайды Контакты: Переход от “AS IS” к “TO BE” Динамические фиче-модули О чем мы говорили t.me/xanderblinov