Dependency injection is an amazing concept that has become a critical part of application architecture within the recent years. Long gone are the days of long method signatures or random objects to hold dependencies to avoid said long method signatures. Thanks to dependency injection libraries, such as Dagger and Koin, a lot of boilerplate work is done for us. It’s almost to the point of where it feels magical. Now, with the coming of Hilt, things are even more obscure.
依赖注入是一个了不起的概念,近年来已成为应用程序体系结构的关键部分。 避免依赖长方法签名的长方法签名或随机对象拥有依赖关系的日子已经一去不复返了。 多亏了依赖注入库,例如Dagger和Koin ,为我们完成了许多样板工作。 几乎到了感觉神奇的地步。 现在,随着Hilt的到来,事情变得更加模糊。
Dagger 2 is one of the frameworks that came across as mystery to me. After hours of exploring the compiled code after creating various modules, components, and submodules it started to make some sense. Then the question pondered in my head,
Dagger 2是我感到不可思议的框架之一。 在创建了各种模块,组件和子模块后,经过数小时的探索后,已开始有意义。 然后这个问题浮现在我的脑海,
what are the memory implications of all this?
所有这些对内存的影响是什么?
I’ve seen countless projects where one huge component is built at the time of application context creation and used throughout the entire lifecycle of the application. I thought to myself,
我见过无数的项目,这些项目在创建应用程序上下文时会构建一个巨大的组件,并在应用程序的整个生命周期中使用它。 我心想,
Wait… This can’t be the best way to go about this. Some of these dependencies may need to live for an extended period of time, but not the entire time…
等等...这不是实现此目的的最佳方法。 其中一些依赖项可能需要生存很长时间,而不是整个生存期……
That brings us to the use of scopes and subcomponents. There are plenty of resources out there to explain how to use the scopes and subcomponents properly, so I won’t make that a focus here. Here are a couple that I found to be particularly helpful.
这使我们可以使用范围和子组件。 有大量的资源来解释如何正确使用范围和子组件,因此在这里我不会重点介绍。 我发现这对夫妇特别有帮助。
Some may be thinking, “my code works with the one component set up and it looks less complicated, so who cares?!” Therefore, it’s time to answer the question, “what are the memory implications of all this?”
有人可能会想:“我的代码使用了一个组件,但看起来并不那么复杂,所以谁在乎呢!!” 因此,是时候回答这个问题了,“所有这些对内存的影响是什么?”
I created a simple Android application that would allow for the investigation of the memory consumed by three different Dagger implementations for the same functionality.
我创建了一个简单的Android应用程序,该应用程序可以调查三种针对相同功能的Dagger实现所消耗的内存。
I implemented the dependency injection in the following ways using Gradle flavors to separate the implementations.
我通过以下方式使用Gradle方式实现了依赖注入,以分离实现。
One component 一个组成部分 Multiple components多种成分Main component with Subcomponents主组件与子组件You can find the full source of the project here.
您可以在此处找到该项目的完整源代码。
The application displays greetings and farewells in two different languages, French and Spanish. This provided a simple enough use case to showcase the use of scopes and the objects living in each scope, depending on the implementation. For this experiment, the garbage collection was forced between each transition to assist in showing which objects were kept alive. The following illustration shows the interaction with the application used to test each implementation.
该应用程序以两种不同的语言(法语和西班牙语)显示问候语和告别语。 这提供了一个足够简单的用例,以展示范围的使用以及每个范围中的对象,具体取决于实现。 对于此实验,在每次转换之间强制进行垃圾收集,以帮助显示哪些对象保持活动状态。 下图显示了与用于测试每个实现的应用程序的交互。
The interaction flow is as follows:
交互流程如下:
User launches app and is shown the Welcome screen 用户启动应用程序并显示欢迎屏幕 User navigates to the Greeting screen用户导航到“问候语”屏幕User navigates to the French Greeting screen用户导航到“法语问候语”屏幕User navigates to the Spanish Greeting screen用户导航到西班牙语问候语屏幕User navigates back to the Welcome screen用户导航回到“欢迎”屏幕User navigates to the Farewell screen用户导航到“告别”屏幕User navigates to the French Farewell screen用户导航到法语告别屏幕User navigates to the Spanish Farewell screen用户导航到西班牙告别屏幕User exits app landing on the Home screen用户在主屏幕上退出应用程序着陆Now, that the user scenario is established, the three different Dagger implementations are implemented in the following way:
现在,已经建立了用户场景,以下三种方式实现了三种不同的Dagger实现:
You can see the full implementation of this component here.
您可以在此处看到此组件的完整实现。
@Singleton @Component(modules = [AppModule::class, FarewellModule::class, GreetingModule::class, WelcomeModule::class]) interface AppComponent: BaseWelcomeComponent, BaseFarewellComponent, BaseGreetingComponentThe one component set up has only one main component that uses every module created for the app resulting in every scoped dependency living for the entire life span of the application.
一个组件设置只有一个主要组件,该组件使用为应用程序创建的每个模块,从而在应用程序的整个生命周期中都存在每个作用域依赖项。
The single app component is initialized in the application context creation.
在应用程序上下文创建中初始化单个应用程序组件。
override fun onCreate() { super.onCreate() instance = this appComponent = DaggerAppComponent.builder().build() DaggerSamplerAppHelper().initializeComponents(this, appComponent) }You will notice that the DaggerSamplerAppHelper in this one component flavor does nothing, because there are no other components to build.
您会注意到,在这一组件风格中,DaggerSamplerAppHelper不会执行任何操作,因为没有其他组件可以构建。
class DaggerSamplerAppHelper { fun initializeComponents(appContext: DaggerSamplerApp, appComponent: AppComponent) { // Do nothing } }You can see the full implementation of this component here.
您可以在此处看到此组件的完整实现。
The multiple component set up has multiple scoped components in addition to the app component that uses only the modules required for that component’s purpose(s). This set up results in every scoped dependency living for the entire life span of the component that it belongs to.
除了仅使用用于该组件目的的模块的应用程序组件外,该多个组件设置还具有多个作用域组件。 这种设置导致每个范围内的依赖项在其所属组件的整个生命周期中均有效。
@Singleton @Component(modules = [AppModule::class]) interface AppComponent { fun getAppProviderInfo(): AppInfoProvider } @WelcomeScope @Component(dependencies = [AppComponent::class], modules = [WelcomeModule::class]) interface WelcomeComponent : BaseWelcomeComponent @GreetingScope @Component(dependencies = [AppComponent::class], modules = [GreetingModule::class]) interface GreetingComponent: BaseGreetingComponent @FarewellScope @Component(dependencies = [AppComponent::class], modules = [FarewellModule::class]) interface FarewellComponent: BaseFarewellComponentFor this experiment, the multiple components are still initialized at application creation. This is better for organization and component responsibility purposes, but not better for memory purposes. This will technically still result in every scoped dependency living for the entire life span of the application.
对于本实验,在创建应用程序时仍会初始化多个组件。 这对于组织和组件责任的目的更好,但是对于存储目的则更好。 从技术上讲,这仍将导致每个范围的依赖项在应用程序的整个生命周期中都有效。
class DaggerSamplerAppHelper { fun initializeComponents(appContext: DaggerSamplerApp, appComponent: AppComponent) { val welcomeComponent = DaggerWelcomeComponent.builder().appComponent(appComponent).build() appContext.setWelcomeComponent(object : ComponentProvider<WelcomeComponent> { override fun provideComponent(): WelcomeComponent? = welcomeComponent }) val greetingComponent = DaggerGreetingComponent.builder().appComponent(appComponent).build() appContext.setGreetingComponent(object : ComponentProvider<GreetingComponent> { override fun provideComponent(): GreetingComponent? = greetingComponent }) val farewellComponent = DaggerFarewellComponent.builder().appComponent(appComponent).build() appContext.setFarewellComponent(object : ComponentProvider<FarewellComponent> { override fun provideComponent(): FarewellComponent? = farewellComponent }) } }You can see the full implementation of the app component and the sub components here.
您可以在此处查看app组件和子组件的完整实现。
The subcomponent set up has multiple scoped subcomponents in addition to the app component that uses only the modules required for that component’s purpose(s). This set up results in every scoped dependency living for the entire life span of the component/subcomponent that it belongs to.
除了仅使用该组件用途所需的模块的app组件之外,该子组件设置还具有多个作用域子组件。 这种设置导致每个范围内的依赖项在它所属的组件/子组件的整个生命周期中都存在。
@Singleton @Component(modules = [AppModule::class]) interface AppComponent { fun getWelcomeComponentFactory(): WelcomeComponent.Factory fun getGreetingComponentFactory(): GreetingComponent.Factory fun getFarewellComponentFactory(): FarewellComponent.Factory } @Subcomponent(modules = [WelcomeModule::class]) interface WelcomeComponent : BaseWelcomeComponent { @Subcomponent.Factory interface Factory { fun create(): WelcomeComponent } } @GreetingScope @Subcomponent(modules = [GreetingModule::class]) interface GreetingComponent: BaseGreetingComponent { @Subcomponent.Factory interface Factory { fun create(): GreetingComponent } } @FarewellScope @Subcomponent(modules = [FarewellModule::class]) interface FarewellComponent: BaseFarewellComponent { @Subcomponent.Factory interface Factory { fun create(): FarewellComponent } }For this experiment, the app component is still initialized at application creation. However, the subcomponents are only initialized when required and are cleared when no longer needed. This is better for organization and component responsibility purposes and also better for memory purposes. This will result in every scoped dependency living for the life span of the object(s) that uses the component. Here is an example of how the subcomponent is dynamically used:
对于此实验,仍然在创建应用程序时初始化应用程序组件。 但是,仅在需要时才初始化子组件,并在不再需要时将其清除。 这对于组织和组件责任的目的更好,也对于存储目的更好。 这将导致每个范围内的依赖项在使用该组件的对象的生命周期内有效。 这是一个动态使用子组件的示例:
class GreetingHelper(private val appContext: DaggerSamplerApp) { fun setComponent() { val greetingComponent = appContext.appComponent.getGreetingComponentFactory().create() appContext.setGreetingComponent(object : ComponentProvider<GreetingComponent> { override fun provideComponent(): GreetingComponent? = greetingComponent }) } fun unsetComponent() { appContext.setGreetingComponent(null) } fun injectDependencies(greetingFragment: GreetingFragment) { appContext.getGreetingComponent()?.inject(greetingFragment) } fun injectDependencies(frenchGreetingFragment: FrenchGreetingFragment) { appContext.getGreetingComponent()?.inject(frenchGreetingFragment) } fun injectDependencies(spanishGreetingFragment: SpanishGreetingFragment) { appContext.getGreetingComponent()?.inject(spanishGreetingFragment) } }Now that the setup of the experiment is explained, let’s see the results of each implementation throughout the user interaction explained previously.
既然已经说明了实验的设置,那么让我们看一下前面解释的整个用户交互过程中每个实现的结果。
The following objects were tracked for memory purposes along with their scopes. “N/A” means the object is not scoped and will get cleaned when no longer used.
为了存储目的,跟踪了以下对象及其作用域。 “ N / A”表示该对象没有范围,并且不再使用时将被清除。
The scopes mentioned here are activated during these parts of the user interaction flow.
在用户交互流程的这些部分中,将激活此处提到的范围。
Singleton — Step 1 at the time of application context creation Singleton —创建应用程序上下文时的步骤1 Greeting — Step 2 when user navigates to greeting screen (Ends at Step 5 for subcomponent implementation when user navigates back to the welcome screen) 问候语-用户导航到问候语屏幕时的步骤2(当用户导航回到欢迎屏幕时,在步骤5结束子组件的实现) Farewell — Step 6 when user navigates to farewell screen (Ends at Step 9 for subcomponent implementation when user exits app) 告别-用户导航到告别屏幕时的步骤6(在退出应用程序时在步骤9结束子组件的实现)The following charts will illustrate the findings when analyzing the memory heap using the Android Studio profiler in each step of the user interaction flow. You can find the raw data, including the memory dumps and screenshots of the memory dumps for each implementation here.
下图将说明在用户交互流程的每个步骤中使用Android Studio分析器分析内存堆时的发现。 您可以在此处找到原始数据,包括内存转储和每种实现的内存转储的屏幕截图。
This chart above shows the memory footprint of each object that is being tracked. In each screen of the user interaction flow, you can notice which objects are still alive in memory for each implementation. Recall the scopes in the previous table to see how the life span of each object is affected by the scope and implementation.
上面的这张表显示了正在跟踪的每个对象的内存占用量。 在用户交互流程的每个屏幕中,您可以注意到每个实现中在内存中仍存在哪些对象。 回顾上表中的作用域,以了解每个对象的寿命如何受到作用域和实现的影响。
Now, we start answering the question that we asked in the beginning, “What are the memory implications?” The chart above shows the total memory in bytes of the tracked objects during each step of the user interaction flow. The impact of each implementation is more noticeable as the user interacts with more screens in the app and scoped dependencies are handled differently. Here we are showing a small amount of data, so let’s take a look at the possible space savings in percentages of the tracked objects.
现在,我们开始回答开始时提出的问题:“内存的含义是什么?” 上表显示了在用户交互流程的每个步骤期间,跟踪对象的总内存(以字节为单位)。 当用户与应用程序中的更多屏幕进行交互并且作用域依赖项的处理方式不同时,每种实现的影响会更加明显。 在这里,我们显示的是少量数据,因此让我们看一下跟踪对象所占空间的可能节省量。
Wow! This experiment has exposed that proper usage of scoped subcomponents (or components not tied to the life cycle of the application) can result in up to a 67 percent space savings at one point of the user interaction flow. This is very specific for this example in the experiment. However, it shows how there can be major memory improvements in larger applications depending on the implementation of Dagger in a project.
哇! 该实验表明,适当使用范围内的子组件(或与应用程序的生命周期无关的组件)可以在用户交互流的某一点节省多达67%的空间。 这对于实验中的这个示例非常具体。 但是,它显示了如何根据项目中Dagger的实现在大型应用程序中进行重大内存改进。
In this post, a question was posed on the memory implications of different implementations of Dagger 2 dependency injection. An experiment using a simple application was set up and conducted to find an answer to that question. The results of the experiment showed there can be major improvements in memory performance with proper use of Dagger scopes and submodules. I hope you have found this useful. Be Curious. Ask Questions. Find Answers!
在这篇文章中,有人提出了有关Dagger 2依赖项注入的不同实现的内存含义的问题。 建立了一个使用简单应用程序进行的实验,以找到该问题的答案。 实验结果表明,正确使用Dagger示波器和子模块可以显着提高内存性能。 我希望您发现这很有用。 保持好奇心。 问问题。 寻找答案!
翻译自: https://medium.com/vmware-end-user-computing/dagger-scopes-subcomponents-memory-and-who-cares-19d1ccb51305
相关资源:浅析早期玉匕首的起源:简析