Bootstrap

Dagger2学习

Dagger2是什么

Dagger2是一个IOC容器,相比起后端的spring来说,Dagger2是编译时完成注入,而spring是运行时。Dagger相比较其它依赖注入框架最大优势是没有采用反射技术,使用APT技术。
Dagger2 生成类初探:https://blog.csdn.net/qfanmingyiq/article/details/116189368
参考:https://blog.csdn.net/u011164827/article/details/124902442
想学习Daager2在Android源码SystemUI的应用见:https://blog.csdn.net/z1804362542/article/details/127136013?spm=1001.2014.3001.5502

依赖和关联

像下图示意的那样,模块或类之间的关系大体可以分为依赖(Dependency)和关联(Association)两种。依赖一般表现为局部参数,关联则表现为属性的持有。
按照被关联对象的生命周期的不同,又可以将关联分为聚合(Aggregation)和组合(Composition)。耦合度依次增强。
在这里插入图片描述
不依赖框架的情况下我们也可以手动注入这些关系。

  • 通过构造函数传参
  • 通过setter函数传参

但面对依赖纵深复杂的大型项目,手动注入这些依赖非常繁琐和易错,互相的依赖关系难以避免得混乱而丑陋。久而久之,耦合度越来越强,难以扩展。修改代码的时候也会牵一发而动全身。这时候自动注入这些依赖关系显得尤为必要。

注解的使用

四个基础的注解:
@Inject 主要有两个作用,一个是使用构造函数上,通过标记构造函数Dagger2来使用(Dagger2通过这个标记可以在需要这个类实例的时候来找到这个构造函数并把相关实例new出来)从而提供依赖,另外一个作用就是标记在变量上、即需要依赖的变量上,让Dagger2为其提供依赖。(可参考:https://developer.android.google.cn/training/dependency-injection/dagger-basics
@Inject还能使用在方法上,用来set变量
@Component 一般用于标注接口,被标注了Component的接口在编译时会产生相应的类的实例来作为提供依赖方和需要依赖方之间的桥梁,把相关依赖注入其中。Component标注的接口在编译时会生成该接口的实现类(例如MainActivityComponent标注的接口会生成接口类DaggerMainActivityComponent)。

一、无参构造(准确来说是构造方法里无第三方对象时的注入)

1.生成@Inject标注的构造方法
2.生成@Component标注的中间接口
3.@Inject标注变量
4。通过DaggerxxxComponent实现注入

public class Car {
    @Inject
    public Car() {
    }

    public String show(){
        return getClass().getSimpleName();
    }
}

接下来需要创建一个用@Component标注的接口,这个接口是一个注入器,用来将对象实例注入到调用方。

@Component   //这个MainComponent就类似我们SystemUI中的SystemUIRootComponent类!
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

完成后可能需要reBuild项目生成DaggerMainComponent类(会自动加前缀Dagger生成这样一个类)
调用测试:创建一个MainActivity测试

public class MainActivity extends AppCompatActivity {
    //定义需要被注入的实例
    @Inject
    Car mCar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //实现注入
        DaggerMainComponent.create().inject(this);
        Toast.makeText(this,"this is " + mCar.show(),Toast.LENGTH_LONG).show();
    }
}
注:
Android 11源码中SystemUI的这个DaggerMainComponent出现在SystemUIFactory的
buildSystemUIRootComponent方法中构建并返回到init方法:
mRootComponent.createDependency().createSystemUI(dependency);

二、带参构造方法(准备来说是构造方法里有第三方对象时的注入)

2.1 @Provides方式
在开发中经常使用到第三方对象 或者 有参数的构造方法 (比如那么当你需要实例化一个第三方的对象时,是不是懵逼了,因为你不可能注解第三方类的构造函数 – 比如说 Gson 类的构造函数。那么现在就需要 @Module 来充分发挥作用了),这时候我们应该如何进行标注?这时候需要使用@Module和@Provide
@Provide用来标注一个方法,该方法可以需要提供依赖时被调用,从而把预先提供好的对象当做依赖
标注了@Injection的变量赋值。Provide主要用于标注Module里的方法。
创建两个类

public class Gear {
    public Gear() {
    }

    @NonNull
    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }
}

public class Engine {
    public Engine(Gear gear) {
        gear.toString();
    }
}

可以看到上面Engine构造方法中需要使用Gear的对象
创建Module类(和第一种方法比多了这一步)**

