Einführung in die Abhängigkeitsinjektion Dependency Injection (DI) ist ein Entwurfsmuster, das zur Implementierung von Inversion of Control (IoC) verwendet wird, wobei die Kontrolle über die Erstellung und Verwaltung von Abhängigkeiten von der Anwendung auf eine externe Entität übertragen wird. Dies hilft bei der Erstellung modulareren, testbareren und wartbareren Codes. Es handelt sich um eine Technik, bei der die Verantwortung für die Erstellung von Objekten auf andere Teile des Codes übertragen wird. Dies fördert eine lose Kopplung und macht den Code modularer und einfacher zu verwalten. Klassen benötigen häufig Verweise auf andere Klassen, um richtig zu funktionieren. Betrachten Sie beispielsweise eine , die eine erfordert. Diese erforderlichen Klassen werden als Abhängigkeiten bezeichnet. Die ist für ihre Funktion auf eine Instanz der angewiesen. Library Book Library Book Es gibt drei grundlegende Möglichkeiten für eine Klasse, die benötigten Objekte abzurufen: : Die Klasse erstellt und initialisiert ihre eigenen Abhängigkeiten. Beispielsweise würde die Klasse ihre eigene Instanz der Klasse erstellen und initialisieren. Selbstkonstruktion Library Book : Die Klasse ruft Abhängigkeiten von einer externen Quelle ab. Einige Android-APIs, wie z. B. Getter und , funktionieren auf diese Weise. Externer Abruf Context getSystemService() : Abhängigkeiten werden der Klasse entweder bei ihrer Erstellung oder durch Methoden, die sie erfordern, bereitgestellt. Beispielsweise würde der eine als Parameter erhalten. Abhängigkeitsinjektion Library Book Die dritte Option ist die Abhängigkeitsinjektion! Mit DI stellen Sie die Abhängigkeiten einer Klasse bereit, anstatt sie von der Klasseninstanz selbst abrufen zu lassen. Beispiel ohne Abhängigkeitsinjektion Ohne DI könnte eine , die ihre eigene erstellt, folgendermaßen aussehen: Library Book 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(); } } Dies ist kein Beispiel für DI, da die Klasse ihr eigenes erstellt. Dies kann aus folgenden Gründen problematisch sein: Library Book : und sind eng gekoppelt. Eine Instanz von verwendet einen , was die Verwendung von Unterklassen oder alternativen Implementierungen erschwert. Enge Kopplung Library Book Library Book : Die starke Abhängigkeit von macht das Testen anspruchsvoller. verwendet eine echte Instanz von und verhindert so die Verwendung von Testdoubles, um für verschiedene Testfälle zu modifizieren. Testschwierigkeiten Book Library Book Book Beispiel mit Abhängigkeitsinjektion Mit DI erstellt nicht jede Instanz von ihr eigenes Objekt, sondern erhält ein Objekt als Parameter in ihrem Konstruktor: 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(); } Die Hauptfunktion verwendet . Da von abhängt, erstellt die App eine Instanz von und verwendet diese dann, um eine Instanz von zu konstruieren. Die Vorteile dieses DI-basierten Ansatzes sind: Library Library Book Book Library : Sie können verschiedene Implementierungen von an übergeben. Sie können beispielsweise eine neue Unterklasse von namens definieren, die verwenden soll. Mit DI übergeben Sie einfach eine Instanz von an und es funktioniert ohne weitere Änderungen. Wiederverwendbarkeit von Library Book Library Book EBook Library EBook Library : Sie können Test-Doubles übergeben, um verschiedene Szenarien zu testen. Einfaches Testen der Library Ein weiteres DI-Beispiel Stellen Sie sich ein Szenario vor, in dem eine Klasse auf einer Klasse basiert. Ohne DI erstellt der direkt eine Instanz von , was die Verwendung unterschiedlicher Benachrichtigungstypen oder das Testen des Dienstes mit verschiedenen Benachrichtigungsimplementierungen erschwert. NotificationService Notification NotificationService Notification Um DI zu veranschaulichen, überarbeiten wir dieses Beispiel: 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(); } } Jetzt hängt von der Schnittstelle ab und nicht von einer bestimmten Klasse. Dadurch können verschiedene Implementierungen von austauschbar verwendet werden. Sie können die Implementierung, die Sie verwenden möchten, über die Methode festlegen: NotificationService Notification Notification sendNotification NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification()); Methoden der Abhängigkeitsinjektion in Android Es gibt drei Haupttypen von DI: : Abhängigkeiten werden über Methoden übergeben, auf die die Klasse über eine Schnittstelle oder eine andere Klasse zugreifen kann. Das vorherige Beispiel demonstriert die Methodeninjektion. Methodeninjektion (Schnittstelleninjektion) : Abhängigkeiten werden der Klasse über ihren Konstruktor übergeben. Konstruktorinjektion 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(); } } : Bestimmte Android-Framework-Klassen, wie Aktivitäten und Fragmente, werden vom System instanziiert, sodass eine Konstruktorinjektion nicht möglich ist. Bei der Feldinjektion werden Abhängigkeiten nach der Erstellung der Klasse instanziiert. 3. Feldinjektion (oder Setter-Injektion) 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(); } } : Abhängigkeiten werden durch Methoden bereitgestellt, häufig unter Verwendung der Annotation . 4. Methodeninjektion @Inject Vorteile der Abhängigkeitsinjektion Klassen werden wiederverwendbarer und sind weniger abhängig von bestimmten Implementierungen. Dies ist auf die Umkehrung der Steuerung zurückzuführen, bei der Klassen ihre Abhängigkeiten nicht mehr verwalten, sondern mit jeder bereitgestellten Konfiguration arbeiten. Abhängigkeiten sind Teil der API-Oberfläche und können bei der Objekterstellung oder Kompilierungszeit überprüft werden, was das Refactoring erleichtert. Da eine Klasse ihre Abhängigkeiten nicht verwaltet, können beim Testen unterschiedliche Implementierungen übergeben werden, um verschiedene Szenarien abzudecken. Automatisierte Abhängigkeitsinjektion Im vorherigen Beispiel haben Sie die Abhängigkeiten verschiedener Klassen manuell erstellt, bereitgestellt und verwaltet, ohne eine Bibliothek zu verwenden. Dieser Ansatz wird als manuelle Abhängigkeitsinjektion bezeichnet. Während dieser Ansatz in einfachen Fällen funktioniert, wird er umständlich, wenn die Anzahl der Abhängigkeiten und Klassen zunimmt. Die manuelle Abhängigkeitsinjektion hat mehrere Nachteile: : Bei großen Anwendungen kann die Verwaltung aller Abhängigkeiten und deren korrekte Verbindung zu viel sich wiederholendem Code führen. In einer mehrschichtigen Architektur erfordert das Erstellen eines Objekts für eine oberste Schicht die Bereitstellung aller Abhängigkeiten für die darunter liegenden Schichten. Um beispielsweise einen Computer zu bauen, benötigen Sie eine CPU, ein Motherboard, RAM und andere Komponenten; und eine CPU benötigt möglicherweise Transistoren und Kondensatoren. Boilerplate-Code : Wenn Sie Abhängigkeiten nicht im Voraus erstellen können – etwa bei verzögerten Initialisierungen oder der Beschränkung von Objekten auf bestimmte Flows in Ihrer App – müssen Sie einen benutzerdefinierten Container (oder ein Abhängigkeitsdiagramm) schreiben und verwalten, um die Lebensdauer Ihrer Abhängigkeiten im Speicher zu verwalten. Komplexe Abhängigkeitsverwaltung Bibliotheken können diesen Prozess automatisieren, indem sie Abhängigkeiten für Sie erstellen und bereitstellen. Diese Bibliotheken fallen in zwei Kategorien: : Diese verbinden Abhängigkeiten zur Laufzeit. Reflexionsbasierte Lösungen : Diese generieren Code, um Abhängigkeiten zur Kompilierungszeit zu verbinden. Statische Lösungen Dagger ist eine beliebte, von Google verwaltete Bibliothek zur Abhängigkeitsinjektion für Java, Kotlin und Android. Dagger vereinfacht die Abhängigkeitsinjektion in Ihrer App, indem es den Abhängigkeitsgraphen für Sie erstellt und verwaltet. Es bietet vollständig statische Abhängigkeiten zur Kompilierungszeit und behebt viele der Entwicklungs- und Leistungsprobleme, die mit reflexionsbasierten Lösungen wie Guice verbunden sind. Reflexionsbasierte Lösungen Diese Frameworks verbinden Abhängigkeiten zur Laufzeit: : Ein Laufzeit-DI-Framework, das Reflektion zum Verbinden von Abhängigkeiten verwendet. Es ist leicht und schnell konzipiert und daher für Android-Anwendungen geeignet. Toothpick Statische Lösungen Diese Frameworks generieren Code, um Abhängigkeiten zur Kompilierzeit zu verbinden: : Hilt basiert auf Dagger und bietet eine Standardmethode, um die Abhängigkeitsinjektion von Dagger in eine Android-Anwendung zu integrieren. . Hilt Es vereinfacht die Einrichtung und Verwendung von Dagger, indem es vordefinierte Komponenten und Bereiche bereitstellt : Ein leichtes und einfaches DI-Framework für Kotlin. Koin verwendet eine DSL zur Definition von Abhängigkeiten und ist einfach einzurichten und zu verwenden. Koin : Ein Kotlin-basiertes DI-Framework, das einfach zu verwenden und zu verstehen ist. Es bietet eine einfache und flexible API zur Verwaltung von Abhängigkeiten. Kodein Alternativen zur Abhängigkeitsinjektion Eine Alternative zur Abhängigkeitsinjektion ist das Service Locator-Muster. Dieses Entwurfsmuster hilft auch dabei, Klassen von ihren konkreten Abhängigkeiten zu entkoppeln. Sie erstellen eine Klasse namens Service Locator, die Abhängigkeiten erstellt und speichert und sie bei Bedarf bereitstellt. 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() } Das Service Locator-Muster unterscheidet sich von der Abhängigkeitsinjektion in der Art und Weise, wie Abhängigkeiten genutzt werden. Beim Service Locator-Muster fordern Klassen die Abhängigkeiten an, die sie benötigen. Bei der Abhängigkeitsinjektion stellt die App die erforderlichen Objekte proaktiv bereit. Was ist Dagger 2? Dagger 2 ist ein beliebtes DI-Framework für Android. Es verwendet Codegenerierung zur Kompilierungszeit und ist für seine hohe Leistung bekannt. Dagger 2 vereinfacht den Prozess der Abhängigkeitsinjektion, indem es den erforderlichen Code zur Handhabung von Abhängigkeiten generiert, Boilerplate-Code reduziert und die Effizienz verbessert. Dagger 2 ist eine annotationsbasierte Bibliothek für die Abhängigkeitsinjektion in Android. Hier sind die wichtigsten Annotationen und ihre Zwecke: : Wird verwendet, um Klassen zu definieren, die Abhängigkeiten bereitstellen. Beispielsweise kann ein Modul einen für Retrofit bereitstellen. @Module ApiClient : Kommentiert Methoden in einem Modul, um anzugeben, wie Abhängigkeiten erstellt und zurückgegeben werden. @Provides : Wird zum Anfordern von Abhängigkeiten verwendet. Kann auf Felder, Konstruktoren und Methoden angewendet werden. @Inject : Eine Schnittstelle, die und verbindet. Sie enthält alle Module und stellt den Builder für die Anwendung bereit. @Component @Module @Inject : Stellt sicher, dass eine einzelne Instanz einer Abhängigkeit erstellt wird. @Singleton : Wird in abstrakten Klassen verwendet, um Abhängigkeiten bereitzustellen, ähnlich wie , aber prägnanter. @Binds @Provides Dolchkomponenten Dagger kann einen Abhängigkeitsgraphen für Ihr Projekt generieren, sodass es bei Bedarf feststellen kann, wo Abhängigkeiten abgerufen werden können. Um dies zu ermöglichen, müssen Sie eine Schnittstelle erstellen und diese mit kommentieren. @Component Innerhalb der -Schnittstelle definieren Sie Methoden, die Instanzen der benötigten Klassen zurückgeben (z. B. ). Die Annotation weist Dagger an, einen Container mit allen Abhängigkeiten zu generieren, die erforderlich sind, um die von ihm bereitgestellten Typen zu erfüllen. Dieser Container wird als Dagger-Komponente bezeichnet und enthält ein Diagramm von Objekten, die Dagger zusammen mit ihren Abhängigkeiten bereitstellen kann. @Component BookRepository @Component Beispiel Betrachten wir ein Beispiel mit einem : LibraryRepository : Fügen Sie dem Konstruktor eine -Annotation hinzu, damit Dagger weiß, wie eine Instanz von erstellt wird. Kommentieren Sie den Konstruktor LibraryRepository @Inject 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; } } : Kommentieren Sie auf ähnliche Weise die Konstruktoren der Abhängigkeiten ( und ), damit Dagger weiß, wie sie erstellt werden. 2. Abhängigkeiten kommentieren LocalLibraryDataSource RemoteLibraryDataSource public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } } : Erstellen Sie eine mit kommentierte Schnittstelle, um den Abhängigkeitsgraphen zu definieren. 3. Definieren Sie die Komponente @Component @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); } Wenn Sie das Projekt erstellen, generiert Dagger für Sie eine Implementierung der Schnittstelle, die normalerweise heißt. ApplicationComponent DaggerApplicationComponent Verwendung Sie können jetzt die generierte Komponente verwenden, um Instanzen Ihrer Klassen mit automatisch eingefügten Abhängigkeiten abzurufen: public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } } In Ihrer Aktivität oder Ihrem Fragment können Sie die Instanz abrufen: 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 } } Schlüsselkonzepte in Dagger 2 1. Module ∘ Schlüsselkonzepte der Module ∘ Module in Komponenten einbinden 2. Geltungsbereich 3. Komponenten 4. Komponentenabhängigkeiten 5. Laufzeitbindungen 1. Module Module in Dagger 2 sind mit annotierte Klassen, die Abhängigkeiten zu den Komponenten bereitstellen. Sie enthalten mit oder annotierte Methoden, um anzugeben, wie Abhängigkeiten erstellt und bereitgestellt werden. Module sind für die Organisation und Verwaltung der Erstellung von Objekten, die Ihre Anwendung benötigt, unerlässlich. @Module @Provides @Binds Schlüsselkonzepte der Module @Module-Annotation: Diese Annotation wird verwendet, um eine Klasse als Dagger-Modul zu definieren. Eine Modulklasse enthält Methoden, die Abhängigkeiten bereitstellen. @Provides-Annotation: Diese Annotation wird bei Methoden innerhalb eines Moduls verwendet, um anzugeben, dass die Methode eine bestimmte Abhängigkeit bereitstellt. Diese Methoden sind für das Erstellen und Zurückgeben von Instanzen der Abhängigkeiten verantwortlich. @Binds-Annotation: Diese Annotation wird in abstrakten Klassen verwendet, um eine Implementierung an eine Schnittstelle zu binden. Sie ist prägnanter als und wird verwendet, wenn das Modul eine abstrakte Klasse ist. @Provides Beispiel eines Moduls @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(); } } In diesem Beispiel ist eine mit annotierte Klasse. Sie enthält zwei mit annotierte Methoden, die Instanzen von und erstellen und zurückgeben. NetworkModule @Module @Provides Retrofit OkHttpClient Verwenden von @Binds Wenn Sie eine Schnittstelle und deren Implementierung haben, können Sie verwenden, um die Implementierung an die Schnittstelle zu binden. Dies ist prägnanter als die Verwendung von . @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); } In diesem Beispiel ist eine abstrakte Klasse, die mit annotiert ist. Die Methode ist mit annotiert, um an zu binden. ApiModule @Module bindApiService @Binds ApiServiceImpl ApiService Module können basierend auf der von ihnen bereitgestellten Funktionalität organisiert werden. Sie können beispielsweise separate Module für Netzwerkvorgänge, Datenbankvorgänge und UI-bezogene Abhängigkeiten haben. Beispiel: : Bietet netzwerkbezogene Abhängigkeiten wie und . NetworkModule Retrofit OkHttpClient : Bietet datenbankbezogene Abhängigkeiten wie . DatabaseModule RoomDatabase : Bietet UI-bezogene Abhängigkeiten wie und . UIModule ViewModel Presenter Einbinden von Modulen in Komponenten Module werden in Komponenten eingebunden, um Abhängigkeiten für die Klassen bereitzustellen, die sie benötigen. So können Sie dies einrichten: Anwendungskomponente.java: @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } In diesem Beispiel umfasst und , um Abhängigkeiten für die Anwendung bereitzustellen. ApplicationComponent NetworkModule DatabaseModule 2. Geltungsbereich Bereiche in Dagger 2 sind Anmerkungen, die den Lebenszyklus von Abhängigkeiten definieren. Sie stellen sicher, dass eine einzelne Instanz einer Abhängigkeit erstellt und innerhalb eines angegebenen Bereichs freigegeben wird. Dies hilft bei der effizienten Verwaltung des Speichers und stellt sicher, dass Abhängigkeiten bei Bedarf wiederverwendet werden. : Stellt sicher, dass während des gesamten Lebenszyklus der Anwendung eine einzelne Instanz einer Abhängigkeit vorhanden ist. Singleton-Bereich : Stellt eine einzelne Instanz einer Abhängigkeit innerhalb des Lebenszyklus einer Aktivität sicher. Aktivitätsumfang : Stellt eine einzelne Instanz einer Abhängigkeit innerhalb des Lebenszyklus eines Fragments sicher. Fragmentumfang 1. Singleton-Bereich : Der -Bereich stellt sicher, dass während des gesamten Lebenszyklus der Anwendung eine einzelne Instanz einer Abhängigkeit erstellt und gemeinsam genutzt wird. Definition @Singleton Dieser Bereich wird normalerweise für Abhängigkeiten verwendet, die von der gesamten Anwendung gemeinsam genutzt werden müssen, z. B. Netzwerkclients, Datenbankinstanzen oder gemeinsame Einstellungen. Beispiel: @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } In diesem Beispiel stellt die Annotation sicher, dass die von und bereitgestellten und Singletons sind und in der gesamten Anwendung gemeinsam genutzt werden. @Singleton NetworkModule DatabaseModule Retrofit Database 2. Tätigkeitsbereich : (ein benutzerdefinierter Bereich) stellt sicher, dass innerhalb des Lebenszyklus einer Aktivität eine einzelne Instanz einer Abhängigkeit erstellt und freigegeben wird. Definition @ActivityScope Dieser Bereich ist nützlich für Abhängigkeiten, die spezifisch für eine Aktivität sind und bei jeder Neuerstellung der Aktivität neu erstellt werden sollten, wie z. B. Präsentatoren oder Ansichtsmodelle. : Beispiel @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } In diesem Beispiel stellt die Annotation sicher, dass die von bereitgestellten Abhängigkeiten auf den Lebenszyklus der Aktivität beschränkt sind. @ActivityScope ActivityModule 3. Fragmentumfang : (ein weiterer benutzerdefinierter Bereich) stellt sicher, dass innerhalb des Lebenszyklus eines Fragments eine einzelne Instanz einer Abhängigkeit erstellt und gemeinsam genutzt wird. Definition @FragmentScope Anwendungsfall: Dieser Bereich ist nützlich für Abhängigkeiten, die spezifisch für ein Fragment sind und bei jeder Neuerstellung des Fragments neu erstellt werden sollten, z. B. fragmentspezifische Presenter oder Ansichtsmodelle. : Beispiel @Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); } In diesem Beispiel stellt die Annotation sicher, dass die von bereitgestellten Abhängigkeiten auf den Lebenszyklus des Fragments beschränkt sind. @FragmentScope FragmentModule 3. Komponenten Komponentenabhängigkeiten ermöglichen die Abhängigkeit einer Komponente von einer anderen Komponente und ermöglichen so die Wiederverwendung von Abhängigkeiten. Es gibt zwei Haupttypen von Komponentenabhängigkeiten: : Bietet Abhängigkeiten, die in der gesamten Anwendung benötigt werden. Anwendungskomponente : Bietet Abhängigkeiten, die innerhalb einer bestimmten Aktivität benötigt werden. Aktivitätskomponente 1. Anwendungskomponente : Die Anwendungskomponente stellt Abhängigkeiten bereit, die in der gesamten Anwendung benötigt werden. Sie wird normalerweise mit definiert, um sicherzustellen, dass die Abhängigkeiten in der gesamten Anwendung gemeinsam genutzt werden. Definition @Singleton Diese Komponente wird für Abhängigkeiten verwendet, die global verfügbar sein müssen, wie z. B. Netzwerkclients, Datenbankinstanzen oder gemeinsame Einstellungen. : Beispiel @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } In diesem Beispiel ist die für die Bereitstellung und verantwortlich, die von der gesamten Anwendung gemeinsam genutzt werden. ApplicationComponent Retrofit Database 2. Aktivitätskomponente : Die Aktivitätskomponente stellt Abhängigkeiten bereit, die innerhalb einer bestimmten Aktivität benötigt werden. Sie wird normalerweise mit einem benutzerdefinierten Bereich wie versehen, um sicherzustellen, dass die Abhängigkeiten bei jeder Neuerstellung der Aktivität neu erstellt werden. Definition @ActivityScope Diese Komponente wird für Abhängigkeiten verwendet, die spezifisch für eine Aktivität sind, wie etwa Präsentatoren oder Ansichtsmodelle. : Beispiel @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } In diesem Beispiel ist die von der abhängig und weist spezifische Abhängigkeiten zur auf. ActivityComponent ApplicationComponent MainActivity 4. Komponentenabhängigkeiten Komponentenabhängigkeiten ermöglichen die Abhängigkeit einer Komponente von einer anderen Komponente und ermöglichen so die Wiederverwendung von Abhängigkeiten. Es gibt zwei Haupttypen von Komponentenabhängigkeiten: : Eine Unterkomponente ist das untergeordnete Element einer anderen Komponente und kann auf die Abhängigkeiten ihres übergeordneten Elements zugreifen. Unterkomponenten : Dadurch kann eine Komponente von einer anderen Komponente abhängig sein, ohne eine Unterkomponente zu sein. Abhängigkeitsattribut 1. Unterkomponenten: Eine Unterkomponente ist ein untergeordnetes Element einer anderen Komponente und kann auf die Abhängigkeiten ihrer übergeordneten Komponente zugreifen. Unterkomponenten werden innerhalb der übergeordneten Komponente definiert und können deren Umfang erben. : Beispiel @ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); } In diesem Beispiel ist eine Unterkomponente der übergeordneten Komponente und kann auf deren Abhängigkeiten zugreifen. ActivitySubcomponent 2. Abhängigkeitsattribut Dadurch kann eine Komponente von einer anderen Komponente abhängig sein, ohne eine Unterkomponente zu sein. Die abhängige Komponente kann auf die von der übergeordneten Komponente bereitgestellten Abhängigkeiten zugreifen. : Beispiel @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); } In diesem Beispiel ist von abhängig und kann auf deren Abhängigkeiten zugreifen. ActivityComponent ApplicationComponent 5. Laufzeitbindungen Laufzeitbindungen in Dagger 2 beziehen sich auf die Bereitstellung von Abhängigkeiten, die zur Laufzeit erstellt und verwaltet werden, basierend auf dem Kontext, in dem sie benötigt werden. : Wird für Abhängigkeiten verwendet, die so lange bestehen müssen wie die Anwendung. Anwendungskontext : Wird für Abhängigkeiten verwendet, die so lange bestehen müssen wie eine Aktivität. Aktivitätskontext 1. Anwendungskontext : Der Anwendungskontext ist ein Kontext, der an den Lebenszyklus der gesamten Anwendung gebunden ist. Er wird für Abhängigkeiten verwendet, die so lange bestehen müssen wie die Anwendung selbst. Definition Abhängigkeiten, die von der gesamten Anwendung gemeinsam genutzt werden und nicht für jede Aktivität oder jedes Fragment neu erstellt werden müssen. Beispiele hierfür sind Netzwerkclients, Datenbankinstanzen und gemeinsame Einstellungen. : Beispiel @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(); } } In diesem Beispiel stellt den Anwendungskontext als Singleton-Abhängigkeit bereit. Die Methode stellt sicher, dass der bereitgestellte Kontext an den Lebenszyklus der Anwendung gebunden ist. AppModule provideApplicationContext 2. Aktivitätskontext : Der Aktivitätskontext ist ein Kontext, der an den Lebenszyklus einer bestimmten Aktivität gebunden ist. Er wird für Abhängigkeiten verwendet, die so lange bestehen müssen wie die Aktivität selbst. Definition Abhängigkeiten, die spezifisch für eine Aktivität sind und bei jeder Neuerstellung der Aktivität neu erstellt werden sollten. Beispiele hierfür sind Ansichtsmodelle, Präsentatoren und UI-bezogene Abhängigkeiten. : Beispiel @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; } } In diesem Beispiel stellt den Aktivitätskontext als bereichsbezogene Abhängigkeit bereit. Die Methode stellt sicher, dass der bereitgestellte Kontext an den Lebenszyklus der Aktivität gebunden ist. ActivityModule provideActivityContext Verwenden von Runtimebindungen in Komponenten Um diese Laufzeitbindungen zu verwenden, müssen Sie die entsprechenden Module in Ihre Komponenten einbinden: : Anwendungskomponente @Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); } : Aktivitätskomponente @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); } Einfügen von Kontexten Nachdem Sie Ihre Komponenten und Module eingerichtet haben, können Sie die Kontexte nach Bedarf in Ihre Klassen einfügen. : Beispiel in einer Aktivität 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); } } In diesem Beispiel erhält sowohl den Aktivitätskontext als auch den Anwendungskontext durch Abhängigkeitseinfügung. Dadurch kann die Aktivität den geeigneten Kontext basierend auf den spezifischen Anforderungen der Abhängigkeiten verwenden. MainActivity Beispiel: Verwenden von Dagger 2 in einer Android-Anwendung Einrichten von Dagger 2 Um Dagger 2 in Ihrem Projekt zu verwenden, müssen Sie Ihrer Datei die folgenden Abhängigkeiten hinzufügen: build.gradle dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' } Ersetzen Sie durch die neueste Version von Dagger 2. 2.x Schritt 1: Definieren Sie ein Modul Erstellen Sie ein Modul, um Abhängigkeiten bereitzustellen. Beispielsweise ein , um eine Instanz bereitzustellen: NetworkModule Retrofit @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } } Schritt 2: Definieren einer Komponente Erstellen Sie eine Komponente, um eine Brücke zwischen dem Modul und den Klassen zu schlagen, die die Abhängigkeiten benötigen: @Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); } Schritt 3: Abhängigkeiten einfügen Verwenden Sie die Komponente, um Abhängigkeiten in Ihre Klassen einzufügen. Beispielsweise in Ihrer : 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; } } Schritt 4: Eingefügte Abhängigkeiten verwenden Jetzt können Sie die eingefügten Abhängigkeiten in Ihren Klassen verwenden. Beispielsweise in einer : 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 // ... } } Abschluss Lassen Sie uns dieses Thema zusammenfassen: Der Hauptzweck von DI besteht darin, die Kopplung zu lockern und so die Verwaltung von Abhängigkeiten zu vereinfachen. Durch die Verwendung von DI können Sie die Codeflexibilität erhöhen und den Testprozess vereinfachen. DI ist ein komplexes Thema mit je nach Szenario unterschiedlichen Implementierungen. DI weist in verschiedenen Sprachen Besonderheiten auf, die sich auf Ihre Arbeit damit auswirken können. Dagger 2 automatisiert den Prozess der Erstellung und Bereitstellung von Abhängigkeiten, reduziert Boilerplate-Code und verbessert die Wartbarkeit. Dagger 2 bietet Sicherheit zur Kompilierungszeit und stellt sicher, dass alle Abhängigkeiten erfüllt sind, bevor die Anwendung ausgeführt wird. Durch die Generierung von Code zur Kompilierzeit vermeidet Dagger 2 den Leistungsmehraufwand, der mit reflexionsbasierten Lösungen verbunden ist.