Android 之 Dagger 的用法(详细莫过于此)

高级工程师,多年互联网从业及架构经验,善于分析各类开源源码和架构设计

文章正文

@Inject,@Component,@Module 逻辑上的关系

如图所示:

image

  • @Inject: 在 Activity/Fragemnt 中通过 @Inject 标注需要使用的成员变量对象
  • @Module : 是实质上 new 创建我们所需要的成员变量的地方
  • @Component : 可以理解为一根管道,连接我们所需要使用的被@Inject 标注成员变量和创建对象的@Module 之间的管道.

引入依赖

implementation 'com.google.dagger:dagger:2.4'
annotationProcessor 'com.google.dagger:dagger-compiler:2.4'

Dagger 的基本用法

@Module
public class StudentModule {

    @Provides
    public Student getStudent(){
        return new Student();
    }

     public class Student{

        String name = "我是小明";
        int ID;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getID() {
            return ID;
        }

        public void setID(int ID) {
            this.ID = ID;
        }
    }
}
  1. 首先先构建提供创建对象的 Modeule 类,在类上用@module 注明
    1. @module : 作用于类上,用于声明我们提供 module 的类
    2. @Provides : 作用于方法上,用于声明 module 类中对外提供创建对象的方法
@Component(modules = {StudentModule.class})
public interface MainComponent {
    void inject(MainActivity activity);
}
  1. 构建前面所说的"管道"
    1. 需要说明的是这个部分创建的类是 Interface,是接口类型的
    2. @Component 声明于接口上
    3. 经过前面的介绍知道了 module 中就是实质创建对象的,在@Component 后面的 modules 中,我们需要用哪些对象,就添加哪些创建这些对象的 module.class
    4. inject(MainActivity activity) : 通过 inject 方法将对象注入到该类中,将会赋值给被@Injetc 注解标注的对象.
public class MainActivity extends AppCompatActivity {
    @Inject
    StudentModule.Student student;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MainComponent mainComponent = DaggerMainComponent
                .builder()
                .studentModule(new StudentModule())
                .build().inject(this);
        System.out.println(student.getName());
    }
}
输出结果:
System.out: 我是小明
  1. 使用
    1. @Injetc 注解标注所需要使用的对象
    2. 在工具类中点击"Build",然后进行"ReBuildProject"操作
    3. 经过 2 操作后,APT 工具自动生成代码,我们通过可以获取到 DaggerMainComponent 完成一整个的依赖注入过程.
    4. 调用我们需要的对象 student 进行使用

一个 component 中依赖加载多个 module 的使用

在上面的例子,component 中就添加一个 module 也就是 StudentModule.class,其支持添加多个 module

创建所需要使用的 module:

老师的 module
@Module
public class TeacherModule {

    @Provides
    public Teacher getTeacher(){
        return new Teacher();
    }

    public class Teacher{

        String name = "我是老师";
        int ID;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getID() {
            return ID;
        }

        public void setID(int ID) {
            this.ID = ID;
        }
    }
}

学生的 module
@Module
public class StudentModule {

    @Provides
    public Student getStudent(){
        return new Student();
    }

     public class Student{

        String name = "我是小明";
        int ID;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getID() {
            return ID;
        }

        public void setID(int ID) {
            this.ID = ID;
        }
    }
}

创建 Component:

@Component(modules = {StudentModule.class, TeacherModule.class})
public interface MainComponent {
    void inject(MainActivity activity);
}
  • 上面 modules{}中新增了一个 TeacherModule.class

使用@Inject 标注的对象

public class MainActivity extends AppCompatActivity {
    @Inject
    StudentModule.Student student;
    @Inject
    TeacherModule.Teacher teacher;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MainComponent mainComponent = DaggerMainComponent
                .builder()
                .studentModule(new StudentModule())
                .build().inject(this);
        System.out.println(student.getName());
        System.out.println(teacher.getName());
    }
}
输出结果:
System.out: 我是小明
System.out: 我是老师

component 中可加入调用方法

@Component(modules = {StudentModule.class, TeacherModule.class})
public interface MainComponent {
    void inject(MainActivity activity);
    TeacherModule.Teacher getTeacher();
    StudentModule.Student getStudent();
}
  • 就还是上面的例子,我们在其中创建添加一个方法 getTeacher()方法,除了通过@Inject 方式获取到生成的对象,通过调用该 Component 对象中的 getTeacher()方法我们也可以直接获取 Teacher 对象
  • Dagger 会根据方法的返回类型,自动去声明的 module 中匹配对应的对象
  • 以 Student 为例,如果通过@Inject 创建了一个对象,又通过 getStudent()获取了一次,那么这两个 Student 是不同的两个对象,除非使用单例模式,关于单例后面会讲
