GVKun编程网logo

Android 依赖注入 DI - Dagger2(android 依赖注入原理)

25

对于Android依赖注入DI-Dagger2感兴趣的读者,本文将提供您所需要的所有信息,我们将详细讲解android依赖注入原理,并且为您提供关于androiddagger2使用笔记、Android

对于Android 依赖注入 DI - Dagger2感兴趣的读者,本文将提供您所需要的所有信息,我们将详细讲解android 依赖注入原理,并且为您提供关于android dagger2使用笔记、Android databinding 和 dagger2 冲突问题、Android espresso – 如何使用dagger2注入依赖项、android – Dagger2没有生成Dagger *类的宝贵知识。

本文目录一览:

Android 依赖注入 DI - Dagger2(android 依赖注入原理)

Android 依赖注入 DI - Dagger2(android 依赖注入原理)

1.依赖注入 (Dependency Injection)

1.1 面向接口编程

public interface Drivable {
    void drive();
}

public class Bike implements Drivable {
    @Override
    public void drive() {
        System.out.println("骑车");
    }
}

public class Car implements Drivable {
    @Override
    public void drive() {
        System.out.println("开车");
    }
}

public class Subway implements Drivable {
    @Override
    public void drive() {
        System.out.println("乘地铁");
    }
}

public class Worker {

    private void gotoWork() {
        // 1.依赖具体,非面向接口编程
        Bike bike = new Bike();
        bike.drive();
        // 2.依赖抽象,面向接口
        Drivable drivable = new Bike();
//        Drivable drivable = new Car();
//        Drivable drivable = new Subway();
        drivable.drive();
    }
    public static void main(String[] args) {
        Worker worker = new Worker();
        worker.gotoWork();
    }
}

方式 1 中直接依赖 Bike 类,Worker 依赖具体的实现类,一旦改变具体的实现类,就需要改动。对于业务开发这是允许的。

方式 2 使用一个 Drivable 接口,采用面向接口编程,对于 Worker 这个上层类来说,不再依赖具体,而是依赖抽象,如果改变 gotoWork 的方式,无需做出改变,扩展性得到了很大提升,符合依赖倒置原则

依赖倒置原则:高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。

1.2 什么是依赖注入?

上面的例子,改成面向接口编程后,虽然扩展性提升,但是仍然有具体的对象创建,如果 Bike 类的构造函数改变,如改成有参构造,那么 Worker 也要跟着变;或者更换为其他出行方式,需要创建其他对象,Worker 依然要改。那么有没有方式不用修改 Worker 类呢?

计算机领域的所有问题都可以通过增加一个中间层来解决

可以想象将具体的对象构建放在 Worker 类的外面,把依赖的对象传入进来,这样就不再需要改变 Worker 类,这就用到了依赖注入(Dependency Injection),依赖注入是**控制反转(Inversion Of Control)**的实现手段之一,其实对于这个例子来说,就是将对象创建的过程交给其他职责类,不再自己完成创建,这就是控制反转。那么如果实现依赖注入呢?

  • 方式一:构造函数传入参数
public class Worker {

    private Drivable mDrivable;

    public Worker(Drivable mDrivable) {
        this.mDrivable = mDrivable;
    }

    private void gotoWork() {
        // 3.1依赖注入
        mDrivable.drive();
    }
    
    public static void main(String[] args) {
        // 3.1交给使用者创建具体对象,IOC
        Worker worker = new Worker(new Bike());
        worker.gotoWork();
    }
}
  • 方式二:通过 setter 方式注入
public class Worker {

    private Drivable mDrivable;

    public void setDrivable(Drivable drivable) {
        this.mDrivable = drivable;
    }

    private void gotoWork() {
        // 3.2 依赖注入
        mDrivable.drive();
    }
    
    public static void main(String[] args) {
        // 3.2 交给使用者创建具体对象,IOC
        Worker worker = new Worker();
        worker.setDrivable(new Bike());
        worker.gotoWork();
    }
}
  • 方式三:通过接口方式注入
public interface DependencySetter {
    void set(Drivable drivable);
}

public class Worker implements DependencySetter { {

    private Drivable mDrivable;

    @Override
    public void set(Drivable drivable) {
        this.mDrivable = drivable;
    }

    private void gotoWork() {
        // 3.3依赖注入
        mDrivable.drive();
    }
    
    public static void main(String[] args) {
        // 3.3 作为接口配置
        Worker worker = new Worker();
        worker.set(new Bike());
        worker.gotoWork();
    }
}
  • 方式四:使用依赖注入框架

(1)Spring 中的依赖注入,依赖接口,通过注解可以完成实现类的注入

在这里插入图片描述

(2)Android 中 dagger 依赖注入框架:dagger 是由 Square 公司开发的,Dagger2 是在 Dagger 的基础上来的,Dagger2 由 Google 公司开发并维护。现在比较『先进』的框架都会采用注解的方式,Dagger2 也是基于注解开发的,而 Dagger2 中所涉及到的注解其实是基于 javax.inject 上开发的,它出自 JSR330。Dagger2 是适应于 Java 和 Android 开发的依赖注入框架,它不仅仅对 Android 开发有效。但是对于后端一般很少使用 dagger2 作为依赖注入框架,因为 Spring 的依赖注入框架更强大。

对于较大的 App,Google 工程师推荐使用 Dagger,对于小型 App 手动注入或者使用其他工具,甚至不使用依赖注入,基本都能满足需求。但是把 Dagger 引入到项目中,对于 App 的长久开发是收益比较大的,特别是开发成本上。

在这里插入图片描述

在这里插入图片描述

从曲线中可以看出,使用 Dagger 开始时,可能相对成本比较高,学习成本等,但是随着 App 越来越大,开发成本等各方面收益是很大的。Dagger 的优势主要在三个方面:

性能(Performance):Dagger >生成的中间代码是在编译期完成的,没有使用反射

正确性(Correctness):Dagger >在编译其能保证类型的准确性,如果注入有问题在工程编译时会报错,不至于在>运行时出现 Crash

扩展性(Scalability):Dagger 具有很好的扩展性,特别是大型 App,如 Gmail、Google Photos、 YouTube.

2.Dagger2

2.1 Java 注解

在这里插入图片描述

1、Inject 注解用于构造函数,方法,字段的注解;
(1)用于字段上代表要给改字段注入,即赋值
(2)用于构造函数上时,代表该对象可以被注入,注入时会通过该构造函数创建对象

2、Scope 注解用来表明一个注解器如何使用被注入的对象的,如果没有使用 @Scope,每次创建时都会重新创建一个实例,如果使用 @Singleton 这样一个注解,那么会保留同一个实例,代表单例。@Singleton 是使用 @Scope 注解的注解,也可以使用 @Scope 自己定义一个注解。

3、Qualifier 用来定义注解的注解,如 @Name,用于区分有歧义的注入,比如注入两个相同类型的变量,可以使用 @Name 来给提供值得地方和被注入的地方打上相同的注解,这样就能正确的注入。

2.2 dagger2 注解

在这里插入图片描述

除了 javax 中提供的注解,Dagger2 中也提供了很多注解,常用的注解:@Component、@Module、@Provides、@Binds、@Subcomponent,还有提供集合类的注解,@IntoSet、@IntoMap、@@Multibinds、@ClassKey、@StringKey 等

