依赖注入 (DI) 是一种用于实现控制反转 (IoC) 的设计模式,其中创建和管理依赖项的控制从应用程序转移到外部实体。这有助于创建更模块化、可测试和可维护的代码。这是一种将创建对象的责任转移到代码其他部分的技术。这促进了松散耦合,使代码更加模块化且更易于管理。
类通常需要引用其他类才能正常运行。例如,假设Library
类需要Book
类。这些必需的类称为依赖项。Library Library
依赖于Book
类的实例才能运行。
类获取其所需对象的主要方式有三种:
Library
类将创建并初始化自己的Book
类实例。Context
getters 和getSystemService()
以这种方式工作。Library
构造函数将接收Book
实例作为参数。第三个选项是依赖注入!使用 DI,您可以提供类的依赖项,而不是让类实例本身获取它们。
如果没有 DI,创建自己的Book
依赖项的Library
可能如下所示:
class Library { private Book book = new Book(); void open() { book.read(); } } public class Main { public static void main(String[] args) { Library library = new Library(); library.open(); } }
这不是 DI 的示例,因为Library
类构造了自己的Book
。这可能会有问题,因为:
Library
和Book
紧密耦合。Library Library
一个实例使用一种Book
类型,因此很难使用子类或其他实现。Book
的严格依赖使测试更具挑战性。Library Library
Book
的真实实例,从而阻止使用测试替身来修改Book
以适应不同的测试用例。使用 DI, Library
的每个实例不再构造自己的Book
对象,而是在其构造函数中接收Book
对象作为参数:
class Library { private Book book; Library(Book book) { this.book = book; } void open() { book.read(); } } public class Main { public static void main(String[] args) { Book book = new Book(); Library library = new Library(book); library.open(); }
main 函数使用Library
。由于Library
依赖于Book
,因此应用程序会创建Book
的一个实例,然后使用它来构造Library
的一个实例。这种基于 DI 的方法有以下好处:
Library
的可重用性:您可以将Book
的不同实现传递给Library
。例如,您可以定义一个名为EBook
的Book
新子类,并希望Library
使用。使用 DI,您只需将EBook
的一个实例传递给Library
,它就可以正常工作,无需进一步更改。Library
:您可以传递测试替身来测试不同的场景。考虑这样一种情况,即NotificationService
类依赖于Notification
类。如果没有 DI, NotificationService
会直接创建Notification
的实例,这使得使用不同类型的通知或使用各种通知实现测试服务变得很困难。
为了说明 DI,让我们重构这个例子:
interface Notification { void send(); } class EmailNotification implements Notification { @Override public void send() { // Send email notification } } class SMSNotification implements Notification { @Override public void send() { // Send SMS notification } } class NotificationService { void sendNotification(Notification notification) { notification.send(); } }
现在, NotificationService
依赖于Notification
接口,而不是特定的类。这允许可以互换使用不同的Notification
实现。您可以通过sendNotification
方法设置要使用的实现:
NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification());
DI 主要有三种类型:
class NotificationService { private final Notification notification; public NotificationService(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(new EmailNotification()); service.sendNotification(); } }
3. 字段注入(或 Setter 注入) :某些 Android 框架类(例如 Activity 和 Fragment)由系统实例化,因此无法进行构造函数注入。使用字段注入,依赖项在类创建后实例化。
class NotificationService { private Notification notification; public Notification getNotification() { return notification; } public void setNotification(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(); service.setNotification(new EmailNotification()); service.sendNotification(); } }
4. 方法注入:通过方法提供依赖项,通常使用@Inject
注解。
在上一个示例中,您手动创建、提供和管理了不同类的依赖项,而无需使用库。这种方法称为手动依赖项注入。虽然它适用于简单的情况,但随着依赖项和类的数量增加,它会变得繁琐。手动依赖项注入有几个缺点:
库可以通过为您创建和提供依赖项来自动化此过程。这些库分为两类:
Dagger 是一个流行的 Java、Kotlin 和 Android 依赖注入库,由 Google 维护。Dagger 通过为您创建和管理依赖关系图来简化应用中的 DI。它提供完全静态的编译时依赖关系,解决了与基于反射的解决方案(如 Guice)相关的许多开发和性能问题。
这些框架在运行时连接依赖项:
这些框架在编译时生成连接依赖项的代码:
依赖注入的替代方案是服务定位器模式。此设计模式还有助于将类与其具体依赖项分离。您可以创建一个称为服务定位器的类,该类可创建和存储依赖项,并根据需要提供它们。
object ServiceLocator { fun getProcessor(): Processor = Processor() } class Computer { private val processor = ServiceLocator.getProcessor() fun start() { processor.run() } } fun main(args: Array<String>) { val computer = Computer() computer.start() }
服务定位器模式与依赖注入的区别在于依赖项的使用方式。使用服务定位器模式时,类会请求所需的依赖项;使用依赖注入时,应用会主动提供所需的对象。
Dagger 2 是一款流行的 Android DI 框架。它使用编译时代码生成,并以其高性能而闻名。Dagger 2 通过生成处理依赖项所需的代码来简化依赖项注入过程,从而减少样板并提高效率。
Dagger 2 是一个基于注释的 Android 依赖注入库。以下是关键注释及其用途:
ApiClient
。@Module
和@Inject
的接口。它包含所有模块并为应用程序提供构建器。@Provides
,但更简洁。Dagger 可以为您的项目生成依赖关系图,从而让它在需要时确定从哪里获取依赖项。要实现此功能,您需要创建一个接口并使用@Component
对其进行注释。
在@Component
接口中,您可以定义返回所需类实例的方法(例如BookRepository
)。 @Component
注释指示 Dagger 生成一个容器,其中包含满足其公开类型所需的所有依赖项。此容器称为 Dagger 组件,它包含 Dagger 知道如何提供的对象图及其依赖项。
让我们考虑一个涉及LibraryRepository
的示例:
LibraryRepository
构造函数添加@Inject
注释,以便 Dagger 知道如何创建LibraryRepository
的实例。 public class LibraryRepository { private final LocalLibraryDataSource localDataSource; private final RemoteLibraryDataSource remoteDataSource; @Inject public LibraryRepository(LocalLibraryDataSource localDataSource, RemoteLibraryDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } }
2. 注释依赖项:类似地,注释依赖项的构造函数( LocalLibraryDataSource
和RemoteLibraryDataSource
),以便 Dagger 知道如何创建它们。
public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } }
3. 定义组件:创建一个用@Component
注释的接口来定义依赖图。
@Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); }
当你构建项目时,Dagger 会为你生成ApplicationComponent
接口的实现,通常名为DaggerApplicationComponent
。
您现在可以使用生成的组件来获取类的实例并自动注入它们的依赖项:
public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }
在您的活动或片段中,您可以检索LibraryRepository
实例:
public class MainActivity extends AppCompatActivity { @Inject LibraryRepository libraryRepository; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MainApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected libraryRepository } }
1. 模块
∘ 模块的关键概念
∘ 在组件中包含模块
2. 范围
3. 组件
4. 组件依赖关系
5.运行时绑定
Dagger 2 中的模块是使用@Module
注释的类,用于为组件提供依赖项。它们包含使用@Provides
或@Binds
注释的方法,用于指定如何创建和提供依赖项。模块对于组织和管理应用程序所需的对象的创建至关重要。
@Provides
更简洁,适用于模块为抽象类的情况。模块示例
@Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } @Provides @Singleton OkHttpClient provideOkHttpClient() { return new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build(); } }
在这个例子中, NetworkModule
是一个用@Module
注释的类。它包含两个用@Provides
注释的方法,用于创建并返回Retrofit
和OkHttpClient
的实例。
使用 @Binds
当你有一个接口及其实现时,可以使用@Binds
将实现绑定到接口。这比使用@Provides
更简洁。
public interface ApiService { void fetchData(); } public class ApiServiceImpl implements ApiService { @Override public void fetchData() { // Implementation } } @Module public abstract class ApiModule { @Binds abstract ApiService bindApiService(ApiServiceImpl apiServiceImpl); }
在这个例子中, ApiModule
是一个用@Module
注解的抽象类。 bindApiService
方法用@Binds
注解,用于将ApiServiceImpl
绑定到ApiService
。
模块可以根据其提供的功能进行组织。例如,您可以为网络操作、数据库操作和 UI 相关依赖项设置单独的模块。
例子:
Retrofit
和OkHttpClient
。RoomDatabase
。ViewModel
和Presenter
。模块包含在组件中,用于为需要它们的类提供依赖项。设置方法如下:
应用程序组件.java:
@Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }
在这个例子中, ApplicationComponent
包括NetworkModule
和DatabaseModule
来为应用程序提供依赖项。
Dagger 2 中的范围是定义依赖项生命周期的注释。它们确保在指定范围内创建和共享依赖项的单个实例。这有助于有效管理内存并确保在适当的情况下重用依赖项。
1. 单例范围
定义: @Singleton
范围确保在整个应用程序的生命周期内创建并共享依赖项的单个实例。
此范围通常用于需要在整个应用程序内共享的依赖项,例如网络客户端、数据库实例或共享首选项。
例子:
@Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }
在这个例子中, @Singleton
注解确保NetworkModule
和DatabaseModule
提供的Retrofit
和Database
实例是单例,并在整个应用程序中共享。
2. 活动范围
定义: @ActivityScope
(自定义范围)确保在活动的生命周期内创建并共享依赖项的单个实例。
此范围对于特定于活动的依赖项很有用,并且每次重新创建活动时都应重新创建,例如演示者或视图模型。
例子:
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }
在此示例中, @ActivityScope
注释确保ActivityModule
提供的依赖项作用域于活动的生命周期。
3. 片段范围
定义: @FragmentScope
(另一个自定义范围)确保在片段的生命周期内创建并共享依赖项的单个实例。
用例:此范围对于特定于片段的依赖项很有用,并且每次重新创建片段时都应重新创建,例如特定于片段的演示者或视图模型。
例子:
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); }
在此示例中, @FragmentScope
注释确保FragmentModule
提供的依赖项作用域于片段的生命周期。
组件依赖关系允许一个组件依赖于另一个组件,从而实现依赖关系的重用。组件依赖关系主要有两种类型:
1. 应用程序组件
定义:应用程序组件提供整个应用程序所需的依赖项。它通常使用@Singleton
来确保依赖项在整个应用程序中共享。
该组件用于需要全局可用的依赖项,例如网络客户端、数据库实例或共享首选项。
例子:
@Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }
在这个例子中, ApplicationComponent
负责提供Retrofit
和Database
实例,它们在整个应用程序共享。
2. 活动组件
定义:活动组件提供特定活动所需的依赖项。它通常使用自定义范围(例如@ActivityScope
)来限定范围,以确保每次重新创建活动时都会重新创建依赖项。
该组件用于特定于活动的依赖项,例如演示者或视图模型。
例子:
@ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }
在此示例中, ActivityComponent
依赖于ApplicationComponent
并提供特定于MainActivity
的依赖项。
组件依赖关系允许一个组件依赖于另一个组件,从而实现依赖关系的重用。组件依赖关系主要有两种类型:
1.子组件:
子组件是另一个组件的子项,可以访问其父组件的依赖项。子组件在父组件内定义,可以继承其范围。
例子:
@ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); }
在这个例子中, ActivitySubcomponent
是父组件的子组件,并可以访问其依赖项。
2. 依赖属性
这允许组件依赖另一个组件,而无需成为子组件。依赖组件可以访问父组件提供的依赖项。
例子:
@ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }
在这个例子中, ActivityComponent
依赖于ApplicationComponent
并且可以访问其依赖项。
Dagger 2 中的运行时绑定是指根据需要的上下文在运行时创建和管理的依赖项的提供。
1. 应用背景
定义:应用程序上下文是与整个应用程序的生命周期相关的上下文。它用于需要与应用程序本身一样长的依赖项。
在整个应用程序中共享的依赖项,无需为每个活动或片段重新创建。示例包括网络客户端、数据库实例和共享首选项。
例子:
@Module public class AppModule { private final Application application; public AppModule(Application application) { this.application = application; } @Provides @Singleton Application provideApplication() { return application; } @Provides @Singleton Context provideApplicationContext() { return application.getApplicationContext(); } }
在此示例中, AppModule
将应用程序上下文作为单例依赖项提供。 provideApplicationContext
方法确保提供的上下文与应用程序的生命周期相关联。
2. 活动背景
定义:活动上下文是与特定活动生命周期相关的上下文。它用于需要与活动本身同时存在的依赖项。
特定于某项活动的依赖项,每次重新创建活动时都应重新创建。示例包括视图模型、演示器和与 UI 相关的依赖项。
例子:
@Module public class ActivityModule { private final Activity activity; public ActivityModule(Activity activity) { this.activity = activity; } @Provides @ActivityScope Activity provideActivity() { return activity; } @Provides @ActivityScope Context provideActivityContext() { return activity; } }
在此示例中, ActivityModule
将活动上下文作为范围依赖项提供。 provideActivityContext
方法确保提供的上下文与活动的生命周期相关联。
要使用这些运行时绑定,您需要在组件中包含相应的模块:
应用程序组件:
@Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); }
活动组件:
@ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); }
注入上下文
设置好组件和模块后,您可以根据需要将上下文注入到类中。
活动中的示例:
public class MainActivity extends AppCompatActivity { @Inject Context activityContext; @Inject Context applicationContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApplicationComponent appComponent = ((MyApplication) getApplication()).getApplicationComponent(); ActivityComponent activityComponent = DaggerActivityComponent.builder() .applicationComponent(appComponent) .activityModule(new ActivityModule(this)) .build(); activityComponent.inject(this); // Use the injected contexts Log.d("MainActivity", "Activity Context: " + activityContext); Log.d("MainActivity", "Application Context: " + applicationContext); } }
在此示例中, MainActivity
通过依赖项注入接收活动上下文和应用程序上下文。这允许活动根据依赖项的特定需求使用适当的上下文。
要在您的项目中使用 Dagger 2,您需要在build.gradle
文件中添加以下依赖项:
dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' }
将2.x
替换为最新版本的 Dagger 2。
创建一个模块来提供依赖项。例如,一个NetworkModule
来提供Retrofit
实例:
@Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } }
创建一个组件来连接模块和需要依赖项的类:
@Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }
使用组件将依赖项注入到您的类中。例如,在您的Application
类中:
public class MyApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder() .networkModule(new NetworkModule()) .build(); applicationComponent.inject(this); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }
现在,您可以在类中使用注入的依赖项。例如,在Activity
中:
public class MainActivity extends AppCompatActivity { @Inject Retrofit retrofit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MyApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected Retrofit instance // ... } }
我们来总结一下这个话题: