Dagger is a dependency injection framework whose core implementation principle is to generate dependency injection code at compile time. We can use the annotations provided by Dagger to describe our dependency injection requirements.
In order to implement dependency injection,Dagger needs to know how the object is created and the developer needs to know how to get the object created by Dagger. This article will describe the functionality of the Dagger annotations and how they are implemented.
Dependency injection: a method of passing in a dependency (object) instead of initializing it.
Dagger basis
@Inject
Dagger can be used to specify both the dependency of the object and how the dependent object is created. Each usage of the Dagger generates a different secondary class at compile time to complete the injection of the dependent instance:
Declared on a member variable
class StudentTest {
@Inject
lateinit var nameInfo: NameInfo
}
Copy the code
Dagger generates the corresponding dependency injection class (StudentTest_MembersInjector) at compile time, which is used at run time to inject the nameInfo instance of the StudentTest object:
public final class StudentTest_MembersInjector implements MembersInjector<StudentTest> {
private final Provider<NameInfo> nameInfoProvider;
public StudentTest_MembersInjector(Provider<NameInfo> nameInfoProvider) {
this.nameInfoProvider = nameInfoProvider;
}
public static MembersInjector<StudentTest> create(Provider<NameInfo> nameInfoProvider) {
return new StudentTest_MembersInjector(nameInfoProvider);}
@Override
public void injectMembers(StudentTest instance) {
injectNameInfo(instance, nameInfoProvider.get());
}
public static void injectNameInfo(StudentTest instance, NameInfo nameInfo) {
instance.nameInfo = nameInfo;
}
}
Copy the code
Provider
Creates the template interface for NameInfo
Declaration on the constructor
class Student @Inject constructor(val nameInfo: NameInfo) : IPeople
Copy the code
Dagger generates the Student_Factory class at compile time, which relies on the construct argument to construct the Student object:
public final class Student_Factory implements Factory<Student> { private final Provider<NameInfo> nameInfoProvider; . public static Student_Factory create(Provider<NameInfo> nameInfoProvider) {return new Student_Factory(nameInfoProvider);
}
public static Student newInstance(NameInfo nameInfo) {
returnnew Student(nameInfo); }}Copy the code
If the construct parameter is marked with @inject, Dagger looks for the XX_Factory of the parameter, creates the parameter object, and then creates the current object
@Module
It encapsulates the method used to create an instance of an object:
Inject the way scattered everywhere is not easy to manage
@Module
class StudentModule {
@Provides
fun provideNameInfo() = NameInfo("wang"."pengcheng")}Copy the code
@Provides needs to be declared in the @Module annotation class, which specifies how the dependent instance is created. For each @Provides annotation method, the Dagger generates the corresponding Factory at compile time:
StudentModule_ProvideNameInfoFactory
public final class StudentModule_ProvideNameInfoFactory implements Factory<NameInfo> { private final StudentModule module; . public static NameInfo provideNameInfo(StudentModule instance) {return Preconditions.checkNotNull(instance.provideNameInfo(), "Cannot return null from a non-@Nullable @Provides method"); }}Copy the code
Create the corresponding NameInfo instance using StudentModule().providenameInfo ().
@Component
Manage dependency instances, link @inject and @Module, Inject dependency instances into objects:
@Component(modules = [StudentModule::class])
interface StudentComponent {
fun inject(studentTest: StudentTest)
}
Copy the code
StudentComponent collects the dependency creation methods in modules = [StudentModule::class] and uses these methods to create object instances and assign values to member variables required by StudentTest.
Dagger generates the corresponding implementation class DaggerStudentComponent for this interface at compile time. This class implements dependency injection for StudentTest:
public final class DaggerStudentComponent implements StudentComponent {
private final StudentModule studentModule;
private DaggerStudentComponent(StudentModule studentModuleParam) {
this.studentModule = studentModuleParam;
}
...
@Override
public void inject(StudentTest studentTest) {
injectStudentTest(studentTest);}
@Override
public NameInfo provideNameInfo() {
returnStudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule); } private StudentTest injectStudentTest(StudentTest instance) { StudentTest_MembersInjector.injectNameInfo(instance, StudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule));returninstance; } public static final class Builder { ... }}Copy the code
The DaggerStudentComponent private constructor is created by passing in the StudentModule object as the Builder
Exposed dependency instances
We can add methods to @Component to expose dependent instances:
@Component(modules = [ClassroomModule::class])
interface ClassroomComponent {
...
fun getTeacher():Teacher
}
Copy the code
Dagger generates a factory method to create a Teacher instance, which can be retrieved directly from the code:
val teacher = DaggerClassroomComponent.builder().classroomModule(ClassroomModule()).build().getTeacher()
Copy the code
Simple use of Dagger
Through the introduction of the above three daggers, we have learned the basic usage and implementation principle of Dagger, which can be used in the following way when coding:
class StudentTest {
@Inject
lateinit var nameInfo: NameInfo
constructor() {
DaggerStudentComponent.builder().studentModule(StudentModule()).build().inject(this)
Log.d("dagger-test"."studentName : ${nameInfo.first} ${nameInfo.last}")}}Copy the code
Logcat output:
D/dagger-test: wang pengcheng
Copy the code
@Binds
It can also specify how a dependent instance is provided like @provides, but it can only be declared in the abstract method, which tells the Dagger interface which implementation to use:
@Module(includes = [StudentModule::class])
abstract class ClassroomModule {
@Binds
abstract fun bindPeopleWithStudent(test: Student): IPeople
}
Copy the code
@Module(includes = [StudentModule::class]) enables ClassroomModule to create dependent instances for StudentModule
Fun bindPeopleWithStudent(test: Student): IPeople defines the implementation class for IPeople as Student
Provide the Student instance with @provides or Inject annotation in the constructor:
class Student @Inject constructor(val nameInfo: NameInfo) : IPeople
Copy the code
@Sharing addresses the need for interface oriented programming, which specifies the implementation classes that depend on the interface. This is also possible, of course, with @Provides (the method entity is a strong transformation of type), but @Binds is much clearer:
@Provides
fun providePeopleWithStudent() = Student(provideNameInfo()) as IPeople
Copy the code
Component dependence
If ClassroomComponent needs to use a dependent instance of StudentComponent, you can write:
@Component(modules = [ClassroomModule::class], dependencies = [StudentComponent::class])
interface ClassroomComponent {
fun inject(test: ClassroomTest) } @Component(modules = [StudentModule::class]) interface StudentComponent { fun provideNameInfo(): NameInfo // pass to the dependent Component}Copy the code
StudentComponent provides NameInfo for StudentModule. ClassroomComponent provides NameInfo for dependencies = [StudentComponent::class]. In addition to dependencies = [StudentComponent::class], you need to expose the dependency instance method Fun provideNameInfo(): NameInfo
The above Dagger will generate:
public final class DaggerClassroomComponent implements TestComponent {
private final StudentComponent studentComponent;
private ClassroomTest injectClassroomTest(ClassroomTest instance) {
ClassroomTest_MembersInjector.injectStudent(instance, getStudent());
return instance;
}
}
public final class DaggerStudentComponent implements StudentComponent {
@Override
public NameInfo provideNameInfo() {
returnStudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule); }}Copy the code
StudentComponent becomes a member variable of the DaggerTestComponent, so you can inject NameInfo dependencies for Test:
class ClassroomTest {
@Inject
lateinit var student: IPeople
constructor() { DaggerClassroomComponent.builder().studentComponent(DaggerStudentComponent.builder().studentModule(StudentModule()).buil d()).build().inject(this) Log.d("dagger-test"."studentName : ${student.getName()}")}}Copy the code
Subcomponent
< span style = “box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important; word-break: inherit! Important;
@Subcomponent
You can makeBComponent
intoAComponent
The inner class is then usedAComponent
Dependency injection capability (@Module
) :
AComponent:
@Component(modules = [ClassroomModule::class])
interface ClassroomComponent {
fun inject(test: ClassroomTest) fun studentComponent(): studentComponent.Builder // Use to construct studentComponent}Copy the code
@Module(subcomponents = [StudentComponent::class])
class ClassroomModule {
@Provides
fun provideTeacher() = Teacher()
}
Copy the code
Subcomponents = [StudentComponent::class] Indicates StudentComponent. You can see the dependency instances provided by ClassroomModule:
BComponent:
@Subcomponent(modules = [StudentModule::class]) interface StudentComponent { fun inject(studentTest: @subComponent. Builder Interface Builder {fun build(): StudentTest}}Copy the code
use@Subcomponent
Statement of the childComponent
You also need to display the declarationBuilder
So that the parent knows how to create the child
Dagger does not generate DaggerStudentComponent, only DaggerClassroomComponent:
public final class DaggerClassroomComponent implements ClassroomComponent {
private final class StudentComponentBuilder implements StudentComponent.Builder {
@Override
public StudentComponent build() {
returnnew StudentComponentImpl(new StudentModule()); } } private final class StudentComponentImpl implements StudentComponent { private final StudentModule studentModule; private StudentComponentImpl(StudentModule studentModuleParam) { this.studentModule = studentModuleParam; } @Override public void inject(StudentTest studentTest) { injectStudentTest(studentTest); } private StudentTest injectStudentTest(StudentTest instance) { StudentTest_MembersInjector.injectNameInfo(instance, StudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule)); StudentTest_MembersInjector.injectTeacher(instance, ClassroomModule_ProvideTeacherFactory.provideTeacher(DaggerClassroomComponent.this.classroomModule));returninstance; }}}Copy the code
You can see that the StudentTest injectStudentTest(StudentTest instance) method uses the dependent instance method provided by ClassroomModule.
Since DaggerStudentComponent is not generated, the build for DaggerStudentComponent must do this:
class StudentTest {
@Inject lateinit var nameInfo: NameInfo
@Inject lateinit var teacher: Teacher
constructor() { DaggerClassroomComponent.builder().classroomModule(ClassroomModule()).build().studentComponentBuilder().build().inject(t his) Log.d("dagger-test"."teacher name : ${teacher.name}")}}Copy the code
@Scope
@scope has a Dagger tied to @Component: If an @Module provides a dependency instance that declares the same @scope as @Component, then the @Component uses the same dependency instance for dependency injection:
@Singleton
@Component(modules = [SingletonModule::class])
interface AppComponent : AndroidInjector<TestApplication> {
fun inject(app: Application)
fun getClassroom():Classroom
}
Copy the code
You can also get the singleton Classroom directly by hand
@Module
class SingletonModule {
@Singleton
@Provides
fun provideClassRoom() = Classroom()
}
Copy the code
The @singleton is the @scope built into the Dagger that defines a unique dependency instance within the @Component
class Test {
...
@Inject lateinit var room1: Classroom
@Inject lateinit var room2: Classroom
...
}
Copy the code
The two member variables of Test refer to the same Classroom instance, but when using @Scope it is important to note that @SubComponent cannot declare the same @Scope as @Component
The implementation principle of singleton
Take a look at the Component injection implementation:
private ClassroomActivity injectClassroomActivity(ClassroomActivity instance) {
ClassroomActivity_MembersInjector.injectTeacher1(instance, provideTeacherProvider.get());
ClassroomActivity_MembersInjector.injectTeacher2(instance, provideTeacherProvider.get());
return instance;
}
Copy the code
Objects obtained using the same Provider
Dagger in Android
The basic principle of Dagger and how to use it are described above, but how to use Dagger in Android?
Using the Dagger with the basic usage above causes some column problems. For example, the Activity/Fragment is created by the system, so we cannot turn it into a dependency instance or do automatic dependency injection, so we need to write code like the following:
class ClassroomActivity : Activity() { @Inject lateinit var teacher: Teacher override fun onCreate(savedInstanceState: Bundle?) { ClassroomActivityComponent.builder().build().inject(this); }}Copy the code
There are two bad things about writing this way:
- Too much code like this
- each
Activity/Fragment
In the injection is all need to know its correspondingDaggerXXXComponent
How do you solve it? Dagger officially gives the following implementation steps:
Automatic injection of activities
-
The top-level Component is bound to the AndroidInjectionModule
-
AndroidInjector
Defines an @SubComponent that inherits from AndroidInjector
@Subcomponent(modules = [ClassroomModule::class])
interface ClassroomActivitySubcomponent : AndroidInjector<ClassroomActivity> {
@Subcomponent.Factory
interface Factory : AndroidInjector.Factory<ClassroomActivity>
}
Copy the code
- To define a
@Module
And bind the previously defined@Subcomponent
@Module(subcomponents = [ClassroomActivitySubcomponent::class]) abstract class ClassroomActivityModule { @Binds @IntoMap @ClassKey(ClassroomActivity::class) abstract funbindClassroomActivityFactory(factory: ClassroomActivitySubcomponent.Factory): AndroidInjector.Factory<*>
}
Copy the code
- Let’s take the above definition
@Module
Bound to the@Component
@Component(modules = [AndroidInjectionModule::class, ClassroomActivityModule::class])
interface AppComponent : AndroidInjector<TestApplication> {
fun inject(app: Application)
}
Copy the code
- Application initialization
DispatchingAndroidInjector
class TestApplication : Application(), HasAndroidInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
override fun onCreate() {
super.onCreate()
DaggerAppComponent.create().inject(this)
}
override fun androidInjector() = dispatchingAndroidInjector
}
Copy the code
- Inject dependencies into your Activity
class ClassroomActivity : Activity() {
@Inject
lateinit var teacher: Teacher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidInjection.inject(this)
setContentView(TextView(this).apply {
text = "${teacher.nameInfo.getName()} "}}})Copy the code
If you don’t want to write AndroidInject.inject (this) every time, you can simply have your Activity inherit from DaggerAcitivity
Inject (this) automatically finds dependent instances in the AppComponent and injects them into ClassroomActivity.
For the above two steps 2, 3, actually can use @ ContributesAndroidInjector to step:
@Module
abstract class ClassroomActivityModule {
@ContributesAndroidInjector(modules = [ClassroomModule::class])
abstract fun contributeInjectorClassroomActivity(): ClassroomActivity
}
Copy the code
Dagger at compile time according to @ ContributesAndroidInjector automatically generated step 2 and 3 of the code above.
So how does that work?
The principle of automatic Activity injection
Take a look at the dependency injection code generated in DaggerAppComponent:
public final class DaggerAppComponent implements AppComponent { ... private Map<Class<? >, Provider<AndroidInjector.Factory<? >>>getMapOfClassOfAndProviderOfAndroidInjectorFactoryOf() {
returnCollections.<Class<? >, Provider<AndroidInjector.Factory<? >>>singletonMap(ClassroomActivity.class, (Provider) classroomActivitySubcomponentFactoryProvider); } @SuppressWarnings("unchecked")
private void initialize() {
this.classroomActivitySubcomponentFactoryProvider = new Provider<ClassroomActivityModule_ContributeInjectorClassroomActivity.ClassroomActivitySubcomponent.Factory>() {
@Override
public ClassroomActivityModule_ContributeInjectorClassroomActivity.ClassroomActivitySubcomponent.Factory get() {return new ClassroomActivitySubcomponentFactory();}
};
}
...
private final class ClassroomActivitySubcomponentImpl implements ClassroomActivityModule_ContributeInjectorClassroomActivity.ClassroomActivitySubcomponent {
...
@Override
public void inject(ClassroomActivity arg0) {injectClassroomActivity(arg0);}
private ClassroomActivity injectClassroomActivity(ClassroomActivity instance) {
ClassroomActivity_MembersInjector.injectTeacher(instance, getTeacher());
returninstance; }}}Copy the code
The logic above has two core points:
ClassroomActivity
Is the use ofClassroomActivitySubcomponentImpl
To complete dependency injectionClassroomActivitySubcomponentImpl
Stored in theDaggerAppComponent.getMapOfClassOfAndProviderOfAndroidInjectorFactoryOf
The map of
Then continue to look at what happens to AndroidInjection. Inject (this) :
public final class AndroidInjection { public static void inject(Activity activity) { Application application = activity.getApplication(); . injector = ((HasAndroidInjector) application).androidInjector(); . injector.inject(activity); }}Copy the code
That it ended up call activity. GetApplication () androidInjector (). The injector, inject (activity), is called the DispatchingAndroidInjector < Any > inj Ect (), which will eventually call:
public boolean maybeInject(T instance) { Provider<AndroidInjector.Factory<? >> factoryProvider = injectorFactories.get(instance.getClass().getName()); AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get(); . factory.create(instance).inject(instance); }Copy the code
Actually injectorFactories above is the Factory in the DaggerAppComponent Map, the final call to ClassroomActivitySubcomponentImpl. Inject ()
Reference documentation
Developer.android.com/training/de…
Dagger. Dev/dev – guide/a…