  • 1.@Component 相当于联系纽带,将 @inject 标记的需求方和依赖绑定起来,并建立了联系,而 Dagger2 在编译代码时会依靠这种关系来进行对应的依赖注入。使用时一般我们只顶一个一个 Component 接口,Dagger 会为我们生成 实现类,通过实现类来进行注入

  • 2.@Component 来完成注入,那么注入需要的对象实例是由 @Module 标注的类提供的,或者由 @Inject 的类。@Module 中的方法一般使用 @Provides 注解的方法(可以是静态方法) 来提供实例,或者 @Binds 注解的方法(抽象方法)

  • 3.@Subcomponent 能够用父 Component 的实例集合,同时可以有自己的实例对象,来进行注入,但是 @Scope 不能和父 Component 相同

2.3 dagger2 基本用法

(1)基本用法

  • 定义 Component

@Component 定义一个接口,接口中声明我们需要的功能,如给 Activity 进行注入,或者返回需要的类

@Singleton
@Component(modules = {EntityModule.class, BindsEntityModule.class})
public interface EntityComponent {

  void injectEntity(InjectorDemoActivity activity);

  void injectEntity(InjectorDemoActivity2 activity);
}
  • 定义 Module 或者使用 @Inject 修饰构造函数,注意需要注入对象的位置,不能同时使用 Module 和 @Inject 提供,否则会报错
// 使用 Module 方式
@Module
public abstract class BindsEntityModule {

  @Provides
  @Named("static provide BindsEntity")
  public static BindsEntity provideFirstEntity(@NonNull BindsEntity entity) {
    return entity;
  }


  @Binds
  @Named("Binds provide BindsEntity")
  public abstract BindsEntity provideSecondEntity(@NonNull BindsEntity entity);
}

// 使用 @Inject 修饰构造函数
public class InjectEntity {

  @Inject
  public InjectEntity() {
  }

  @NonNull
  @Override
  public String toString() {
    return InjectEntity.class.getSimpleName();
  }
}
public class InjectorDemoActivity extends AppCompatActivity {

  public static final String TAG = "InjectorDemoActivity";

  @Inject
  InjectEntity mInjectEntity;
  @Inject
  SingletonEntity mSingletonEntity1;
  @Inject
  SingletonEntity mSingletonEntity2;
  @Inject
  ModuleEntity mModuleEntity;
  @Named("static provide BindsEntity")
  @Inject
  BindsEntity mBindsEntity1;
  @Named("Binds provide BindsEntity")
  @Inject
  BindsEntity mBindsEntity2;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_injector_demo);
    findViewById(R.id.button).setonClickListener(v ->
        startActivity(new Intent(this, InjectorDemoActivity2.class)));
    // 构建 Component 进行注入
    DaggerEntityComponent.builder()
        .build()
        .injectEntity(this);
  }

  @Override
  protected void onResume() {
    super.onResume();
    // mInjectEntity
    Log.e(TAG, mInjectEntity.toString());
    // mModuleEntity和mSingletonEntity
    Log.e(TAG, mModuleEntity.toString());
    Log.e(TAG, mSingletonEntity1.toString());
    Log.e(TAG, mSingletonEntity2.toString());
    // mBindsEntity1 和 mBindsEntity2
    Log.e(TAG, mBindsEntity1.toString());
    Log.e(TAG, mBindsEntity2.toString());
  }
}

(2)multibinds 集合注入

定义 MultiBindsComponent,给 MultiInjectActivity 中的变量进行注入。MultiBindsComponent 中使用的 modules 是 MultiBindsModule.class,同时用到了 dependencies,可以依赖 SetComponent.class,MapComponent.class。modules 和 dependencies 都可以是一个或者多个 Class,dependencies 可以理解为组合的形式。

@Component(modules = MultiBindsModule.class,
dependencies = {SetComponent.class, MapComponent.class})
public interface MultiBindsComponent {

  void inject(MultiInjectActivity activity);
}
public class MultiInjectActivity extends AppCompatActivity {

  public static final String TAG = "MultiInjectActivity";
  // MultiBinds
  @Inject
  Set<String> stringSet;
  @Inject
  Set<Integer> integerSet;

  @Inject
  Map<String, Integer> mSIMap;

  @Inject
  Map<String, String> mSSMap;