public class MainActivity extends AppCompatActivity {
    @Inject
    StudentModule.Student student;
    @Inject
    TeacherModule.Teacher teacher;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MainComponent mainComponent = DaggerMainComponent
                .builder()
                .studentModule(new StudentModule())
                .teacherModule(new TeacherModule())
                .build();
        mainComponent.inject(this);
        System.out.println(student.getName());
        System.out.println(mainComponent.getStudent().getName()+"   :getStudent 方式");
        System.out.println(teacher.getName());
        System.out.println(mainComponent.getTeacher().getName()+"   :getTeacher 方式");

    }
}
输出结果:
System.out: 我是小明
System.out: 我是小明   :getStudent 方式
System.out: 我是老师
System.out: 我是老师   :getTeacher 方式

Dagger 的单例实现

局部单例

  1. 在上文中列举的代码 学生的 module 在对外提供的创建对象的方法上加上: @Singleton 注解
    @Singleton
    @Provides
    public Student getStudent(){
        return new Student();
    }
  1. 为 Component 也添加上@Singleton 注解
@Singleton
@Component(modules = {StudentModule.class, TeacherModule.class})
public interface MainComponent {
    void inject(MainActivity activity);
    void inject(Activity2 activity);
    TeacherModule.Teacher getTeacher();
    StudentModule.Student getStudent();
}

3.实现如下:

public class MainActivity extends AppCompatActivity {
    @Inject
    StudentModule.Student student;
    @Inject
    TeacherModule.Teacher teacher;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MainComponent mainComponent = DaggerMainComponent
                .builder()
                .studentModule(new StudentModule())
                .teacherModule(new TeacherModule())
                .build();
        mainComponent.inject(this);
        System.out.println("student address: "+student.toString());
        System.out.println("student address: "+mainComponent.toString()+"   :getStudent 方式");

        System.out.println("teacher address: "+teacher.toString());
        System.out.println("teacher address: "+mainComponent.getTeacher().toString()+"   :getTeacher 方式");
输出结果:
System.out: student address:...StudentModule$Student@1c87043
System.out: student address:...StudentModule$Student@1c87043   :getStudent 方式


System.out: student address:...TeacherModule$Teacher@3006ff9
System.out: student address:...TeacherModule$Teacher@3006ff9   :getTeacher 方式
  • 通过输出结果我们可以看出,经过 singleton 注解的 Student 内存地址一致是单例
  • 未被 singleton 修饰的 Teacher 内存地址不同,不是单例
  • ==这种方式的单例是局部单例也就是只在该 Activity 中为单例,如果在其他 activity/fragment 中再使用 Student 那么对象内存地址将与上述结果日志中的地址不同==

全局单例

  • 在 Dagger 中的全局单例需要依赖于我们的 Application 才能够实现所需要的全局单例.
@Module
public class AppModule {
    @Singleton
    @Provides
    public Object getApp(){
        return new Object();
    }

}
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
    Object getAPP();
}
public class ExampleApplication extends Application {
    private AppComponent mAppComponent;

    @Override public void onCreate() {
        super.onCreate();
        mAppComponent = DaggerAppComponent
                .builder()
                .appModule(new AppModule())
                .build();
    }

    public AppComponent getAppComponent() {
        return mAppComponent;
    }
}
  • 首先在我们的 Application 中做好如上构建
@PreActivity
@Component(modules = {StudentModule.class, TeacherModule.class},dependencies = {AppComponent.class})
public interface MainComponent {
    void inject(MainActivity activity);
    void inject(Activity2 activity);
    Object getAPP();
}
  • 在我们调用的 Component 中添加 dependencies
  • 同时,我们发现还新增了一个方法 getAPP(),虽然在我们添加的 module 中没有提供该返回类型的方法,但该方法被调用的时候,会先去我们添加的 module 中去查找对应的提供该返回值类型的方法,当没有,就会去 dependencies 中的 AppComponent.class 中去查找对应的提供该返回值类型的方法,通过 getAPP()我们就可以获取到 AppComponent 中返回的单例对象
  • 我们还发现多了一个 @PreActivity 注解,这是因为
    • component 的 dependencies 与 component 自身的 scope 不能相同,即组件之间的 scope 不同
    • 没有 scope 的 component 不能依赖有 scope 的 component

