Dagger2 est également présenté dans le Guide des développeurs Android sur l'architecture des applications (https://developer.android.com/topic/libraries/architecture/guide.html). Il est temps ** Je pense qu'il est peu probable que les ingénieurs Android ne comprennent pas Dagger2 ou DI **, alors j'ai cherché. Si vous n'êtes pas sûr, veuillez lire la [Note complémentaire] correspondante. Puisque le code source est affiché, il peut être facile à imaginer. Je n'ai pas pu parler de la relation récemment mise à jour avec Android (dagger.android) cette fois. Audacieux ce qui vous a surpris en tant que débutant Dagger.
C'est presque une traduction des documents suivants. Veuillez indiquer ce qui ne va pas.
User's Guide https://google.github.io/dagger/users-guide.html
User's Guide
En tant que bonne classe pour de nombreuses applications, BarcodeDecoder aura probablement des dépendances de classe telles que BarcodeCameraFinder, DefaultPhysicsEngine et HttpStreamer. En revanche, un exemple de mauvaise classe est une classe qui prend de la place sans rien faire, comme BarcodeDecoderFactory. Vous pouvez le remplacer par le modèle de conception d'injection de dépendances. Les tests peuvent être simplifiés en assemblant à l'aide d'annotations standard javax.inject (JSR 330). Il n'y a pas besoin d'une implémentation qui la remplace par une classe pour accéder à l'API Web de Fake. ** L'injection de dépendances n'est pas uniquement destinée aux tests. Vous pouvez créer des modules faciles à réutiliser et à remplacer. ** Par exemple, le module d'authentification peut être partagé et utilisé dans l'application, ou le DevLoggingModule peut être utilisé pour le débogage et le ProdLoggingModule peut être utilisé en production.
Il existe jusqu'à présent de nombreuses bibliothèques DI. Pourquoi avez-vous redéveloppé les roues? Dagger2 est le premier à être entièrement implémenté par génération de code. La règle est de générer une injection de dépendances aussi simple, traçable et exécutable que possible.
Dagger Construit une instance de la classe de l'application et remplit ses dépendances.
Utilisez annotation javax.inject.Inject pour identifier les constructeurs et les champs.
Utilisez un constructeur qui utilise l'annotation @ Inject
lorsque Dagger instancie une classe. Lorsqu'une nouvelle instance est nécessaire, Dagger obtient les paramètres requis et appelle le constructeur.
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
...
}
Dagger peut également injecter des champs (variables membres) directement. Dans cet exemple, nous allons mettre une instance du champ de chauffage et du champ de pompe.
class CoffeeMaker {
@Inject Heater heater;
@Inject Pump pump;
...
}
S'il y a un champ avec ** @ Inject
et aucun constructeur avec @ Inject
, Dagger créera le champ si demandé, mais n'essaiera pas de créer l'instance. (Probablement comme ce CoffeeMaker.) En créant un constructeur sans argument avec @ Inject
, il créera une instance de la même manière. ** **
Dagger prend également en charge l'injection de méthode, mais il est couramment utilisé dans les champs et les constructeurs.
Les classes sans «@ Inject» ne peuvent pas être construites avec Dagger.
C'était un morceau de code, donc c'est difficile à comprendre, n'est-ce pas?
C'est l'image. (Le code est ici. Https://github.com/takahirom/dagger2-sample/commit/4b4e7a047dee0993735c6605bc06bdcd3084c8b8)
En ce qui concerne la zone autour de @ Component
, je l'introduirai plus tard, mais créerai un composant et appelez maker ().
** Ce qui se passe lorsque vous appelez maker (), c'est qu'il renvoie simplement un nouveau CoffeeMaker () et une nouvelle instance injectée Heater () dans ce champ CoffeeMaker. ** **
Je crée un composant (décrit plus tard)
public class MainActivity extends AppCompatActivity {
private CoffeeShop coffeeShop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
coffeeShop = DaggerMainActivity_CoffeeShop.create();
System.out.println(coffeeShop.maker());
}
@Component
interface CoffeeShop {
CoffeeMaker maker();
}
}
@ Inject
permet au constructeur d'être exécuté à partir de Dagger, et @ Inejct
spécifie le champ.
class CoffeeMaker {
@Inject
Heater heater;
@Inject
public CoffeeMaker() {
}
}
Un appareil de chauffage est également créé à partir du champ.
class Heater {
@Inject
public Heater() {
}
}
Cette fois, j'ai utilisé @ Inject
pour le champ (variable membre),
Si vous l'attachez à l'argument du constructeur, l'objet de l'argument sera nouveau en premier, et il sera utilisé pour appeler le constructeur.
Par défaut, Dagger remplit la dépendance en construisant le type demandé. Lorsque vous demandez CoffeeMaker new CoffeeMaker ()
définit les champs qui peuvent être injectés.
Cependant, «@ Inject» ne peut pas être utilisé partout.
Dans de tels cas, une méthode utilisant «@ Provides» peut satisfaire la dépendance. La valeur de retour de la méthode doit être un type qui satisfait la dépendance.
Par exemple, la méthode provideHeader est appelée lorsque Heater est nécessaire.
@Provides static Heater provideHeater() {
return new ElectricHeater();
}
** De plus, les méthodes avec «@ Provides» peuvent avoir leurs propres dépendances. Voici un exemple de retour de Thermosiphon là où la pompe est nécessaire. ** **
@Provides static Pump providePump(Thermosiphon pump) {
return pump;
}
Les méthodes avec «@ provide» doivent appartenir à un module.
Ajoutez simplement l'annotation @ Module
à la classe.
@Module
class DripCoffeeModule {
@Provides static Heater provideHeater() {
return new ElectricHeater();
}
@Provides static Pump providePump(Thermosiphon pump) {
return pump;
}
}
** Par convention, la méthode @ Provides
doit avoir un nom de méthode commençant par provide et le module doit avoir un nom de classe qui se termine par Module. ** **
Je vais le résumer dans le sens de la construction de la relation suivante (graphique).
Classes avec des relations de formulaire @ Injection
et @ Provides
(Graphiques) construites par des dépendances.
Accédez à l'association (Graph) via un "ensemble de routes d'association bien définies" avec un code d'appel tel que la méthode main () de l'application ou la classe Application Android. Dans Dagger2, l'ensemble est déclaré dans une interface où ** la classe souhaitée a une méthode sans argument de retour. ** Appliquez l'annotation @ Component
et passez le module aux modules de l'annotation pour créer une implémentation complète en utilisant ce module.
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}
Une classe pour le Dagger et le premier composant sera générée à partir de laquelle vous pourrez créer une instance de CoffeeShop.
CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
.dripCoffeeModule(new DripCoffeeModule())
.build();
S'il s'agit d'une classe interne, elle sera générée avec un délimiteur _ comme celui-ci
Dans les cas suivants, il sera nommé DaggerFoo_Bar_BazComponent.
class Foo {
static class Bar {
@Component
interface BazComponent {}
}
}
Ce dripCoffeeModule () n'est pas nécessaire si la classe DripCoffeeModule a accès (et n'a pas besoin d'être) le constructeur par défaut. De plus, si toutes les méthodes avec «@ Provides» sont statiques, le constructeur n'a pas besoin d'être accessible.
DaggerCoffeeShop.builder()
.dripCoffeeModule(new DripCoffeeModule())
Si vous n'avez pas besoin d'une instance du module, vous pouvez utiliser la méthode create ()
comme suit au lieu d'utiliser le générateur.
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
Maintenant, vous pouvez simplement utiliser CoffeeShop avec une implémentation qui permet à Dagger d'obtenir un CoffeeMaker entièrement dépendant.
public class CoffeeApp {
public static void main(String[] args) {
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
coffeeShop.maker().brew();
}
}
Cette fois, c'était aussi un fragment de code, donc ça peut être difficile à comprendre, donc je vais mettre tout le code. (Voici le code. Https://github.com/takahirom/dagger2-sample/commit/7adcffb7b4b38d1aede06645df9bbdc37603bd23)
public class MainActivity extends AppCompatActivity {
private CoffeeShop coffeeShop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
coffeeShop = DaggerMainActivity_CoffeeShop.create();
System.out.println(coffeeShop.maker());
}
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}
@Module
static class DripCoffeeModule {
@Provides static Heater provideHeater() {
return new ElectricHeater();
}
@Provides static Pump providePump(Thermosiphon pump) {
return pump;
}
}
}
class CoffeeMaker {
@Inject
Heater heater;
@Inject
Pump pump;
@Inject
public CoffeeMaker() {
}
}
Actuellement, il existe des classes vides @ Inject-only telles que Chauffage, Pompe, Chauffage électrique et Thermosiphon.
public class ElectricHeater extends Heater {
@Inject
public ElectricHeater(){
}
}
En utilisant @ Module
, vous pouvez voir que chaque instance est utilisée.
Cet exemple montre un exemple de construction d'un composant à l'aide de liaisons typiques, mais il existe différents mécanismes pour aider à la liaison d'association (Graph). Les éléments suivants peuvent être utilisés lors de la création de dépendances et sont utilisés pour créer des composants en bon état.
(Il semble y avoir ce qui suit, mais je ne comprends pas tout)
@ Component.modules
et avec @ Module
ou avec l'annotation @ Provides
des modules référencés de manière transitoire depuis @ Module.includes
@ Inject
qui n'ont pas @ Scope
ou ont @ Scope
et sont dans la portée du composant.@ Singleton
est attaché à une méthode avec @ Provides
ou une classe injectable. Graphique Utilisez une instance pour tous les utilisateurs.
@Provides @Singleton static Heater provideHeater() {
return new ElectricHeater();
}
@ Singleton
à la classe est également utile comme document. Vous pouvez voir que cette classe peut être partagée par plusieurs threads.
@Singleton
class CoffeeMaker {
...
}
** Dagger2 connecte les instances étendues (y compris @ Singleton
) dans Graph avec les instances de l'implémentation du composant **, vous devez donc étendre la portée du composant. Par exemple, cela n'a pas de sens d'avoir à la fois la liaison «@ Singleton binding» (qui semble signifier la liaison à un objet dans le graphique) et la liaison «@ RequestScoped» (portée personnalisée) en même temps. Parce qu'il doit être dans un composant avec un cycle de vie différent. Pour créer une portée personnalisée, appliquez simplement les annotations au composant.
@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
CoffeeMaker maker();
}
Comment les instances Singleton sont-elles gérées? (Voici le code https://github.com/takahirom/dagger2-sample/commit/063b7f45c9fb97e54b90ce56a4f96b217e39a6bc )
Dagger2 connecte les instances étendues (y compris @ Singleton
) dans Graph avec les instances de l'implémentation du composant. Qu'est-ce que ça veut dire?
Comment est-il géré?
À titre d'exemple simple, j'ai expérimenté avec @ Singleton
.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final CoffeeShop coffeeShop = DaggerMainActivity_CoffeeShop.create();
System.out.println(coffeeShop.maker());
System.out.println(coffeeShop.maker());
final CoffeeShop coffeeShop2 = DaggerMainActivity_CoffeeShop.create();
System.out.println(coffeeShop2.maker());
System.out.println(coffeeShop2.maker());
}
@Singleton
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}
}
@Singleton
class CoffeeMaker {
@Inject
Heater heater;
@Inject
Pump pump;
@Inject
public CoffeeMaker() {
System.out.println("new CoffeeMaker()");
}
}
Cela produira ce qui suit: En d'autres termes, «@ Singleton» semble être associé à Coffee Shop.
new CoffeeMaker()
com.github.takahirom.dagger_simple.CoffeeMaker@a69a8a4
com.github.takahirom.dagger_simple.CoffeeMaker@a69a8a4
new CoffeeMaker()
com.github.takahirom.dagger_simple.CoffeeMaker@336f60d
com.github.takahirom.dagger_simple.CoffeeMaker@336f60d
J'ai essayé de vider la mémoire dans cet état. Dans cette implémentation, la référence à CoffeeMaker disparaît, c'est-à-dire que l'instance disparaît lorsque MainActivity.onCreate () se termine.
Et si j'ai une instance de CoffeeShop dans Actviity?
public class MainActivity extends AppCompatActivity {
private CoffeeShop coffeeShop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
coffeeShop = DaggerMainActivity_CoffeeShop.create();
System.out.println(coffeeShop.maker());
System.out.println(coffeeShop.maker());
}
Il semble que l'instance soit détenue par le coffeeMakerProvider dans le champ de la classe DaggerMainActivity_CoffeeShop générée automatiquement par Dagger qui implémente l'interface de CoffeeShop.
** Dagger2 avait en quelque sorte un sens pour ** connecter des instances étendues (y compris @ Singleton
) dans Graph avec des instances de l'implémentation du composant.
J'ai également expérimenté sans @Singleton, mais l'instance CoffeeMaker a disparu normalement.
Parfois, vous souhaitez limiter le nombre d'instanciations, mais parfois vous n'êtes pas obligé de le limiter à un. L'instanciation est un processus coûteux sur Android, par exemple.
Dans de tels cas, vous pouvez utiliser @ Réutilisable
. @ Reusable
, contrairement aux autres portées, ne se lie pas à un composant. Au lieu de cela, le composant renvoie une instance mise en cache ou l'instancie.
...
(Cela semble être une fonctionnalité peu utilisée, je vais donc l'omettre cette fois **)
... (Il semble être implémenté comme ça, mais il semble qu'il ne soit pas beaucoup utilisé, alors ** je l'omettrai cette fois **)
@Inject @ForReleasableReferences(MyScope.class)
ReleasableReferences myScopeReferences;
void lowMemory() {
myScopeReferences.releaseStrongReferences();
}
...
Lazy injections
Parfois, il peut être nécessaire de retarder l'instanciation. Vous pouvez retarder l'instanciation jusqu'à ce que vous appeliez la méthode get () de Lazy
class GridingCoffeeMaker {
@Inject Lazy<Grinder> lazyGrinder;
public void brew() {
while (needsGrinding()) {
// Grinder created once on first call to .get() and cached.
lazyGrinder.get().grind();
}
}
}
Provider injections
Vous pouvez avoir besoin de plusieurs instances au lieu d'injecter une seule valeur. Il existe plusieurs options, telles que l'utilisation de Factory ou Builder, mais une option consiste à utiliser Provider @ Provides
.
class BigCoffeeMaker {
@Inject Provider<Filter> filterProvider;
public void brew(int numberOfPots) {
...
for (int p = 0; p < numberOfPots; p++) {
maker.addFilter(filterProvider.get()); //new filter every time.
maker.addCoffee(...);
maker.percolate();
...
}
}
}
Remarque: le code qui injecte Provider
(** Également utilisé dans l'exemple d'application du composant d'architecture ** https://github.com/googlesamples/android-architecture-components/blob/e33782ba54ebe87f7e21e03542230695bc893818/GithubBrowserSample/app/src/main/java/com/android/ exemple / github / viewmodel / GithubViewModelFactory.java # L30)
Qualifiers Parfois, les types seuls ne suffisent pas pour identifier les dépendances. Par exemple, dans une cafetière sophistiquée, je voudrais séparer le chauffe-eau pour l'eau et le radiateur pour la plaque chauffante. Dans ce cas, ajoutez une annotation de qualificatif. Ceci est une annotation avec «@ Qualifier». La déclaration de «@ Named» de l'annotation de qualificatif incluse dans javax.inject est la suivante.
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
String value() default "";
}
Vous pouvez créer votre propre annotation de qualificatif ou utiliser @Named. Il peut être limité en appliquant des annotations aux champs ou aux arguments. Les types et les annotations de qualificatif sont utilisés pour identifier les dépendances.
class ExpensiveCoffeeMaker {
@Inject @Named("water") Heater waterHeater;
@Inject @Named("hot plate") Heater hotPlateHeater;
...
}
Fournit la valeur spécifiée en annotant la méthode @Provides correspondante.
@Provides @Named("hot plate") static Heater provideHotPlateHeater() {
return new ElectricHeater(70);
}
@Provides @Named("water") static Heater provideWaterHeater() {
return new ElectricHeater(93);
}
Les dépendances peuvent ne pas avoir plusieurs annotations de qualificatif. (?)
En fait, j'ai essayé de l'utiliser et j'ai essayé de voir ce qui se passerait si j'entrais un nom différent dans named. (Voici le code https://github.com/takahirom/dagger2-sample/commit/fbabf72a454ec7a96628783a81e12bcb765d6cad)
@Module
static class DripCoffeeModule {
@Provides
@Named("water")
static Heater provideHeater() {
return new ElectricHeater();
}
class CoffeeMaker {
@Inject
@Named("hot plate")
Heater heater;
...
}
Il le détecte correctement comme une erreur et semble m'avertir correctement au moment de la compilation.
MainActivity.java:30:Erreur: @javax.inject.Named("hot plate") com.github.takahirom.dagger_simple.Heater cannot be provided without an @Provides- or @Produces-annotated method.
CoffeeMaker maker();
^
@javax.inject.Named("hot plate") com.github.takahirom.dagger_simple.Heater is injected at
com.github.takahirom.dagger_simple.CoffeeMaker.heater
com.github.takahirom.dagger_simple.CoffeeMaker is provided at
com.github.takahirom.dagger_simple.MainActivity.CoffeeShop.maker()
1 erreur
Vous pouvez le compiler si vous le faites arroser correctement comme indiqué ci-dessous.
class CoffeeMaker {
@Inject
@Named("water")
Heater heater;
Optional bindings ... (Cela semble être une fonction lors de l'utilisation de Optional et Dagger de Java 8 ou Guava, donc je vais l'omettre.)
Binding Instances Les données sont souvent disponibles lors de la construction de composants. Par exemple, considérons une application qui utilise des arguments de ligne de commande. Vous souhaiterez peut-être lier ces arguments dans un composant. Supposons que votre application accepte un argument (String) qui représente le nom de l'utilisateur que vous souhaitez injecter avec l'annotation «@ UserName». Vous pouvez ajouter «@ BindsInstance» au Component Builder afin de pouvoir injecter cette instance dans le Component Builder.
@Component(modules = AppModule.class)
interface AppComponent {
App app();
@Component.Builder
interface Builder {
@BindsInstance Builder userName(@UserName String userName);
AppComponent build();
}
}
Et l'application ressemble à ceci.
public static void main(String[] args) {
if (args.length > 1) { exit(1); }
App app = DaggerAppComponent
.builder()
.userName(args[0])
.build()
.app();
app.run();
}
Dans l'exemple ci-dessus, si vous injectez la chaîne @ UserName
dans un composant, l'instance fournie au générateur est utilisée lors de l'appel de cette méthode. (? Je ne savais pas où se trouve cette méthode ...) Vous devez appeler toutes les méthodes @ BindsInstance
avant de construire le composant. Les liaisons «@ Nullable» suivantes sont une exception.
Si le paramètre de la méthode @ BindsInstance
est marqué @ Nullable
, la liaison est considérée comme" nullable "de la même manière que la méthode @ Provides
. Par conséquent, l'injecteur est également «@ Nullable» et nul doit être accepté. Vous pouvez également omettre l'appel de méthode Builder, où le composant traite l'instance comme nulle.
** La méthode @ BindsInstance
doit être utilisée de préférence à la passer à @ Module
dans le constructeur et à l'adapter immédiatement. ** **
(Ceci est également un exemple de composants d'architecture https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/di/AppComponent. java # L38 semble être utilisé.)
C'est presque la même chose, mais jetons un coup d'œil au code (le code est ici https://github.com/takahirom/dagger2-sample/commit/c2cbfdbbf883203378ad51fd1d629ea25a7dbe6f)
Le qualificatif semble être bon pour celui qui était UserName dans l'exemple. Je l'utilise donc chez @Named. Cette fois, j'ai essayé de passer s'il fallait utiliser du sucre lors du démarrage de l'application.
J'ai spécifié d'utiliser du sucre avec du sucre (vrai) (j'ajouterai du sucre)
coffeeShop = DaggerMainActivity_CoffeeShop.builder().sugar(true).build();
System.out.println(coffeeShop.maker());
C'est le même que l'exemple.
@Singleton
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
@Component.Builder
interface Builder {
@BindsInstance
Builder sugar(@Named("sugar") boolean isAddingSugger);
CoffeeShop build();
}
}
Mettez le suger où vous le souhaitez.
class CoffeeMaker {
...
@Inject
@Named("sugar")
boolean isAddSugar;
... (Le processeur d'annotation de Dagger est strict et donnera une erreur s'il est faux, donc je l'omettrai.)
Le processeur d'annotations de Dagger génère automatiquement des classes telles que CoffeeMaker_Factory.java et CoffeeMaker_MembersInjector.java. Ces classes sont les détails de l'implémentation de Dagger. C'est utile lors du débogage via des injections, mais vous n'avez pas à en profiter directement. ** Tout ce dont vous avez besoin dans votre code est celui qui commence par Dagger pour le composant. ** **
... (Je vais l'omettre car c'est Android après tout.)
J'ai senti que ce serait pratique si je pouvais le maîtriser. En particulier, si vous créez une instance d'une certaine classe avec Dagger, s'agit-il d'une chaîne de création d'instances telles que la création d'arguments pour ce champ ou cette méthode? C'était très pratique parce que j'allais le faire. Cependant, au début, j'ai l'impression que le coût d'apprentissage est un peu lourd, alors j'espère qu'il y a des gens qui seront un peu plus faciles avec cette Qiita.
Recommended Posts