  @Inject
  Map<MapEntityType, MapEntity> mEntityMap;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_muiti_inject);

    inject();
    print();
  }

  private void inject() {
    DaggerMultiBindsComponent.builder()
        .setComponent(DaggerSetComponent.create())
        .mapComponent(DaggerMapComponent.builder().build())
        .build()
        .inject(this);
  }
}

MultiBindsModule 是 抽象类,@Multibinds 的使用时要求抽象类,Dagger2 会创建一个空的 Map 或者 Set 集合,如果我们需要注入的集合可能为空的时候才使用这个种方式。

@Module
public abstract class MultiBindsModule {

  @Multibinds
  abstract Set<Integer> provideIntegerSet();

  @Multibinds
  @Named("empty map")
  abstract Map<String, Integer> provideStringMap();
}

在看下依赖的 Component,这里只看下 MapComponent。MapComponent 的定义和 MultiBindsComponent 的定义是一样的,只不过 MapComponent 不会对 Activity 直接注入,它负责给 MultiBindsComponent 的提供依赖。

@Component(modules = MapModule.class)
public interface MapComponent {

  Map<String, Integer> provideSIMap();

  Map<String, String> provideSSMap();

  Map<MapEntityType, MapEntity> provideClassMap();

  @Component.Builder
  interface Builder {
    MapComponent build();
  }
}

MapModule 中提供 Map 集合中的元素,Key 对用方法注解上的 Key,方法返回值对应的是 Value,Dagger2 会把相同 Key - Value 类型的元素放入同一个 Map 中。

@Module
public class MapModule {

  // 放入 Map<String,Integer> 集合中
  @Provides
  @IntoMap
  @StringKey("first")
  static Integer provideFirstInteger() {
    return 11;
  }
  // 放入 Map<String,Integer> 集合中
  @Provides
  @IntoMap
  @StringKey("second")
  static Integer provideSecondInteger() {
    return 22;
  }
  // 放入 Map<String,String> 集合中
  @Provides
  @IntoMap
  @StringKey("first")
  static String provideFirstString() {
    return "AA";
  }
  // 放入 Map<String,String> 集合中
  @Provides
  @IntoMap
  @StringKey("second")
  static String provideSecondString() {
    return "BB";
  }
  
  // 放入 Map<MapEntityType,MapEntity> 集合中
  @Provides
  @IntoMap
  @MapEntityKey(MapEntityType.TYPE_A)
  static MapEntity provideFirstClass(MapEntity entity) {
    return entity;
  }

  // 放入 Map<MapEntityType,MapEntity> 集合中
  @Provides
  @IntoMap
  @MapEntityKey(MapEntityType.TYPE_B)
  static MapEntity provideSecondClass(MapEntity entity) {
    return entity;
  }
}

MapEntityKey 是自定义的一个 MapKey, Dagger2 中提供了 4 种 MapKey,ClassKey、IntKey、StringKey 和 LongKey,这个 MapKey 对用 Map 集合中的 Key,value 对应 Module 中返回的对象。

@MapKey
public @interface MapEntityKey {
  MapEntityType value();
}

public enum MapEntityType {
  TYPE_A, TYPE_B
}

利用 multibinds 可以实现一个 spi(Service Provider Interface) 的 plugin,这种方式实现的 plugin 相比 APT 方式,相对代码会多出一些,这里仅给出一个简单的例子。

在这里插入图片描述

@Singleton
@Component(modules = {UserModule.class, ShoppingModule.class})
public interface PluginComponent {
  Map<Class<?>, Plugin> getPlugins();
}

// module-user --> UserModule
@Module
public class UserModule {
  @Singleton
  @Provides
  @IntoMap
  @ClassKey(UserPlugin.class)
  static Plugin provideUserPlugin(UserPluginImpl userPlugin) {
    return userPlugin;
  }
}

// module-shopping --> ShoppingModule
@Module
public class ShoppingModule {

  @Singleton
  @Provides
  @IntoMap
  @ClassKey(ShoppingPlugin.class)
  static Plugin provideUserPlugin(ShoppingPluginImpl shoppingPlugin) {
    return shoppingPlugin;
  }
}
public class PluginManager {
  public static final String TAG = "PluginManager";
  private static final PluginManager INSTANCE = new PluginManager();
  private Map<Class<?>, Plugin> mPluginMaps = new HashMap<>();

  public static PluginManager getInstance() {
    return INSTANCE;
  }

  private PluginManager() {
    //no instance
  }

  @Nullable
  public <T extends Plugin> T getPlugin(Class<T> clazz) {
    if (mPluginMaps != null && !mPluginMaps.isEmpty()) {
      return (T) mPluginMaps.get(clazz);
    }
    return null;
  }

  public void addplugin(Map<Class<?>, ? extends Plugin> pluginMaps) {
    if (pluginMaps == null || pluginMaps.isEmpty()) {
      return;
    }
    for (Map.Entry<Class<?>, ? extends Plugin> entry : pluginMaps.entrySet()) {
      if (!mPluginMaps.containsKey(entry.getKey())) {
        mPluginMaps.put(entry.getKey(), entry.getValue());
      }
    }
  }

plugin 接口

public interface Plugin {
}

public interface ShoppingPlugin extends Plugin {
  void getShopping();
}

public interface UserPlugin extends Plugin {
  void getUserInfo();
}

(3)subcomponent/

Subcomponent 的使用有一定的规则:

  • Subcomponent 能获取到父 Component 中能提供的所有对象
  • Subcomponent 只能有一个父 Component
  • Subcomponent 需要显示的声明一个 Builder,父 Component 中需要对外界提供 Subcomponent 接口
  • Subcomponent 的 Scope 不能和 父 Component 的 Scope 相同
// ParentComponent
@Singleton
@Component(modules = ParentModule.class)
public interface ParentComponent {

  ChildComponent.Builder childComponentBuilder();
}

// ParentModule
@Module(subcomponents = ChildComponent.class)
public class ParentModule {