@Module
public class MainActivityModule {
    @Provides //@Provides 用以标注 Module 类中的方法,它的作用是 标注该 Module 
    //可以向外界提供的类的实例对象的方法
    public Engine provideEngine(){
        return new Engine(new Gear());
    }
}



补充:@Module(includes={ModuleA.class,ModuleB.class,ModuleC.class})
    Module里面也可以includes许多Module,一般用于构建更高层的Module时候使用

这里使用了@Module标注了类,用@Provides标注方法

@Component(modules = {MainActivityModule.class})//modules中可以有多个Module类,通过","隔开
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

实现注入

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    //定义需要被注入的实例
    @Inject
    Car mCar;
    @Inject
    Engine mEngine;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //实现注入
        DaggerMainComponent.create().inject(this);
        //Toast.makeText(this,"this is " + mCar.show(),Toast.LENGTH_LONG).show();
        Toast.makeText(this,"this is " + mEngine.toString(),Toast.LENGTH_LONG).show();
        Log.d(TAG, "onCreate: " + mCar.show() + " Engine " + mEngine.toString());
    }
}

2.1 @Binds方式
借用上面已经讲过的例子

public class Gear {
    public Gear() {
    }

    @NonNull
    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }
}

public class Engine {
    public Engine(Gear gear) {
        gear.toString();
    }
}

需要创建Module类,new Engine()里面需要一个依赖,我们在这里直接new了,真实开发其实这个Gear类也是需要去注入的。那么,实现方式就需要改变

@Module
public class MainActivityModule {
    @Provides 
    public Engine provideEngine(){
        return new Engine(new Gear());
    }
}
改成:
@Module
public class MainActivityModule {
    @Provides   //形参需要注入Gear类,来自下面的提供
    public Engine provideEngine(Gear gear){
        return new Engine(gear);
    }

    @Provides //这里提供给上面使用
    public Gear provideGear(){
        return new Gear();
    }
    
}

这里使用了@Module标注了类,用@Provides标注方法

@Component(modules = {MainActivityModule.class})//modules中可以有多个Module类,通过","隔开
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

实现注入

public class MainActivity extends AppCompatActivity {
    //定义需要被注入的实例
    @Inject
    Engine mEngine;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //实现注入
        DaggerMainComponent.create().inject(this);
        
        Toast.makeText(this,"this is " + mEngine.toString(),Toast.LENGTH_LONG).show();

    }
}

好了,现在的问题是,如果new Engine()里面需要很多参数,那么单纯依靠Provide方式将会导致代码变得臃肿。

@Module
public class MainActivityModule {
    @Provides   //形参需要注入Gear类,来自下面的提供
    public Engine provideEngine(Gear gear,A a,B b,C c){
        return new Engine(gear,a,b,c);
    }

    @Provides //这里提供给上面provideEngine使用
    public Gear provideGear(){
        return new Gear();
    }
    @Provides //这里提供给上面provideEngine使用
    public Gear provideA(){
        return new Gear();
    }
    @Provides //这里提供给上面provideEngine使用
    public Gear provideB(){
        return new Gear();
    }
    @Provides //这里提供给上面provideEngine使用
    public Gear provideC(){
        return new Gear();
    } 
}

而用@Binds注解可以做到解决这一点:
Module类改为abstract class。@Provides 改为@Binds,改方法名字去掉方法体,对应的Engine类构造方法加@Inject

@Module
public abstract class MainActivityModule {
    @Binds
    public abstract Engine bindEngine(Engine Engine);
}
public class Engine {
    @Inject
    public Engine(Gear gear,A a,B b,C c) {
        gear.toString();
    }
}

当然,Gear\A\B\C等类还是要提供出来**(在别的地方提供,不是在MainActivityModule抽象类中)**

   @Provides
    public Gear provideGear(){
        return new Gear();
    }
...

用@Binds不用@Provides的好处:provides的方法里面太多参数并不方便,比如要导入很多包名导MainActivityModule类中,有些是Engine的内部类,并且是private的。
而@Bings通过在括号里指定对应的类绑定过去却很方便,具体开发过程中用到就能感受到了。

三、限定的方式

在面向接口编程中,经常需要使用一个接口,多个实现类的方式进行操作,那么应该如何进行这种类型的初始化?这里需要使用到@Qualifier或者@Name注解,同时这也是当对象有多个构造函数时的解决方法。
方案一:@Qualifier限定符,可以用来给注解做注解
方案二:@Named是被@Qualifier注解的,可以省略手动创建注解类,主要用来注解方法和对象,也用来指定具体实现类名字的
方案一实现步骤:
1.创建@Qualifier注解过的注解
2.给Provider方法加上@Qualifier注解
示例:
创建一个接口