自定义注解 @PreActivity 实现方式如下:

@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PreApp {
}
  • 然后在我们需要调用全局单例的地方完成如下操作:
Activity1,和 Activity2 中分别如下:
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MainComponent mainComponent = DaggerMainComponent
                .builder()
                .studentModule(new StudentModule())
                .teacherModule(new TeacherModule())
                .appComponent(((ExampleApplication) getApplication()).getAppComponent())
                .build();
        mainComponent.inject(this);
        System.out.println(mainComponent.getAPP().toString());
}
  • 将我们在 Application 中构建的 AppComponent 加入,并调用其单例进行 print 输出
输出结果:
System.out: java.lang.Object@1c87043
System.out: java.lang.Object@1c87043

可以看到,两个 activity 中的内存地址一致,是一个全局单例


@Binds 的使用

首先看下面的例子:

@Provides
    public IPerson getTeacher(Student student){
        return new Teacher(new Student());
    }
  • 在此之前,是通过@Provides 方式进行实现的
@Provides
    public IPerson getTeacher(Student student){
        return new Teacher(new Student());
    }
  • 假如在创建 Teahcer 对象的时候需要创建 Student 对象,那么就只能通过这种方式进行创建
  • 这也就意味着,当 Teacher 构造函数发生改变的时候我们也需要来这里进行对应的修改
  • 那么这种方式下的逻辑构建会因 Teacher 构造函数发生改变而存在耦合
通过@Bind 进行相同实现:
@Module
public abstract class TeacherModule {
    @Binds
    public abstract IPerson getTeacher(Teacher teacher);

}

@Provides
    public static String setName(){
        return "我是老师";
    }
}    
  • ==@Binds :我们可以理解为会将方法的形参进行返回==
  • 方法的形参会自动去构建相应的对象实例
  • 会自动将上一步构建好的实例进行返回
  • ==这样一来,不论 Teacher 这个对象的构造函数如何变换,都与当前的引用完全解耦==
  • 有两个地方需要注意
    • ==当使用@Binds 返回类型是 抽象类的时候,当前的@Module,其类也将会变成抽象类==
    • ==当@Module 是抽象类的时候,其内部的@Provides 方法必须是 static 修饰的静态方法==
  • 上面这个只是@Binds 实现的片段代码,具体实现步骤请看下面

@Bind 具体实现步骤:

@Module
public abstract class TeacherModule {
    @Binds
    public abstract IPerson getTeacher(Teacher teacher);


@Provides
    public static String setName(){
        return "我是老师";
    }
}    
  • 使用 @Binds 声明相应的方法
public interface IPerson {
    String getName();
    int getID();
}
  • Teacher 所实现用的接口
public class Teacher implements IPerson{
    @Inject
    public Teacher(Student student){
        System.out.println(student.getName());
    }
    String name = "";
    int ID;
    @Override
    public String getName() {
        return name;
    }
    @Override
    public int getID() {
        return ID;
    }
}
  • 在 Teahcer 类中,通过@Inject 对构造函数进行声明
  • 到此可能会问,那么构造函数中的 Student 怎么传进来,请看下面
public class Student{
    String name = "老师,我是小明";
    int ID;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getID() {
        return ID;
    }
    public void setID(int ID) {
        this.ID = ID;
    }
}
@Module
public class StudentModule {
    @Provides
    public  Student getStudent(){
        return new Student();
    }
}
@Component(modules = {StudentModule.class, TeacherModule.class})
public interface MainComponent {
    void inject(MainActivity activity);
    void inject(Activity2 activity);
}
  • 我们创建了一个 StudentModule 并且在其中@Provides 提供了对外创建 Student 对象的方法
  • 并且在@Component 中声明了所使用到的 module
  • 在 Teacher 构造函数需要传入 Student 对象时,Dagger 就会在@Component(modules = {StudentModule.class, TeacherModule.class})中查找提供返回 Student 对象的 @Provides 方法去执行该方法,进行对象的创建
public class MainActivity extends AppCompatActivity {


    @Inject
    IPerson teacher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MainComponent mainComponent = DaggerMainComponent
                .builder()
                .studentModule(new StudentModule())
                .build();
        mainComponent.inject(this);
        System.out.println("teacher : "+teacher.toString());
    }
}
  • 最后在 MainActivity 中进行执行调用
输出结果:
System.out: 老师,我是小明