  @Singleton
  @Provides
  public ParentEntity provideParentEntity() {
    return new ParentEntity();
  }
}
// ChildComponent
@Subcomponent(modules = ChildModule.class)
public interface ChildComponent {

  void inject(SubComponentDemoActivity activity);

  // Subcomponent 需要 定义一个 Builder
  @Subcomponent.Builder
  interface Builder {
    ChildComponent build();
  }
}

// ChildModule
@Module
public class ChildModule {

  @Provides
  public SubEntity provideEntity(ParentEntity entity){
    return new SubEntity(entity);
  }
}

对比 dependencies 形式的依赖,dependencies 形式上类似于组合,subcomponents 形式上类似于继承,可以理解为横向扩展和纵向扩展

在这里插入图片描述

我们知道在设计上优先使用组合,具有更好的扩展性,但 Subcomponent 也是有一定应用场景的,父 Component 中的资源需要注入到 Subcomponent 中。这种情况虽然可以也使用组合依赖的形式,但是使用组合依赖,会重新创建一个 Component。如:ComponentB 和 ComponentC 均依赖 ComponentA,使用组合形式,那么 ComponentB 和 ComponentC 中的 ComponentA 不是同一个,那么导致 ComponentB 和 ComponentC 不能使用相同的资源,在 Android 中可以保证 ComponentA 是 Application 级别单例,这种情况是没问题的。假设一个 Activity 中有个多个 Fragment,这种情况下每个 Fragment 都需要 Activity 级别的 Component 注入,那么每个 Fragment 需要拿到同一个 Activity 的 Component,这个 Component 就需要维护,这样的情况使用 Subcomponent 会更好一些,每个 Fragment 中的 SubComponent 都是在 Activity 中的 Component 创建的,并且 能保证 Fragment 的 SubComponent 的 Scope 小于 Activity 的 Scope。

在这里插入图片描述

2.4 Scope

@Scope 代表作用域,实际上是 Component 的作用域,并不是说打上 @Singleton ,就一定代表是单例,单例的维护主要靠对应的 Component 来维护,将 Component 设置为 Application 级别的对象,也是在整个应用中 Component 只有一个,那么它提供的对应也只有一个。

@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScope {
}

@Scope
@Documented
@Retention(RUNTIME)
public @interface FragmentScope {
}

上面提到 Subcomponent 不能和父 Component 的 Scope 一致,对于组合形式依赖的 Component,之间也不能有相同的 Scope,也就是说不同 Component 之间的 Scope 不能相同。JakeWharton 给出一个解释,如果两个不同 Component 有相同的 Scope,会打破 Scope 的限制。

在这里插入图片描述

2.5 Dagger 在 Android 上使用的问题

1、因为Android 中的四大组件是有生命周期的,而且实例的创建是有系统来完成的,而最好的方式使用 Dagger,是完全有 Dagger 来创建实例,所以就需要我们在生命周期中来完成注入的工作,就会出现这样的代码:

