An overview,

Dagger2 dependency injection framework benefits:

  • The injection and configuration of dependencies are independent of the component
  • Dependent objects are initialized in a separate, uncoupled place, with less code to change when the initialization mode changes.
  • Dependency injection makes unit testing easier.

Advantages of Dagger2 over other frameworks:

  • Code is generated at compile time, and errors are reported at compile time.
  • Errors are traceable.
  • Easy to debug.

Disadvantages of Dagger2:

  • Lack of flexibility.
  • There is no dynamic mechanism.

Second,Dagger2annotations

There are seven main types of annotations in Dagger2:

  • @InjectThis annotation serves two purposes: it marks the member variable telling in the target classDaggerVariables of this type require an instance object; The tag depends on the constructor in the class, tellingDaggerI can provide examples of this type of dependency.
  • @Component: is used to mark interfaces or abstract classes, also known as injectors@Injectand@ModuleBridges, all of themComponentYou can get through itmodulesKnow the range of dependencies it provides, oneComponetYou can rely on one or moreComponentAnd get relied onComponentExposed instances,ComponenetthedependenciesA property is an implementation that determines a dependency.
  • @Module: is used to mark a classModuleAt the end,ModuleThe main function of the system is to centralize management@ProvidesTag method, we define a by@ModuleAnnotated classes,DaggerYou’ll know where to find dependencies to satisfy instances of the created class,ModuleAn important feature of the web is that it is designed as blocks and can be grouped together.
  • @ProvidesAnnotate methods that have return types, tellingDaggerHow do we create and provide dependency instances of this type (typically in methodsnewFor example), use@ProvidesThe method of marking is recommendedprovideAs a prefix.
  • @QualifierThe: qualifier, which can be used when a class’s type is not sufficient to indicate a dependency, will callDataModuleMethod to return the appropriate dependent class instance.
  • @Scope: With custom annotations to limit scope, all objects no longer need to know how to manage their instances,Dagger2There is a default scoped annotation in@Singleton, is usually used to mark theAppAn instance that survives throughout its life cycle can also be defined@PerActivityAnnotations, used to indicate that the life cycle is related toActivityConsistent.
  • @SubComponentIf we need the parent component to provide all the objects, we can use the include mode, rather than the dependency mode, does not need the parent component to expose the object, can get all the objects of the parent component, andSubComponentJust in the parentComponentBuckle declaration is ok.

Three,Dagger2Simple application of –@Injectand@Component

Step 1: Basic configuration, add corresponding dependencies in build.gradle:

// Add (1) apply plugin:'com.neenbedankt.android-apt'Buildscript {repositories {jCenter ()} dependencies {// Add (2) classpath'com. Neenbedankt. Gradle. Plugins: android - apt: 1.8'
    }
}

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.0"

    defaultConfig {
        applicationId "com.demo.zejun.repodragger2"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit: junit: 4.12'
    compile 'com. Android. Support: appcompat - v7:23.0.0'// Add (3) apt'com. Google. Dagger: a dagger - compiler: 2.0'// Add (4) compile'com. Google. Dagger: a dagger: 2.0'
}
Copy the code

Step 2: Add @inject tag to the constructor of User as the member object that needs to be instantiated in the target class:

public class User {

    public String name;

    @Inject
    public User() {
        name = "lizejun";
    }

    public String getName() {
        returnname; }}Copy the code

Step 3: Declare Component

@Component()
public interface OnlyInjectComponent {
    void inject(AnnotationActivity annotationActivity);
}
Copy the code

Step 4: Add the annotation @inject in the target class and call the DaggerXXX method to Inject according to the Component we declared in Step 3:

public class AnnotationActivity extends AppCompatActivity {

    @Inject
    public User mUser;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dragger2); // Add the Dagger prefix to the Component interface or abstract class declared in step 3. DaggerOnlyInjectComponent.builder().build().inject(this); }}Copy the code

The above example has two drawbacks:

  • You can mark only one constructor, because if you mark more than two, you don’t know which constructor to use to provide the instance.
  • Cannot mark other classes that we cannot modify, such as third-party libraries.
  • If you use@InjectIf the tag’s constructor has an argument, then that argument also needs to be provided elsewhere, similar toStringThese classes that we can’t modify, we can only use@ModuleIn the@ProvidesTo provide an example.

Four,@ModuleTo provide dependencies

Classes with the @Module tag provide dependencies. Classes with the @Module tag provide management. The only way to actually provide instances of dependencies is through the @provides method with the return type. Step 1: Like above, we define a dependent class, but its constructor does not need to be marked with @Inject:

