Dagger2 使用

Dagger2 使用

Dagger2是一款基于Java注解来实现的完全在编译阶段完成依赖注入的开源库,主要用于模块间解耦、提高代码的健壮性和可维护性。Dagger2在编译阶段通过apt利用Java注解自动生成Java代码,然后结合手写的代码来自动帮我们完成依赖注入的工作。

1. 依赖倒置

依赖倒置原则(Dependence Inversion Principle),程序要依赖抽象,而不是依赖具体实现。
A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
B.抽象不应该依赖于具体,具体应该依赖于抽象。

什么是依赖关系?
A类中要用的B类中的部分功能,A类就得持有一个B类对象,即A类依赖B类。

public class A {
    private B mb;
    public void action(){
        mb.show();
    }
}

依赖有什么问题?
软件的需求都是不断变动的,当B类发生变动(比如B类删除了show方法),那A类对象要正常工作,就得跟着变动。耦合程度太高。

依赖抽象是什么意思?
高层次的模块不直接持有低层次模块的引用,而是持有一个接口类型的引用,低层次模块实现该接口;一般而言只要定义合理,接口是不怎么变动的,而低层次模块的实现变动不会影响到高层次模块对接口方法的调用。

2. 依赖注入

前面提到让A类依赖接口,但始终要传入一个实现了该接口的实例对象时才能让A类对象正常工作,通常有以下几种形式。

public class A {
    // IB是个接口
    private IB mb;

    public A() {
        // 在A类中生成
        this.mb = new B();
    }

    public A(IB mb) {
        // 构造A时传入B对象
        this.mb = mb;
    }

    public void setMb(IB mb) {
        // 使用setter来传入B对象实例
        this.mb = mb;
    }

    public void action() {
        mb.show();
    }
}

在A类外部构造好IB接口的实例对象,传入A对象的方式就是依赖注入。

3. Dagger2基本使用

Dagger2使用编译器注解生成代码来完成依赖注入,使用分为三大步:
引入依赖库

    implementation 'com.google.dagger:dagger:2.21'
    implementation 'com.google.dagger:dagger-android:2.21'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.21'
  1. 构建依赖对象,在外部实现依赖对象的创建,Dagger2中负责提供依赖的组件被称为Module。
@Module
public class MainModule {
    @Provides
    Person personProviders() {
        System.out.println("a person created in MainModule ");
        return new Person();
    }
}

使用@Module标记类,@Provides标记方法,方法返回依赖对象实例。
2. 创建Injector,用于将依赖对象注入到消费依赖对象的组件中去,Dagger2中称为Component。

Component是一个组件,这个组件初始化build之后,可以通过各类对象实例。而这个Component不见得只能注入到一个类中,其他需要这些实例的地方都可以注入。

@Component(modules = {MainModule.class})
public interface MainComponent {
    void inject(MainActivity mainActivity);
}
  1. 完成依赖注入,因为MainActivity依赖Person类,需要在MainActivity中构建Injector对象。
public class MainActivity extends AppCompatActivity {
    @Inject
    Person mPerson;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main2);
        MainComponent mainComponent = DaggerMainComponent.builder().mainModule(new MainModule()).build();
        mainComponent.inject(this);
    }
}

以上就是最简单的注入,Mudule是需求提供方,在其中创建需要注入的对象;MainActivity是需求方,在Activity中声明所需要的对象。MainComponent是连接彼此的桥梁。

下面解释一下上面用到的几个注解

@Inject 可用在三个地方

  1. 用于类成员变量前,标记该字段需要被注入一个对象。

        @Inject
        Person mPerson;
    

    Component会找到合适的Module中合适的被@Provides标记的方法,调用该方法将对象注入。

  2. 用于构造方法前,在dagger2通过Module找不到一个方法来生成该类实例时,会调用该构造方法生成实例对象,并将该实例注入给需求方。

    	   @Inject
        public Person() {
            ID = UUID.randomUUID().toString();
        }
    	或者
    	    @Inject Person(String name){
            ID=UUID.randomUUID().toString();
            this.name=name;
        }
    

    当然不能同时标记两个构造函数,不然会引起歧义。

  3. 用在普通方法前,当一个Activity中的成员变量都已经注入后,会调自动调用被标记的普通方法。如果该普通方法有参数,则也会从Module中寻找或通过构造函数来创建的方式来创建所需参数对象。

        @Inject
        public void normalMethod(Person person) {
            Toast.makeText(this, "normalMethod" + person +Toast.LENGTH_LONG).show();
        }
    

@Module 用在Module类前面,被标记的类包含了一系列被@Provides标记的方法,可以对外提供对象实例。

因为有些对象需要手动创建,不能通过给构造函数加@Inject的方式完成,比如第三方库中的对象。Module类也需要初始化需要参数时,可以创建构造函数,传入所需参数。

@Module
public class CcModule {
    private ICommonView mICommonView;
    public CcModule(ICommonView ICommonView) {
        mICommonView = ICommonView;
    }
    @ActivityScope
    @Provides
    public ICommonView provideICommonView() {
        return this.mICommonView;
    }
}

@Provides 用于标注Module所标注的类中的方法,该方法在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject的变量赋值;(当该方法中有参数时,这些参数对象要能通过其他module提供出来)。

@Component 标记在接口上,是一个容器,指明了该Component包含的Module,以及该容器绑定到哪个组件上。一个Activity或Fragment只能注册一个Component,而该Component要满足能提供该Activity或Fragment中所需的所有要注入的对象才行。

@ActivityScope
@Component(modules = {PersonModule.class})
public interface PersonComponent {
    void inject(MainActivity mainActivity);
}