@Binds 使用注意事项

   @Binds
    public abstract IPerson getTeacher(Teacher teacher);
  1. 前面的例子中使用 @Binds,如上面的代码,注意其形参 Teacher,Teacher 的构造函数通过@Inject 声明的时候只能有一个构造函数被@Inject 声明,假如在 Teacher 的两个或者多个构造函数上都进行@Inject 声明,则无法通过编译
  2. Teacher 其构造函数的形参 Student 必须在@Component(modules = {StudentModule.class, TeacherModule.class})中任意一 module 中有相应的@Provide 方法提供相同的返回类型,否则无法通过编译
  3. 3.
MainComponent mainComponent = DaggerMainComponent
                .builder()
                .studentModule(new StudentModule())
                .teacherModule(new TeacherModule() {
                    @Override
                    public IPerson getTeacher(Teacher teacher) {
                        Student student = new Student();
                        student.setName("老师,我是小明  :teacherModule");
                        return new Teacher(student);
                    }
                })
                .build();
        mainComponent.inject(this);

当通过 @Binds 方式实现后,再在 activity 里面进行实现 teacherModule,则 activity 中的创建的 teacherModule 实现并不会被执行,原因是在尽管实现了这个方法,但 Dagger 在执行的时候,执行到@Binds 注解的方法时,依旧是按照其注解的逻辑去执行


@Qualifier 和@Named

  • 当我们所需要的类型值有多个方法提供该类型时,则可以使用@Named 注解
  • 在@Name 注解中,事实上真正起作用的是@Qualifier
  • 用法是:
    • 在@provide 的方法上用@Name 声明该方法的别名
    • 在参数、成员变量、方法上等需要调用的地方,用@Name 指明所需要调用的@provide 方法
public interface IPerson {
    String getName();
    int getID();
}
public class Teacher implements IPerson{

      @Inject
      public Teacher(@Named("Teacher3")String name){
          this.name = name;
      }

    String name = "";
    int ID;
    @Override
    public String getName() {
        return name;
    }
    @Override
    public int getID() {
        return ID;
    }
}
@Module
public abstract class TeacherModule {

    @Binds
    public abstract IPerson getTeacher(Teacher teacher);

    @Provides
    public static String setName(){
        return "我是老师(Binds 方式实现 2)";
    }

    @Provides
    @Named("Teacher3")
    public static String setName2(){
        return "我是老师(Binds 方式实现 3)";
    }
}
  • 在上述代码可以看到在 Teacher(String name)的构造函数中需要传入 String 类型
  • 但是在 TeacherModule 中含有多个方法具有该类型的返回值
  • 这个时候我们可以通过@Named 对不同的方法进行命名,然后在需要引用的构造函数的形参前面进行指导,Dagger 在执行的时候就会去调用指定别名的方法
public class MainActivity extends AppCompatActivity {


    @Inject
    IPerson teacher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MainComponent mainComponent = DaggerMainComponent
                .builder()
                .studentModule(new StudentModule())
                .build();
        mainComponent.inject(this);
        System.out.println(teacher.getName().toString());
    }

}
输出结果:
System.out: 我是老师(Binds 方式实现 3)

懒加载 Lazy 的使用

通过 Lazy 修饰,该对象它在真正被使用时才被实例化

    @Inject
    Lazy<IPerson > teacher;
    .......
    IPerson person = teacher.get();//获取对象

Dagger 使用总结

  1. Component 的 inject 方法接收父类型参数,而调用时传入的是子类型对象则无法注入,也就是说无法使用多态方式进行注入。
  2. component 关联的 modules 中不能有重复的 provide
  3. module 的 provide 方法使用了 scope ,那么 component 就必须使用同一个注解
  4. module 的 provide 方法没有使用 scope,那么 component 和 module 是否加注解都无关紧要,可以通过编译
  5. component 的 dependencies 与 component 自身的 scope 不能相同,即组件之间的 scope 不同
  6. Singleton 的组件不能依赖其他的 scope 的组件,只能其他 scope 的组件依赖 Singleton 的组件。
  7. 没有 scope 的 component 不能依赖有 scope 的 component
  8. 一个 component 不能同时有多个 scope(Subcomponent 除外)
  9. @Singleton 的生命周期依附于 component,同一个 module 有 provideXX()提供一个实例,且被@Singleton 标注,针对不同的 component,创建的实例不同。
作者正在撰写中...
× 订阅 Java 精选频道
¥ 元/月
订阅即可免费阅读所有精选内容