public class Person {

    private String name;

    public Person() {
        this.name = "lizejun";
    }

    public String getName() {
        returnname; }}Copy the code

Step 2: We need to define an @Module to manage instances of these dependent classes:

@Module
public class PersonDataModule {

    @Provides
    public Person providePerson() {
        returnnew Person(); }}Copy the code

Step 3: Define an @Component that points to the @Module defined above

@Component(modules = {PersonDataModule.class}) public interface PersonInjectComponent { void inject(PersonInjectActivity  injectActivity); }Copy the code

Step 4: Do dependency injection in the target class

public class PersonInjectActivity extends Activity {

    @Inject
    Person mPerson;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerPersonInjectComponent.create().inject(this);
        System.out.println("Person name="+ mPerson.getName()); }}Copy the code

There are two ways to inject this, one is like the one above, which is appropriate for the PersonDataModule constructor with only one parameter, otherwise we would call it like this:

DaggerPersonInjectComponent.builder().personDataModule(new PersonDataModule()).build().inject(this);

Copy the code

Steps for initializing dependent instances

  • Find if there is a method to create this type in Module (that is, the interface of the @Component tag contains the Module class of the @Module tag; if not, find the constructor corresponding to @Inject directly).

  • If there is a create class method, check to see if the method has parameters

  • If no arguments are present, the instance of the class is initialized directly, and dependency injection ends once.

  • If there are parameters, initialize each from Step 1.

  • If there is no create class method, find the constructor of this type with @inject mark and check whether the constructor has parameters:

  • If none exists, the class instance is initialized directly, and dependency injection ends once.

  • If there are parameters, initialize each from Step 1.

Vi.@Qualifierqualifiers

Dagger has a defined qualifier, @name, and we’ll define our own:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface PeopleThreeQualifier {}
Copy the code

Step 1: As before, we define a dependent class that needs to be instantiated:

public class People {

    private int count;

    public People() {
        count = 0;
    }

    public People(int count) {
        this.count = count;
    }

    public int getCount() {
        returncount; }}Copy the code

Step 2: I define a DataModule. The difference is that in its provideXXX method annotations we add @name (XXX) and a custom annotation PeopleThreePeople:

@Module
public class PeopleDataModule {

    @Provides
    @Named("Five People")
    People provideFivePeople() {
        return new People(5);
    }

    @Provides
    @Named("Ten People")
    People provideTenPeople() {
        return new People(10);
    }

    @Provides
    @PeopleThreeQualifier
    People provideThreePeople() {
        returnnew People(3); }}Copy the code

Step 3: Define Component

@Component(modules = PeopleDataModule.class)
public interface PeopleInjectComponent {
    void inject(PeopleInjectActivity peopleInjectActivity);
}
Copy the code

While providing @Inject annotation, we also need to declare the corresponding qualifier in PeopleDataModule so that the Dagger knows which function to use to generate the dependent class instance in the target class:

public class PeopleInjectActivity extends Activity {

    @Inject
    @Named("Five People")
    People mFivePeople;

    @Inject
    @Named("Ten People")
    People mTenPeople;

    @Inject
    @PeopleThreeQualifier
    People mThreePeople;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerPeopleInjectComponent.builder().peopleDataModule(new PeopleDataModule()).build().inject(this);
        System.out.println("Five People=" + mFivePeople.getCount() + ",Ten People=" + mTenPeople.getCount() + ", Three People="+ mThreePeople.getCount()); }}Copy the code

Seven,@Scope

The @scope function is primarily a reminder and management function when organizing components and modules. Within the Dagger, there is a default Scope @singleton. Dagger2 can use custom Scope annotations to specify that instances of classes created by Module and Inject have the same life cycle as the target class. Scope’s real role is in the organization of Component:

  • Better managementComponentIt is necessary to use customization for the organization of dependencies and containmentScopeAnnotate theseComponentAnd the compiler checks for dependencies or containmentComponentIf anyComponentNo customizationScopeAnnotation, an error is reported.
  • Better managementComponentwithModuleThe compiler checks the relationship betweenComponentThe management ofModule, if foundComponentThe custom ofScopeAnnotations andModuleAn error will be reported if the annotation of the annotation creates an instance of the class method in.
  • Improve readability of programs.

Here is an example using @singleton: Step 1: Define the class to instantiate:

public class AnSingleObject {

    private String objectId;