    DaggerEntityComponent.builder()
        .build()
        .injectEntity(this);

这样带来的问题:大量类似的代码导致后期的维护和重构问题。根本上来讲,它要求需要被注入的类型,如 Activity 依赖 injector,也就是 Component,,尽管是面向接口的,但是仍旧破坏了依赖注入的原则,需要被注入的类,不应该知道如何被注入,只关心被注入的值。

3 Dagger 原理简单分析

3.1 APT

APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的 Annotation,根据注解自动生成代码。Annotation 处理器在处理 Annotation 时可以根据源文件中的 Annotation 生成额外的源文件和其它的文件(文件具体内容由Annotation 处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成 class 文件。ButterKnife,dagger 等开源注解框架都采用了 APT 技术。APT 的使用能很好的简化开发工作,提高效率。

APT 主要流程

  • 定义注解(如@automain)
  • 定义注解处理器,自定义需要生成代码
  • 使用处理器
  • APT自动完成如下工作。

3.2 Dagger 原理

在这里插入图片描述

1、APT 生成 DaggerComponent 生成,名称为 Dagger + 自定义 Component 名字,如自定义 EntityComponent,生成后名字为 DaggerEntityComponent,提供构建方法有两种,通过 Builder 构建,也可以通过 create() 静态方法构建,前提是 Builder 没有参数,create() 实际上也是调用 Builder 构建。我们可以在自定义 Component 显示生命 @Component.Builder,否则 Dagger2 为默认生成一个 Builder,参数则是依赖的 Module 和 Component

  • Builder 构建 Component
  public static final class Builder {
    private EntityModule entityModule;

    private Builder() {
    }

    public Builder entityModule(EntityModule entityModule) {
      this.entityModule = Preconditions.checkNotNull(entityModule);
      return this;
    }

    public EntityComponent build() {
      if (entityModule == null) {
        this.entityModule = new EntityModule();
      }
      return new DaggerEntityComponent(entityModule);
    }
  }
  • 使用 MemberInjector 注入

MemberInjector 的情况是 Component 接口中函数中带参数,所带的参数就是需要被注入的对象,对该对象中 @Inject 的成员变量进行注入。没有参数的情况则不会生成 MemberInjector,而是对函数的返回参数提供实例。这里仅分析一下函数中带参数生成 MemberInjector 情况。

生成 InjectorDemoActivity_MembersInjector,DaggerEntityComponent 通过 InjectorDemoActivity_MembersInjector 对 InjectorDemoActivity 中参数进行注入。

  @Override
  public void injectEntity(InjectorDemoActivity activity) {
    injectInjectorDemoActivity(activity);
  }

  private InjectorDemoActivity injectInjectorDemoActivity(InjectorDemoActivity instance) {
    InjectorDemoActivity_MembersInjector.injectMInjectEntity(instance, new InjectEntity());
    InjectorDemoActivity_MembersInjector.injectMSingletonEntity1(instance, singletonEntityProvider.get());
    InjectorDemoActivity_MembersInjector.injectMSingletonEntity2(instance, singletonEntityProvider.get());
    InjectorDemoActivity_MembersInjector.injectMModuleEntity(instance, EntityModule_ProvideEntityFactory.provideEntity(entityModule));
    InjectorDemoActivity_MembersInjector.injectMBindsEntity1(instance, namedBindsEntity());
    InjectorDemoActivity_MembersInjector.injectMBindsEntity2(instance, new BindsEntity());
    return instance;
  }

InjectorDemoActivity_MembersInjector 需要注入的对象实例来源主要有以下几种情况:

(1)Component 中的 Provider,这种一般是单例,或者是 Module 中的方法中有参数,对应的参数会生成 Provider

(2)直接 new 一个实例,对应构造函数上有 @Inject 注解,或者 Module 中使用 @Binds 抽象方法中参数,官方文档一般建议使用 @Binds,因为能够少少生成一些辅助代码

(3)Component 中有 Module 实例或者其他 Component 实例,这种情况则使用 Module 实例或者其他 Component 对应的方法提供的实例对象,如果是 Module,则不是直接调用 Module 实例的方法,而是通过 Module_Factory 中间类来调用 Module 中的方法来提供实例

(4)Component 依赖的 Module 中提供实例的方式是静态的,同样 MemberInjector 会通过 Module_Factory 中间类来调用 Module 中方法来提供实例。

上述就是注入过程需要的核心类,注入的出发过程就是调用 Component 的中方法。

4 Dagger2 在 MVP 中的使用

Dagger2 是一个依赖注入框架,在 Java 开发或者 Android 开发中都可以使用,这里看下在 MVP 框架中的使用,当然在 MVVM 同样是可以使用。

在这里插入图片描述

没有使用 Dagger 的 MVP,P 层和 M 层都需要自己创建,同时 P 层和 M 层中依赖的对象也要手动获取,就前面所说,如果 App 工程越来越大,对于开发成本从长期角度会很高, Dagger 的引入主要就是解决这个问题。
MVP 的注入从以下两个方面来考虑:

  • 每一层需要注入的实例

V 层 :对于 V 层(四大组件,Fragment),如果依赖 P 层,则需要给 V 层注入 P;

P 层:依赖 M 层和 V 层,需要注入 M 层以及对应 V 实现的 IView 接口实例,实际上就是四大组件或 Fragment;

M 层:依赖一些全局的实例,如网络请求配置,缓存等

  • 注入的时机

由于 Android 中四大组件是由系统来创建实例,所以对于这些组件中的 P 层注入就需要在合适时机,即根据他们生命周期来注入,一般会在生命周期回调最先调用的方法中进行注入,对于全局的 Component,会设置成单例,在 Application 中进行创建和保存,然后会传递给四大组件的 Component 作为依赖的 Component。

这里分析一个比较好的 MVP 的开源框架 MVPArms,该框架采用 MVP+Dagger2+Retrofit+RxJava,集成了多个开源框架,开发起来相对比较容易,前提需要对 Dagger2 有一定的理解。

4.1 MVPArms 简单说明

(1)需要全局配置 GlobalConfiguration,需要包括网络相关配置,异常处理,缓存配置,Gson 解析配置等,需要在 AndroidManifest.xml 中注册

 <!-- Arms 配置 -->
 <Meta-data android:name="me.jessyan.mvparms.demo.app.GlobalConfiguration"
 android:value="ConfigModule" />

在这里插入图片描述

(2)从分包结构上可以看出,di 包是依赖注入的相关类,Component 和 Module,mvp 包除了 Model、Presenter、View,还有一个 contract,用于生命 Model 和 View 的接口,只是存放的一个规则,也可以不放在一起。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVJuiKK0-1610253637469)(https://github.com/RalfNick/PicRepository/raw/master/dagger2/dagger_arms_apply.png)]

一个页面中不一定仅仅有一个 Component,可能有多个 Component 和对应的 Module,但是仅有一个 Component 对 Activity 或者 Fragment 进行注入,Component 提供的对象,都是 M 层和 P 层需要的对象。

4.2 MVPArms 框架分析

在这里插入图片描述

Activity 级别的 Component 依赖 Application 级别的 Component(单例),对 Presenter 和 Model 进行注入,这里的注入时机设计的比较好:首先 AppComponent 在 Application 的 onCreate()方法中创建和注入,并保存成全局的对象,然后注册 Application.ActivityLifecycleCallbacks 实现,在 onActivityCreated 回调中对 Activity 进行注入,对于 Fragment 同样,在
Activity 回调 onActivityCreated 中,判断如果有 Fragment 则注册 FragmentLifecycleCallbacks,在 FragmentLifecycleCallbacks 的 onFragmentCreated 回调中对 Fragment 进行注入。

该框架一个比较巧妙的设计:将配置信息配置在 AndroidManifest.xml 中,对于不同的模块可以设置自己的独特的配置,解析 AndroidManifest.xml 时会把所有的配置 Configuration 读取出来,作为依赖注入的提供者。

优势:该框架高度集成,也具有很好的可配置性,对于前中期 APP 开发,能够完成快速开发,对于 Component 和 Module,甚至是 Presenter 也能够做到复用。

缺点 :多了 Module 和 Component,这个也是属于 Dagger2 的缺点,由于 Android 组件生命周期的存在,不能像 Spring 那样仅仅用一个注解就完成依赖注入。此外框架中使用 Presser 的主要是在 Activity 或者 Fragment 中,Recyclerview 中 item 没有使用 presenter,即没有采用 MVP 模式,对于简单的列表页面可以不用采用,对于复杂的列表情况,可以考虑集成 MVP 模式。

5 总结

  • 介绍了依赖注入 (Dependency Injection) 的概念,是 IOC 的一种实现方式,以及 DI 的几种方式,构造函数传入;setter 设置;接口传入;使用 DI 框架,Android 中的 Dagger2。
  • Dagger2 是基本 javax 中的 @Inject、@Scope 等注解基础上开发的,以及在 性能(Performance)、正确性(Correctness)、扩展性方面的特点,在中型和大型 App 中使用 Dagger2 收益更高。
  • 介绍 Dagger2 中常用几个注解,以及基本使用方式,利用 multibinds 实现一个简单的 plugin。
  • Dagger2 是编译器框架,利用 APT 技术生成辅助代码,供开发者在运行时调用,在合适的时机进行注入,Dagger2 没有使用反射等操作,所以性能比较高
  • 分析了一个开源框架 MVPArms 对于 Dagger2 的应用以及分析了其基本原理
  • 对于 Dagger2 的应用,官方也提供了一套 Android-Dagger2 框架,个人认为集成度有点高,理解起来相对困难,

参考

Demo

Dagger2

Dagger 官方文档

Using Dagger in Android apps

Dependency Injection guidance on Android — ADS 2019

MVPArms

Dagger 2. Part II. Custom scopes, Component dependencies, Subcomponents

轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)

轻松学,听说你还没有搞懂 Dagger2

android dagger2使用笔记

android dagger2使用笔记

Dependency Injecte(依赖注入)

首先写个不使用依赖注入的示例
  • interface
// House.java
public interface House {
    void prepareForWar();