意思是PersonComponent中要能提供Mainactivity中所需的所有要注入的对象才行,不然编译不通过。 而且inject中的类型必须是实际类型。

@Singleton 单例模式,当我们需要创建一个单例对象时,使用该注解即可,方式如下:

// 1. 创建module,用@Singleton标记提供对象的方法。
@Module
public class PersonModule {
    @Provides
    @Singleton
    public Person providePerson() {
        return new Person("姓名","password",18);
    }
}
// 2. 在module所在的Component上添加@Singleton
@Singleton
@Component(modules = {PersonModule.class})
public interface PersonComponent {
    void inject(MainActivity mainActivity);
}

如此,MainActivity中的注入的Person对象就是单例对象了,但这个单例模式的仅在MainActivity中有效,当PersonComponent被多个Activity使用时,每个Activity都会有一个Person单例对象,但从全局来看实际上实例化了多个Person对象。
所以,要创建全局单例,就只能在Application对应的Component中创建。

@dependencies 依赖其他Component,一个Component依赖其他Component,则该Component就包含了其他Component可以提供的对象(需要显式声明,直白说就是你只要使用dependencies,就得显示声明你需要被依赖组件中的哪个对象)。

@Component(modules = {CarModule.class}, dependencies = {PersonComponent.class})
public interface CarComponent {
    void inject(MainActivity mainActivity);
    // 添加组件依赖关系后,仍然需要显示声明,才可以在Car组件中使用Person组件中的对象。
    Person getPerson();
}

CarComponent依赖了PersonComponent,则CarComponent也有了提供Person相关对象的能力。依赖和java中继承很相似,子类“依赖”父类,就持有了父类的非私有成员变量。

一个没有scope的组件component不可以以来一个有scope的组件component。子组件和父组件的scope不能相同。

即如果PersonComponent 有作用域注解,CarComponent就必须有作用域注解,并且不能和PersonComponent的作用域注解相同。

@Named 解决注入对象的冲突,假设我们的Activity中需要两个不同类型的Person,有两个provide方法;但注入时Dagger是无法区分谁是谁的。使用@Named注解可以解决该问题。

@Module
public class PersonModule {
    @Provides
    @Named("man")
    public Person provideManPerson() {
        return new Person("男人", "password", 18);
    }

    @Provides
    @Named("woman")
    public Person provideWomanPerson() {
        return new Person("女人", "passwd", 18);
    }
}

// MainActivity中
    @Inject
    @Named("man")
    Person mManPerson;

    @Inject
    @Named("woman")
    Person mWomanPerson;

@Qualifier 以上冲突解决方案,需要字符串一致,不够优雅容易出错。我们可以使用@Qualifier定义新的注解来解决这个问题。

// 1. 定义注解
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Man {
}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Woman {
}

// 2. Module中使用类型区分注解
@Module
public class PersonModule {
    @Provides
    @Man
    public Person provideManPerson() {
        return new Person("男人", "password", 18);
    }

    @Provides
    @Woman
    public Person provideWomanPerson() {
        return new Person("女人", "passwd", 18);
    }
}
// 3. MainActivity中使用类型区分注解
@Inject
@Man
Person mManPerson;
@Inject
@Woman
Person mWomanPerson;

当然声明的注解也可以用在方法参数中。

@Scope 自定义范围注解,我们可以为不同的组件提供不同的范围注解,比如为Activity、Fragment、Application等不同组件提供不同的范围标签。一个组件中只能有一种作用域注解
定义Scope

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

@Binds 声明接口类型变量所持有的实际类型。之前我们提到了在Module种可以使@Provides 方式提供对象实例,也可以在构造方法上加@Inject来对外提供对象实例,
直接看代码简答明了。

    @Singleton
    @Binds
    @Local
    abstract TasksDataSource provideTasksLocalDataSource(TasksLocalDataSource dataSource);

    @Singleton
    @Binds
    @Remote
    abstract TasksDataSource provideTasksRemoteDataSource(FakeTasksRemoteDataSource dataSource);

TasksDataSource是接口类型,有多种实现形式,我们使用自定义的@Local@Remote限定了使用时具体的实现类型。在Module中一般使用@Provider+方法 对外提供对象实例。
也可以使用@Binds来绑定创建对象实例时所用的实际类型,只需要声明为抽象方法即可,入参就是创建对象实例所需的类型。

@SubComponent

直接声明子组件,可以获取父组件中所有的实例对象,但需要在父组件中显示声明子组件(不是某个实例的获取方式)

Subcomponents are components that inherit and extend the object graph of a parent component. You can use them to partition your application’s object graph into subgraphs either to encapsulate different parts of your application from each other or to use more than one scope within a component.

An object bound in a subcomponent can depend on any object that is bound in its parent component or any ancestor component, in addition to objects that are bound in its own modules. On the other hand, objects bound in parent components can’t depend on those bound in subcomponents; nor can objects bound in one subcomponent depend on objects bound in sibling subcomponents.

In other words, the object graph of a subcomponent’s parent component is a subgraph of the object graph of the subcomponent itself.

以上是官方对Subcomponents 的解释,有三项重点:

子组件继承并扩展了父组件的对象依赖图,子组件可以直接使用父组件中提供的所有依赖对象。那么子组件可以使用一个Scope 父组件可以使用另一个Scope ,相当于子组件中有两个Scope。

子组件可以依赖父组件提供的对象,父组件不能依赖子组件提供的对象,子组件不能依赖兄弟组件提供的对象。

因为子组件扩展了父组件的对象关系依赖图,所以父组件的对象关系依赖图是子组件的对象关系依赖图的子图。

有必要硬啃一下官方文档。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值