    public AnSingleObject() {
        objectId = toString();
    }

    public String getObjectId() {
        returnobjectId; }}Copy the code

Step 2: Define the DataModule. In its provideXXX method, the @singletion annotation is provided:

@Module
public class AnSingleObjectDataModule {

    @Provides
    @Singleton
    AnSingleObject provideAnSingleObject() {
        returnnew AnSingleObject(); }}Copy the code

Step 3: Define the Component. Instead, add the @singleton annotation to the Component:

@Component(modules = {AnSingleObjectDataModule.class})
@Singleton
public abstract class AnSingleObjectInjectComponent {

    private static AnSingleObjectInjectComponent sInstance;

    public abstract void inject(AnSingleObjectInjectActivity anSingleObjectInjectActivity);

    public static AnSingleObjectInjectComponent getInstance() {
        if (sInstance == null) {
            sInstance = DaggerAnSingleObjectInjectComponent.create();
        }
        returnsInstance; }}Copy the code

Step 4: Inject dependency into the target class. Each time we start the Activity, we can see that the printed hash is the same:

public class AnSingleObjectInjectActivity extends Activity {

    @Inject
    AnSingleObject object;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AnSingleObjectInjectComponent.getInstance().inject(this);
        System.out.println("AnSingleObject id="+ object.getObjectId()); }}Copy the code

Eight, organization,Component

Components are organized in three ways:

  • Dependency: oneComponentDependency on one or moreComponentIs adopted@ComponentthedependenciesProperties.
  • Include: is used here@SubComponentAnnotations, used to mark interfaces or abstract classes to indicate that they can be covered. aComponentIt can contain one or moreComponentAnd includedComponentAnd you can go on and include other thingsComponent.
  • Inheritance: Use oneComponentInherit anotherComponent.

Nine,GoogleOfficial Framework Analysis

Here is the directory structure of the official Google framework:

Addedittask, statistics, taskdetail, tasks
The data, util
ToDoApplication

public class ToDoApplication extends Application {

    private TasksRepositoryComponent mRepositoryComponent;
    
    @Override
    public void onCreate() {
        super.onCreate();
        mRepositoryComponent = DaggerTasksRepositoryComponent.builder()
                .applicationModule(new ApplicationModule((getApplicationContext())))
                .build();
    }
    
    public TasksRepositoryComponent getTasksRepositoryComponent() {
        returnmRepositoryComponent; }}Copy the code

In ToDoApplication, we instantiate a variable TasksRepositoryComponent, which is equivalent to the manager of all the other components in the project, and is declared @Singleton, meaning that only one exists in the App’s lifetime. Using @Component also indicates that it is associated with the TaskRepositoyModule and ApplicationModule modules.

@Singleton
@Component(modules = {TasksRepositoryModule.class, ApplicationModule.class})
public interface TasksRepositoryComponent {
    TasksRepository getTasksRepository();
}
Copy the code

TaskRpositotyModule provides two types of data source objects, which are distinguished by @local and @remote:

@Module
public class TasksRepositoryModule {

    @Singleton
    @Provides
    @Local
    TasksDataSource provideTasksLocalDataSource(Context context) {
        return new TasksLocalDataSource(context);
    }

    @Singleton
    @Provides
    @Remote
    TasksDataSource provideTasksRemoteDataSource() {
        returnnew FakeTasksRemoteDataSource(); }}Copy the code

Next, look at the ApplicationModule

@Module
public final class ApplicationModule {

    private final Context mContext;

    ApplicationModule(Context context) {
        mContext = context;
    }

    @Provides
    Context provideContext() {
        returnmContext; }}Copy the code

Let’s use a simpler interface to look at how TasksRepositoryComponent relates to other components, starting with StatisticsActivity:

public class StatisticsActivity extends AppCompatActivity { private DrawerLayout mDrawerLayout; @Inject StatisticsPresenter mStatiticsPresenter; // Object instantiated with a Dagger. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.statistics_act); // Initialize the Fragment interface. StatisticsFragment statisticsFragment = (StatisticsFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);if(statisticsFragment == null) { statisticsFragment = StatisticsFragment.newInstance(); ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), statisticsFragment, R.id.contentFrame); } DaggerStatisticsComponent.builder() .statisticsPresenterModule(new StatisticsPresenterModule(statisticsFragment)) .tasksRepositoryComponent(((ToDoApplication) getApplication()) .getTasksRepositoryComponent()) .build().inject(this); }}Copy the code