    void reportForWar();
}
  • 新建两个实现 House 接口的类
// Starks.java
public class Starks implements House {

    @Override
    public void prepareForWar() {
        //do something
        System.out.println(this.getClass().getSimpleName()+" prepared for war");
    }

    @Override
    public void reportForWar() {
        //do something
        System.out.println(this.getClass().getSimpleName()+" reporting..");
    }
}
// Boltons.java
public class Boltons implements House {
    @Override
    public void prepareForWar() {
        //do something
        System.out.println(this.getClass().getSimpleName()+" prepared for war");
    }

    @Override
    public void reportForWar() {
        //do something
        System.out.println(this.getClass().getSimpleName()+" reporting..");
    }
}
  • 接着需要一个依赖于这两个的类的类
public class War {

    private Starks starks;

    private Boltons boltons;

    public War(){
        starks = new Starks();
        boltons = new Boltons();

        starks.prepareForWar();
        starks.reportForWar();
        boltons.prepareForWar();
        starks.reportForWar();
    }

}
  • 下次改用依赖注入的方式实现这个类
public class War {

    private Starks starks;
    private Boltons boltons;
    
    //DI - getting dependencies from else where via constructor
    public War(Starks starks, Boltons bolton){
        this.starks = starks;
        this.boltons = bolton;
    }

    public void prepare(){
        starks.prepareForWar();
        boltons.prepareForWar();
    }

    public void report(){
        starks.reportForWar();
        boltons.reportForWar();
    }

}
  • 从外部注入依赖的对象
public class BattleOfBastards {

    public static void main(String[] args){

        Starks starks = new Starks();
        Boltons boltons = new Boltons();

        War war = new War(starks,boltons);
        war.prepare();
        war.report();
    }
}
利用dagger2进行依赖注入
  • Dagger 2 works on Annotation processor. 需要了解一定的java注解知识
  • 首先了解dagger2最常用的2个注解 @Inject@Component

@Inject Annotation

可以作用于

  • 构造器
  • 字段
  • 方法

@Inject注解告诉dagger哪些方法,构造器或者字段是需要依赖注入

But @Inject doesn’t work everywhere:

  • Interfaces can’t be constructed.
  • Third-party classes can’t be annotated.
  • Configurable objects must be configured!

@Component Annotation

作用于接口

@Componentdagger会生成一个实现该接口的class,该class会实现其中的方法,提供需要依赖的对象,相当于是一个代理

修改的代码

添加默认构造器,并添加注解@Inject

public class Boltons implements House {

    // 添加默认构造器,并添加注解@Inject
   @Inject
   public Boltons(){
    }

    @Override
    public void prepareForWar() {
        //do something
        System.out.println(this.getClass().getSimpleName()+" prepared for war");
    }

    @Override
    public void reportForWar() {
        //do something
        System.out.println(this.getClass().getSimpleName()+" reporting..");
    }
}
public class Starks implements House {

    @Inject //Dagger 2
    public Starks(){
    }

    @Override
    public void prepareForWar() {
        //do something
        System.out.println(this.getClass().getSimpleName()+" prepared for war");
    }

    @Override
    public void reportForWar() {
        //do something
        System.out.println(this.getClass().getSimpleName()+" reporting..");
    }
}
public class War {

    private Starks starks;

    private Boltons boltons;

    @Inject
    public War(Starks starks, Boltons bolton){
        this.starks = starks;
        this.boltons = bolton;
    }

    public void prepare(){
        starks.prepareForWar();
        boltons.prepareForWar();
    }

    public void report(){
        starks.reportForWar();
        boltons.reportForWar();
    }

}
  • 修改依赖加载方式
public class BattleOfBastards {

    public static void main(String[] args){
//        Mannual DI
//        Starks starks = new Starks();
//        Boltons boltons = new Boltons();
//        War war = new War(starks,boltons);
//        war.prepare();
//        war.report();

//      Using Dagger 2
        BattleComponent component = DaggerBattleComponent.create();
        War war = component.getWar();
        war.prepare();
        war.report();

    }
}
在build之后查看annotation processor 自动生成的代码
  • 如图

  • DaggerBattleComponent.java 为dagger自动生成的代码,已类名加上Dagger前缀命名, 该类实现了BattleComponent接口
package com.explore.lin.didemo.javaDIdemo;

import javax.inject.Provider;

public final class DaggerBattleComponent implements BattleComponent {
  private Provider<War> warProvider;