public interface Mechanical {
}

创建注解

@Qualifier
@Retention(RetentionPolicy.RUNTIME) //java元注解,表标注被标注的注解只被保存在class源文件中,并且可以被反射机制所读取。
public @interface QualifierCar {
}

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

修改Car和Engine , 模拟多个实现类的效果

public class Car implements Mechanical{
    @Inject
    public Car() {
    }

    @NonNull
    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
}

public class Engine implements Mechanical{
    @Inject
    public Engine(Gear gear) {
        gear.toString();
    }
}


修改Module类

@Module
public class MainActivityModule {
    @QualifierEngine
    @Provides
    public Mechanical provideEngine(){
        return new Engine(new Gear());
    }

    @QualifierCar
    @Provides
    public Mechanical provideCar(){
        return new Car();
    }
}

//@Component标注一个接口作为中间桥梁
@Component(modules = {MainActivityModule.class})
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

使用测试

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    //定义需要被注入的实例
    @QualifierCar
    @Inject
    Mechanical mCar;
    @QualifierEngine
    @Inject
    Mechanical mEngine;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //实现注入
        DaggerMainComponent.create().inject(this);
        //Toast.makeText(this,"this is " + mCar.show(),Toast.LENGTH_LONG).show();
        Toast.makeText(this,"this is " + mEngine.toString(),Toast.LENGTH_LONG).show();
        Log.d(TAG, "onCreate: " + mCar.toString() + " Engine " + mEngine.toString());
    }
}

方案二实现步骤:
@Named
1.使用@Named标记Module中生成类实例的方法
2.使用@Named标注目标类中相应类实例
使用@Named就不用上面那样去创建注解@QualifierEngine和@QualifierCar了,而是用@Named来指定

@Module
public class MainActivityModule {
    @Named("Engine")
    @Provides
    public Mechanical provideEngine(){
        return new Engine(new Gear());
    }

    @Named("Car")
    @Provides
    public Mechanical provideCar(){
        return new Car();
    }
}

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    //定义需要被注入的实例
    @Named("Car")
    @Inject
    Mechanical mCar;
    @Named("Engine")
    @Inject
    Mechanical mEngine;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //实现注入
        DaggerMainComponent.create().inject(this);
        //Toast.makeText(this,"this is " + mCar.show(),Toast.LENGTH_LONG).show();
        Toast.makeText(this,"this is " + mEngine.toString(),Toast.LENGTH_LONG).show();
        Log.d(TAG, "onCreate: " + mCar.toString() + " Engine " + mEngine.toString());
    }
}
限定符解决多构造方法情况

原先不变的代码拷贝过来

public class Gear {
    @Inject
    public Gear() {
    }

    @NonNull
    @Override
    public String toString() {
        return "不好"+this.getClass().getSimpleName();
    }
}

@Component(modules = {MainActivityModule.class})
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

目前的Egine类有三个构造方法,你如何知道注入提供给别人用的时候,是那个构造方法生效的呢?

public class Engine {

    public Engine(Gear gear) {
        gear.toString();
    }

    public Engine(Gear gear,Gear gear1) {
        gear.toString();
    }

    public Engine() {
    }

}

MainActivityModule类报错了。单单只是方法名、参数不一样并不能区分返回的Egine的实例的不同。就是说当依赖需求方需要依赖提供方的时候,发现有3个提供方,不知道用那一个
在这里插入图片描述

解决:使用限定符注解比如@Name

@Module
public class MainActivityModule {

    @Named("111")
    @Provides
    public Engine ProvideEngine11(){
        Log.d("111","111");
        return new Engine();
    }
    @Named("222")
    @Provides
    public Engine ProvideEngine22(Gear gear,Gear gear1){
        Log.d("111","222");
        return new Engine(gear,gear1);
    }
    @Named("333")
    @Provides
    public Engine ProvideEngine33(Gear gear){
        Log.d("111","333");
        return new Engine(gear);
    }

}

这样,根据你所想要的Egine构造方法所生成的对象,来选择要哪一个提供方,比如我想要new Engine();无参构造Engine所生成的实例。添加@Named(“111”)

public class MainActivity extends AppCompatActivity {
    @Named("111")
    @Inject
    Engine engine;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //实现注入
        DaggerMainComponent.create().inject(this);
        Toast.makeText(this,"this is " + engine.toString(),Toast.LENGTH_LONG).show();
    }
}

查看日志运行到了
在这里插入图片描述

;