  private DaggerBattleComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static BattleComponent create() {
    return new Builder().build();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.warProvider = War_Factory.create(Starks_Factory.create(), Boltons_Factory.create());
  }

  @Override
  public War getWar() {
    return new War(new Starks(), new Boltons());
  }

  public static final class Builder {
    private Builder() {}

    public BattleComponent build() {
      return new DaggerBattleComponent(this);
    }
  }
}
  • 继续在component中添加方法
@Component
interface BattleComponent {
    War getWar();
    //adding more methods
    Starks getStarks();
    Boltons getBoltons();
}
  • 观察dagger2生成的DaggerBattleComponent中
  // DaggerBattleComponent.java 
  @Override
  public War getWar() {
    return new War(new Starks(), new Boltons());
  }

  @Override
  public Starks getStarks() {
    return new Starks();
  }
  • 如果我们删除Boltons.java构造器中的注解@Inject,会发现无法通过编译,因为在war.java的构造器中存在对Boltons的依赖
总结

比如有个classA中存在对classB的依赖,用dagger2怎么实现呢

  • 一种
class A {

    @Inject
    public A() {
    }

    @Inject
    B b;

    void act() {
        b.prepare();
    }
}

class B {
    @Inject
    public B() {

    }

    void prepare() {
        System.out.println("b.prepare()");
    }
}

@Component
interface AComponent {
    A a();
}

public class InjectDemo {

    public static void main(String[] args) {
        DaggerAComponent.create().a().act();
    }
}
  • 二,什么时候使用@Provide,比如你使用第三方库,或则B中构造器没有@Inject
class AP {
    @Inject
    public AP() {

    }
    @Inject
    BP bp;

    void act() {
        bp.prepare();
    }
}

class BP {
    void prepare() {
        System.out.println("bp.prepare()");
    }
}

@Component(modules = {APModule.class})
interface APComponent{
    AP ap();
}

@Module
class APModule {

    @Provides
    BP providerBP() {
        return new BP();
    }
}

public class ProvideDemo {
    public static void main(String[] main) {
        DaggerAPComponent.create().ap().act();
    }
}
在android中应用dagger
  • 同样先新建不使用dagger的项目

参考https://github.com/DaiHangLin/dependencyInjecte

Android databinding 和 dagger2 冲突问题

Android databinding 和 dagger2 冲突问题

databinding 和 dagger2 之间还会因为 android-apt 出现冲突,单独使用 databinding,编译能通过,同时使用 dagger2,databinding 就不能生成相应的类,当前 dagger2 版本为 2.9

尝试过网上的教程:更新到 1.8

classpath ''com.neenbedankt.gradle.plugins:android-apt:1.8''
provided ''com.google.dagger:dagger-compiler:2.9''

但是都不起作用,有同时使用这两个库的大神,指点下我,应该怎样解决吗

Android espresso – 如何使用dagger2注入依赖项

Android espresso – 如何使用dagger2注入依赖项

使用dagger2将依赖注入espresso测试是我想要做的.

我想要一种能够使用dagger为我的测试用例提供依赖关系的方法.

特别是有一个MockwebServer级,我想用匕首注入.这是怎么做到的?
我的项目已经设置了匕首.它现在是一个单独的组件,单个组件有5个模块
看起来像这样:

@Singleton
@Component(modules = {AppModule.class, NetworkModule.class, RepositoryModule.class, UseCaseModule.class, ActivityModule.class, PresenterModule.class})
public interface AppComponent {

    void inject(NetworkSessionManager target);
    void inject(SplashActivity target);
    void inject(AuthenticationActivity target);
    void inject(WelcomeActivity target);
    void inject(LoginFragment target);
}

它的工作很精细.但是现在当我移动到androidTest文件夹进行espresso测试时,我将如何使用以下组件:

    //note the NetworkTestModule.class i want to use is defined instead of //networkModule.class
        @Singleton
        @Component(modules = {AppModule.class, NetworkTestModule.class, RepositoryModule.class, UseCaseModule.class, ActivityModule.class, PresenterModule.class})
        public interface AppTestComponent

 {

        void inject(NetworkSessionManager target);
        void inject(SplashActivity target);
        void inject(AuthenticationActivity target);
        void inject(WelcomeActivity target);
        void inject(LoginFragment target);
        void inject (MYTESTCLASS target);
    }

我一直在做什么将AppTestComponent保留在主要源代码中,但是它不能以这种方式看到MYTESTCLASS?

我想要注入我的类的原因是,我希望在将它传递给改造之后注入一个mockWebServer类,就像这样的baseurl:

TestNetworkModule.java:

@Provides
@Singleton
public Retrofit provideRetrofit(Converter.Factory converter, OkHttpClient client, @Named(BASE_URL) String baseUrl, MockWebServer server) {
    return new Retrofit.Builder()
            .baseUrl(server.url("/").toString())
            .client(client)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(converter)
            .build();
}

    @Singleton
    @Provides
    MockWebServer providerMockWebServer() {
        return new MockWebServer();
    }
    //....
    }

这样我就可以获得MockWebServer的参考,并在我的测试中使用它,并对其进行改造,以便我可以进行快速集成测试

同样在gradle我使用以下依赖项请确认:

 compile 'com.google.dagger:dagger:2.9'
testCompile 'com.google.dagger:dagger:2.9'
annotationProcessor 'com.google.dagger:dagger-compiler:2.9'

解决方法:

我将尝试解释它是如何做到的:

在您的应用程序类中,您应该指定包含API服务的Application组件:

protected AppComponent initializeAppComponent() {
    return DaggerAppComponent.builder()
            .apiServiceModule(new APIServiceModule(this))
            .otherModules(new otherModules(this))
            .build();
}

并注入这样:

@Override
public void onCreate() {
    applicationComponent = initializeAppComponent();
    applicationComponent.inject(this)}};

它是标准初始化.您只需将组件构建器移动到稍后可以覆盖的方法.

在你的Android测试包中:

现在,您需要创建新的Application类,它扩展了您的应用程序类,您可以在其中初始化dagger组件.

所以现在你可以覆盖initializeAppComponent()方法并通过扩展前一个Module的新方法切换你的APIServiceModule.
它应该是这样的:

public class MockApp extends App {

@Override
protected AppComponent initializeAppComponent() {
    return DaggerAppComponent.builder()
            .apiServiceModule(new MockAPIServiceModule(this))
            .otherModules(new OtherModules(this))
            .build();
}

@Module
private class MockAPIServiceModule extends APIServiceModule {

    @Override
    public ApiService provideApiService(@Nonnull final Retrofit retrofit,
                                        @Nonnull final Gson gson) {
        return new ApiService() {
            @Override
            public Observable<LoginResponse> login(@Body final LoginRequest loginRequest) {
                return // what you want to
            }
        }
    }
}}

您需要声明应该将哪个Application类用于测试.
现在你还需要做两件事:
 1.创建指向新App类的新跑步者

public class MockTestRunner extends AndroidJUnitRunner{

@Override
public void onCreate(Bundle arguments) {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().build());
    super.onCreate(arguments);
}
@Override
public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, illegalaccessexception, ClassNotFoundException {
    return super.newApplication(cl, MockApp.class.getName(), context);
}}

>声明将在build.gradle中使用哪个运行器.

testInstrumentationRunner“.MockTestRunner”

而已.现在您的请求将使用模拟响应!
如果您有任何疑问,请询问.

干杯

android – Dagger2没有生成Dagger *类

android – Dagger2没有生成Dagger *类

正如标题所说,Dagger2没有为我的Android项目生成Dagger *前缀类.我查看了我能找到的所有其他类似帖子,但没有任何帮助.我正在尝试将它添加到现有项目中,并且我遇到了一些初始问题,使其能够很好地处理数据绑定,但我似乎已经对其进行了排序,即数据绑定没有编译错误,并且它为它生成代码.我还下载了几个工作正常的示例项目.

我的顶级graddle文件有

    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

而我的构建级别有

apply plugin: 'com.neenbedankt.android-apt'

....
final DAGGER_VERSION = '2.8'
....

    compile 'javax.inject:javax.inject:1'
    compile 'javax.annotation:javax.annotation-api:1.2'

    apt "com.google.dagger:dagger:$DAGGER_VERSION"
    compile "com.google.dagger:dagger:$DAGGER_VERSION"
}

组件文件非常简单

@Singleton
@Component(modules = {
        AndroidModule.class
})

public interface ApplicationComponent {
    void inject(MainActivity activity);
}

AndroidModule文件包含

@Module
public class AndroidModule {
    private final Context application;

    public AndroidModule(Application application) {
        this.application = application;
    }
}

和Application类有

import com.rockwellcollins.fsl.dagger.DaggerApplicationComponent;
...

public class FslApplication extends Application {

    private static ApplicationComponent component;

    @Override
    public void onCreate() {
        super.onCreate();

        component = DaggerApplicationComponent.builder()
                .androidModule(new AndroidModule(this))
                .build();
    }

    public static void inject(MainActivity target) {
        component.inject(target);
    }

    public ApplicationComponent getComponent() {
        return component;
    }
}

我得到的输出是

information:Gradle tasks [clean, :android-sliding-layer-lib:Library:generateDebugSources, :android-sliding-layer-lib:Library:mockableAndroidJar, :android-sliding-layer-lib:Library:prepareDebugUnitTestDependencies, :android-sliding-layer-lib:Library:generateDebugAndroidTestSources, :android-sliding-layer-lib:Library:compileDebugSources, :android-sliding-layer-lib:Library:compileDebugUnitTestSources, :android-sliding-layer-lib:Library:compileDebugAndroidTestSources, :mobile:generateDebugSources, :mobile:mockableAndroidJar, :mobile:prepareDebugUnitTestDependencies, :mobile:generateDebugAndroidTestSources, :mobile:compileDebugSources, :mobile:compileDebugUnitTestSources, :mobile:compileDebugAndroidTestSources, :wear:generateDebugSources, :wear:generateDebugAndroidTestSources, :wear:mockableAndroidJar, :wear:prepareDebugUnitTestDependencies, :wear:compileDebugSources, :wear:compileDebugAndroidTestSources, :wear:compileDebugUnitTestSources]

Warning:WARNING: Dependency org.json:json:20131018 is ignored for debug as     it may be conflicting with the internal version provided by Android.
Warning:WARNING: Dependency org.json:json:20131018 is ignored for release as it may be conflicting with the internal version provided by Android.
Warning:View field aircraftList collides with a variable or import
Warning:View field groundList collides with a variable or import

C:\Git\android\mobile\src\main\java\com\rockwellcollins\fsl\FslApplication.java
Error:(7, 38) error: cannot find symbol class DaggerApplicationComponent
Error:org.gradle.api.internal.tasks.compile.CompilationFailedException: 
Compilation Failed; see the compiler error output for details.
information:BUILD Failed

正如我所说,Dagger的东西根本没有代码生成.如果有人能指出问题是什么,我将非常感激.

解决方法:

你的依赖应该是

apt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"

您缺少工件ID的-compiler部分.

此链接还包含一些有用的信息.
https://github.com/google/dagger#android-gradle

我们今天的关于Android 依赖注入 DI - Dagger2android 依赖注入原理的分享就到这里,谢谢您的阅读,如果想了解更多关于android dagger2使用笔记、Android databinding 和 dagger2 冲突问题、Android espresso – 如何使用dagger2注入依赖项、android – Dagger2没有生成Dagger *类的相关信息,可以在本站进行搜索。

本文标签: