依赖注入框架Dagger2

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

什么的依赖注入

在软件工程中,依赖注入是实现控制反转的方式之一。百度百科中对于控制反转的解释如下:控制反转(Inversion of Control,英文缩写为IoC)把创建对象的权利交给框架。我们可以举例来说明一下,在开发中我们经常有类组合的情况,例如Student类中引用了School类,用于描述学生的学校信息,那么这就是典型的组合,再具体点的例子:假设我们的网络请求类RetrofitManager中需要有一个OkHttpClick对象。

public class RetrofitManager {

    OkHttpClient client;

    public RetrofitManager() {
        client = new OkHttpClient.Builder()
                .connectTimeout(1000L, TimeUnit.SECONDS).build();
    }
}

我们可以看到上面的类中直接在构造器中将client对象进行初始化,那么此时就产生了较高的耦合现象。这种在一个类中直接创建另一个类的对象的代码,和硬编码(hard-coded strings)以及硬编码的数字(magic numbers)一样,是一种导致耦合的坏味道,我们可以把这种坏味道称为硬初始化(hard init),违背了单一职责原则与开闭原则,同时也会严重影响到单元测试与代码的可读性。

试想一下,领导现在要求你修改网络请求的相关参数或者让你添加固定的请求头又或者要添加相关的网络拦截器等等。这时候我们就必须对RetrofitManager中的client初始化进行修改。当然,这个例子还好,可能就只有这一个地方对吧。但是如果是其他情况,修改的地方有可能是成千上万。因此,我们需要使用依赖注入来解决这个问题。

实现依赖注入的三种方式

这里会介绍几种实现依赖注入的简单方式,而复杂的依赖注入框架当然思想都是一样的。就是避免在调用者中直接实例化(new)被调用者对象,而是通过其他方式进行引入。

1.构造器注入

直接通过构造器参数的形式将被调用者的对象传入

public class RetrofitManager {

    OkHttpClient client;

    public RetrofitManager(OkHttpClient client) {
        this.client = client;
    }
}

2.setter注入

通过提供setter方式让外界将对象注入

public class RetrofitManager {

    OkHttpClient client;

    public void setClient(OkHttpClient client) {
        this.client = client;
    }
}

2.接口注入

定义注入接口,让调用者都实现改接口

public interface Inject {

    void inject(OkHttpClient client);
}

public class RetrofitManager implements Inject{

    OkHttpClient client;

    @Override
    public void inject(OkHttpClient client) {
        this.client = client;
    }
}

OK,简单了解了依赖注入与耦合问题之后,我们再来看Dagger2

Dagger2是什么

Dagger2

Dagger2一个Android和java快速依赖注入框架,Dagger2是Dagger的升级版,目前由Google进行维护。Dagger2中杜绝了反射的使用,而是采用编译时注解的方式在编译时候实现代码的自动生成。

注解的分类

Annotation(注解)是Java提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的Annotation对象,然后通过该Annotation对象来获取注解中的元数据信息。

通常自定义注解时我们会通过@Retention元注解(元注解是专门用来定义注解的注解)来标记该注解的保留周期:.SOURCE:在源文件中有效(即源文件保留)、CLASS:在class文件中有效(即class保留)、RUNTIME:在运行时有效(即运行时保留)。SOURCE,在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。CLASS,在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。RUNTIME,始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。

扯一些题外话,早期的一些控件注入框架普遍采用RUNTIME(运行时)形式的注解通过反射机制在运行时动态完成控件的注入工作。甚至是一些依赖注入框架也采用这种实现形式。大家都知道,反射对性能是有影响的,所有现在流行的框架一般都是采用CLASS(编译时)形式的注解配合javapoet或者javassist等字节码操作工具实现编译时修改代码或者生成类文件的方式去实现相关功能。

Dagger2的引入

有两种方式可以完成Dagger2的依赖,第一种方式是使用android-apt,第二种方式是使用annotationProcessor。前者是开发者开发的apt框架,后者是谷歌Android Gradle插件支持的apt方式。Dagger2最新文档上可以看到Android端推荐使用的是第二种方式。具体细节可查看博客:http://blog.csdn.net/xx326664162/article/details/68490059

我们采用第二种方式对Dagger2进行依赖,直接在项目gradle中添加以下两行

annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
compile 'com.squareup.okhttp3:okhttp:3.10.0'

Inject + Component

1.定义类ClassA,为它的无参构造器加上注解@Inject,表明该类支持快速依赖注入

public class ClassA {

    @Inject
    public ClassA() {
    }
}

2.定义接口MainActivityComponent,为该接口添加注解@Component,定义抽象方法,其他返回值为void,参数为需要注入对象的类(必须的需要注入对象的真实所在类,比如:MainActivity,而不能是父类Activity),方法名无限制,但是一般规范为inject或者injectXXX。Component可以理解为classA对象(被调用者)与MainActivity(调用者)之间的桥梁。

@Component
public interface MainActivityComponent {

    void inject(MainActivity activity);
}

3.对项目重新Build,框架会根据MainActivityComponent自动生成DaggeMainActivityComponent类(命名规则为Dagger+Component名称),这也验证了在上面说到的自动生成代码的这茬。在MainActivity中声明ClassA成员对象,并用@Inject注解标记,然后使用DaggeMainActivityComponent完成对象注入。

public class MainActivity extends AppCompatActivity {

    @Inject
    ClassA classA; // 不能是private,后面会提到

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerMainActivityComponent.builder().build().inject(this);
        System.out.println("MainActivity.onCreate" + classA.toString());
    }
}

com.exp.dagger2 I/System.out: MainActivity.onCreatecom.exp.dagger2.ClassA@2711f49

这个例子很简单,那么我们现在来看一下Dagger2生成的类在哪?类文件的内容是什么?是怎么完成对象注入的。我们将目录结构切换到project模式下进入到 \app\build\generated\source\apt\debug 下可以发现自动生成的三个类:ClassA_Factory、DaggerMainActivityComponent、MainActivity_MembersInjector。

ClassA_Factory是对应ClassA类中构造器上的@Inject注解,只要类的构造器标记@Inject,那么Dagger2框架会自动为其生成对应的工厂类,用于提供实例化对象,那肯定是通过某种途径提供给需要的地方(暂时我们只能认为是MainActivity)

// Generated by dagger.internal.codegen.ComponentProcessor (https://google.github.io/dagger).
package com.exp.dagger2;

import dagger.internal.Factory;

public final class ClassA_Factory implements Factory<ClassA> {
  private static final ClassA_Factory INSTANCE = new ClassA_Factory();

  @Override
  public ClassA get() {
    return new ClassA();
  }

  public static Factory<ClassA> create() {
    return INSTANCE;
  }

DaggerMainActivityComponent很显然对应的是MainActivityComponent,DaggerMainActivityComponent是由Dagger2生成的一个MainActivityComponent接口的实现类。

public final class DaggerMainActivityComponent implements MainActivityComponent {
  private MembersInjector<MainActivity> mainActivityMembersInjector;

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

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

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

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

    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(ClassA_Factory.create());
  }

  @Override
  public void inject(MainActivity activity) {
    mainActivityMembersInjector.injectMembers(activity);
  }

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

    public MainActivityComponent build() {
      return new DaggerMainActivityComponent(this);
    }
  }
}

从代码中可以看出DaggerMainActivityComponent构造器的唯一调用是在Builder中,通过builder设计模式来完成Component的实例化。Builder类中的build生成了Component对象,而我们是通过inject方法完成对象注入,而我们可以看到mainActivityMembersInjector对象中实际调用的是MainActivity_MembersInjector的injectMembers方法

public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
  private final Provider<ClassA> classAProvider;

  public MainActivity_MembersInjector(Provider<ClassA> classAProvider) {
    assert classAProvider != null;
    this.classAProvider = classAProvider;
  }

  public static MembersInjector<MainActivity> create(Provider<ClassA> classAProvider) {
    return new MainActivity_MembersInjector(classAProvider);
  }

  @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.classA = classAProvider.get();
  }

  public static void injectClassA(MainActivity instance, Provider<ClassA> classAProvider) {
    instance.classA = classAProvider.get();
  }
}

在实例化MainActivity_MembersInjector传入了ClassA_Factory对象,在injectMembers方法中直接通过instance.classA进行对象赋值(因此注入目标成员的修饰符不能是private,否则此处无法直接引用),而classAProvider.get()就是ClassA_Factory中返回的new ClassA()。

整个简单的流程就分析完了,现在再来总结下。首先我们的ClassA类构造器添加了@Inject,Dagger2为其生成工厂类。接着我们定义了Component接口,声明抽象注入方法,Dagger2为其生成了对应的实现了并在方法实现中调用MainActivity_MembersInjector的inject方法,而MainActivity_MembersInjector中封装了通过ClassA_Factory实现classA对象的注入。

简单流程浅析

Inject + Module + Component

上面的ClassA是我们自己定义的类文件,现在有个问题。假设我们需要为某个类注入一个OkHttpClient对象。好的,那么咱们去OkHttpClient类中找个地方加@Inject注解,呃呃呃…不对,通过外部gradle命令依赖的它很高冷,咱们不能修改它。这个时候我们需要用到Module,就是说这个时候可以用Module去代替上面的@Inject(这么说不恰当)

怎么理解Module呢,我们可以想一下上个例子,构造器是干什么的?完成对象实例化的,提供对象的。没错,Module我们可以这么理解,它是用来向Component提供依赖对象的。实际操作一下~

1.创建一个类MainModule,并用注解@Module标记。类中定义提供OkHttpClient对象的方法并用@Provides标记

@Module
public class MainModule {

    @Provides
    public OkHttpClient provideOkHttpClient(){
        return new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).build();
    }
}

2.修改MainActivityComponent,为其使用@Component注解添加module引用。表明这个Component可能需要用到MainModule中提供的对象

@Component(modules = MainModule.class)
public interface MainActivityComponent {

    void inject(MainActivity activity);
}

3.MainActivity增加OkHttpClient对象,并标记@Inject

    @Inject
    OkHttpClient client;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerMainActivityComponent.builder().build().inject(this);
        System.out.println("MainActivity.onCreate" + client);
    }

com.exp.dagger2 I/System.out: MainActivity.onCreateokhttp3.OkHttpClient@d28c04e

这个过程与上个例子的区别主要在于MainModule,咱们这次再看生成的类是什么样的。我们主要讲解差异点

自动生成的类文件

多了MainModule_ProvideOkHttpClientFactory类,该类其实与ClassA_Factory本质是一样的。只不过ClassA_Factory只是单纯的提供ClassA对象,而MainModule_ProvideOkHttpClientFactory中是用于提供OkHttpClient对象的,假设MainModule还有其他Provides,那么会生成对应数量的MainModule_ProvideXXXFactory。MainModule_ProvideOkHttpClientFactory会提供n个get方法(方法的重载),不同的get方法会返回不同的对象,这些方法的生成规则是根据MainModule中那么使用@Provides注解标记的方法,此例只有一个。

public final class MainModule_ProvideOkHttpClientFactory implements Factory<OkHttpClient> {
  private final MainModule module;

  public MainModule_ProvideOkHttpClientFactory(MainModule module) {
    assert module != null;
    this.module = module;
  }

  @Override
  public OkHttpClient get() {
    return Preconditions.checkNotNull(
        module.provideOkHttpClient(), "Cannot return null from a non-@Nullable @Provides method");
  }

  public static Factory<OkHttpClient> create(MainModule module) {
    return new MainModule_ProvideOkHttpClientFactory(module);
  }
}

DaggerMainActivityComponent在创建mainActivityMembersInjector时候增加了一个provideOkHttpClientProvider参数,因为MainActivity_MembersInjector的作用是要完成MainActivity的对象注入,而MainActivity此时需要注入的有两个对象,第一个是ClassA,第二个是OkHttpClient。他们需要分别从ClassA_Factory与MainModule_ProvideOkHttpClientFactory获取

  // DaggerMainActivityComponent#initialize
  private void initialize(final Builder builder) {

    this.provideOkHttpClientProvider =
        MainModule_ProvideOkHttpClientFactory.create(builder.mainModule);

    this.mainActivityMembersInjector =
        MainActivity_MembersInjector.create(ClassA_Factory.create(), provideOkHttpClientProvider);
  }
  // MainActivity_MembersInjector#injectMembers
    @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.classA = classAProvider.get();
    instance.client = clientProvider.get();
  }

如果我们把ClassA的@Inject去掉,在MainModule中添加ClassA对象的提供方法,可以?答案:是的;可以自己试试

Module带参数

通常情况下大多数类的实例化可能都需要传入参数,那么我们来看看带参数情况下应该怎么写

我们将OkHttpClient网络请求的超时时限作为参数传入

@Module
public class MainModule {

    int timeOut;

    public MainModule(int timeOut) {
        this.timeOut = timeOut;
    }

    @Provides
    public OkHttpClient provideOkHttpClient(){
        return new OkHttpClient.Builder().connectTimeout(timeOut, TimeUnit.SECONDS).build();
    }

    @Provides
    public ClassA providesClassA(){
        return new ClassA();
    }
}

然后在MainActivity中我们通过设置MainModule对象完成参数设置

DaggerMainActivityComponent.builder().mainModule(new MainModule(10)).build().inject(this);

这里有个需要注意的地方,之前的例子中都没有参数,那么Component生成的类中会提供create方法直接创建Component

DaggerMainActivityComponent.create().build().inject(this);

而在有参数的情况下,不再提供create方法,而是只有build方法,因为此时需要传入MainModule对象

当然我们更建议以下写法:通过provide方法提供参数,一方面是解耦,一方面是代码可读性
Module中,不能出现参数和返回参数一致的情况,否则会导致死循环

@Module
public class MainModule {

    int timeOut;

    public MainModule(int timeOut) {
        this.timeOut = timeOut;
    }

    @Provides
    public int provideTimeOut(){
        return timeOut;
    }

    @Singleton
    @Provides
    public OkHttpClient provideOkHttpClient(int timeOut){
        return new OkHttpClient.Builder().connectTimeout(timeOut,TimeUnit.SECONDS).build();
    }

    @Provides
    public ClassA providesClassA(){
        System.out.println("MainModule.providesClassA");
        return new ClassA();
    }
}

Scope作用域

通过Singleton注解来看Scope,我们在页面上注入了两个OkHttpClient对象,我们来看看结果

    @Inject
    OkHttpClient client1;
    @Inject
    OkHttpClient client2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerMainActivityComponent.builder().mainModule(new MainModule(10)).build().inject(this);
        System.out.println("MainActivity.onCreate client1 --- " + client1);
        System.out.println("MainActivity.onCreate client2 --- " + client2);
    }

System.out: MainActivity.onCreate client1 — okhttp3.OkHttpClient@d28c04e
System.out: MainActivity.onCreate client2 — okhttp3.OkHttpClient@f45d56f

可以看到两个是不同的对象,那假设我们需要让他保持单例呢?可以使用@Single注解。我们需要关注的是如果我们希望把这个Person变成单例,这个注解需要在哪些地方使用。主要两个地方,如下:

一是:提供Person对象的地方,有可能是类构造器,有可能是Module中某个标记了@Provids注解的方法。但是由于@Inject与@Singleton不能同时使用,因此只能是在Module中某个标记了@Provids注解的方法。

@Module
public class MainModule {

    int timeOut;

    public MainModule(int timeOut) {
        this.timeOut = timeOut;
    }

    @Singleton
    @Provides
    public OkHttpClient provideOkHttpClient(){
        return new OkHttpClient.Builder().connectTimeout(timeOut, TimeUnit.SECONDS).build();
    }

    @Provides
    public ClassA providesClassA(){
        System.out.println("MainModule.providesClassA");
        return new ClassA();
    }
}

二是:作为提供对象与对象调用者之间的桥梁-Component

@Singleton
@Component(modules = MainModule.class)
public interface MainActivityComponent {

    void inject(MainActivity activity);
}

System.out: MainActivity.onCreate client1 — okhttp3.OkHttpClient@2711f49
System.out: MainActivity.onCreate client2 — okhttp3.OkHttpClient@2711f49

我们来看看生成的文件差异,主要是在DaggerMainActivityComponent#initialize方法中

  private void initialize(final Builder builder) {

    this.provideOkHttpClientProvider =
        DoubleCheck.provider(MainModule_ProvideOkHttpClientFactory.create(builder.mainModule));

    this.mainActivityMembersInjector =
        MainActivity_MembersInjector.create(provideOkHttpClientProvider);
  }

在获取provideOkHttpClientProvider对象时,额外增加了DoubleCheck.provider(…)

  /** Returns a {@link Provider} that caches the value from the given delegate provider. */
  public static <T> Provider<T> provider(Provider<T> delegate) {
    checkNotNull(delegate);
    if (delegate instanceof DoubleCheck) {
      /* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped
       * binding, we shouldn't cache the value again. */
      return delegate;
    }
    return new DoubleCheck<T>(delegate);
  }

意思就是说如果咱们使用了scoped类型的注解,我们将返回之前缓存的Provider对象。我们再看看Singleton注解的定义

/**
 * Identifies a type that the injector only instantiates once. Not inherited.
 *
 * @see javax.inject.Scope @Scope
 */
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

非常明显,类注释已经告诉我们这个注解的作用就是用来实现单例的。这边要注意到该注解使用了元注解@Scope,通过深入研究可以发现该Singleton注解的作用也仅仅是个标记作用而已。也就是说我们可以自定义一个注解来代替@Singleton`

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

还有需要注意的一点是Scope的作用周期取决于Component的周期长短,假设我们定义了AppComponent用于全局Application相关成员对象的注入,那么周期就是整个应用程序周期。而我们上面用到的MainActivityComponent对应的周期则是Activity的声明周期。这边有些同学会有疑惑的地方,如果说AppComponent中我们注入了一个单例OkHttpClient,我们后面又在别的地方通过另一个Component注入一个OkHttpClient,那么这两个OkHttpClient对象是否为同一个呢?答案是否定的,因为Scope与Component是对应关系。尽管都设定了Scope,但是由于Component,他们之间是相关独立的。

Qualifier方法区分

当我们的Module中存在两个相同返回值的方法时,我们可以使用@Name进行区分,对应在注入时也需要指明。这里的Qualifier与Scope完全类似,大家可以对比一下。因此,我们也可以自定义注解去代替@Name

// AppModule
@Module
public class AppMudole {

    @Named("two")
    @Provides
    String providesString1(){
        return "test_one";
    }

    @Named("three")
    @Provides
    String providesString2(){
        return "test_two";
    }

    @Provides
    CacheManager providesCacheManager(){
        return new CacheManager();
    }
}

// MainActivity
@Named("two")
@Inject
String test1;
@Named("three")
@Inject
String test2;

自定义注解的话可以这样子


@Qualifier
@Documented
@Retention(RUNTIME)
public @interface QualifierTwo {

}

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface QualifierThree {

}

@Module
public class AppMudole {

    @QualifierTwo
    @Provides
    String providesString1(){
        return "test_one";
    }

    @QualifierThree
    @Provides
    String providesString2(){
        return "test_two";
    }

    @Provides
    CacheManager providesCacheManager(){
        return new CacheManager();
    }
}

@QualifierTwo
@Inject
String test1;
@QualifierThree
@Inject
String test2;

Component间的依赖

很多时候可能某个东西是很多地方都需要使用到的,这个时候我们就可以通过Component之间的依赖实现

方式1:dependence

// 修改ClassA构造
public class ClassA {

    CacheManager cacheManager;

    public ClassA(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }
}

@Module
public class AppMudole {

    Context context;

    public AppMudole(Context context) {
        this.context = context;
    }

    @Provides
    Context providesContext(){
        return context;
    }

    @Provides
    CacheManager providesCacheManager(){
        return new CacheManager();
    }
}

@Component(modules = AppMudole.class)
public interface AppComponent {

    CacheManager providesCacheManager();
}

@Singleton()
@Component(dependencies = AppComponent.class,modules = MainModule.class)
public interface MainActivityComponent {

    void inject(MainActivity activity);
}

定义一个AppModule提供对象,定义AppComponent作为全局基础组件,提供应用中可能常用到的对象。在基础组件中我们一般就不会提供注入方法,因为该Component一般作为其他Component的依赖,而不会具体在某个地方使用。

然后我们在注入时候需要将AppComponent对象传递给MainActivityComponent

        AppComponent appComponent = DaggerAppComponent
                .builder().appMudole(new AppMudole(this)).build();
        DaggerMainActivityComponent.builder().appComponent(appComponent)
                .mainModule(new MainModule(10))
                .build().inject(this);

这个时候咱们来看看apt生成的代码


  // DaggerMainActivityComponent#initialize
  private void initialize(final Builder builder) {

    this.providesCacheManagerProvider =
        new com_exp_dagger2_AppComponent_providesCacheManager(builder.appComponent);

    this.providesClassAProvider =
        MainModule_ProvidesClassAFactory.create(builder.mainModule, providesCacheManagerProvider);

    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(providesClassAProvider);
  }

  // DaggerMainActivityComponent$com_exp_dagger2_AppComponent_providesCacheManager
  private static class com_exp_dagger2_AppComponent_providesCacheManager
      implements Provider<CacheManager> {
    private final AppComponent appComponent;

    com_exp_dagger2_AppComponent_providesCacheManager(AppComponent appComponent) {
      this.appComponent = appComponent;
    }

    @Override
    public CacheManager get() {
      return Preconditions.checkNotNull(
          appComponent.providesCacheManager(),
          "Cannot return null from a non-@Nullable component method");
    }
  }

DaggerMainActivityComponent中对于Component依赖中提供的对象获取仍然是通过Provider对象的形式实现,只不过对于Component依赖,会为其创建一个命名规则为”包名依赖组件名提供对象的方法名”静态内部类。然后通过构造模式的Builder获取到组件对象,作为参数传入com_exp_dagger2_AppComponent_providesCacheManager生成Provider。

假设AppMudole中提供了获取另一个类型对象的方法,这里举例如下

    @Provides
    String providesString(){
        return "test";
    }

这个时候我们尝试在MainActivity中进行String对象的注入

    @Inject
    String test;

编译会发现报错,Dagger2找不到从哪能获取到这个String对象

java.lang.String cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method.

我们在必须在AppComponent中显式提供String对象的方法即可

@Component(modules = AppMudole.class)
public interface AppComponent {

    CacheManager providesCacheManager();
    String providesString();
}

那么这样来看的话就很清晰了,其实AppComponent被其他Component依赖,那么它只是作为AppModule对象的转发者

方式2:SubComponent

AppComponent中的定义不再是提供AppModule中的对象转发,而是变成提供子Component

@Component(modules = AppMudole.class)
public interface AppComponent {

    MainActivityComponent activityComponent();
}

这种情况下,就可以直接使用AppModule提供的对象完成注入(对此上例中的String对象注入)
然后子Comonent的定义很简单,没有变化,只是去除了dependencies换为SubComponent

@Singleton()
@Subcomponent(modules = MainModule.class)
public interface MainActivityComponent {

    void inject(MainActivity activity);
}

需要注意的是这个时候MainModule必须提供无参构造器(这个在后面分析会提到)

注入时通过DaggerAppComponent完成注入,ClassA对象的注入是由SubComponent(即MainActivityComponent)提供的,而String对象的注入是由AppComponent提供的


    @Inject
    ClassA classA;
    @Inject
    String test;

    DaggerAppComponent.builder().build().activityComponent().inject(this);

OK,我们再来看看生成的代码


 // DaggerAppComponent部分代码

  @Override
  public MainActivityComponent activityComponent() {
    return new MainActivityComponentImpl();
  }

  private final class MainActivityComponentImpl implements MainActivityComponent {
    private final MainModule mainModule;

    private Provider<ClassA> providesClassAProvider;

    private MembersInjector<MainActivity> mainActivityMembersInjector;

    private MainActivityComponentImpl() {
      this.mainModule = new MainModule();
      initialize();
    }

    @SuppressWarnings("unchecked")
    private void initialize() {

      this.providesClassAProvider =
          MainModule_ProvidesClassAFactory.create(
              mainModule, DaggerAppComponent.this.providesCacheManagerProvider);

      this.mainActivityMembersInjector =
          MainActivity_MembersInjector.create(
              providesClassAProvider, DaggerAppComponent.this.providesStringProvider);
    }

    @Override
    public void inject(MainActivity activity) {
      mainActivityMembersInjector.injectMembers(activity);
    }
  }

可以看到DaggerAppComponent中为每个子Component生成了一个命名规则为“ComponentName + Impl”的内部类,并提供了获取方法(其实是根据AppComponent接口的定义),然后在initialize方法中我们可以看到mainActivityMembersInjector由两个部分组成:providesClassAProvider(由MainModule而来,SubComponent所用的Module),providesStringProvider(由AppModule而来,AppComponent所用的Module,对象由外部类DaggerAppComponent创建)。注意,这种情况下时候子Component不会生成任何DaggerXXXComponent类。

两种方式比较

两种方式都是用来实现Component之间关联的,dependencies 方法更像Java中的组合形式,而Subcomponent更像是类之间的继承关系。具体情况根据实际需要进行选择,下面是我以自身的理解进行差异分析

两者与Scope需要注意的:两个拥有依赖关系的 Component 是不能有相同 @Scope 注解的,而使用@SubComponent 则可以使用相同的@Scope注解。

dependencies 方法

特点:

  • 可以清晰的对应Component所依赖的其他Component
  • 两个Component都会生成对应的DaggerXXXComponent
  • 被依赖Component为目标Component提供相关对象获取
  • 注入时使用目标Component传入依赖Component完成

注意:

  • 被依赖Component中没有显式提供的依赖无法完成注入

SubComponent 方法

特点:

  • 只有父Component会生成DaggerXXXComponent
  • 父Component可以方便的管理子Component
  • 默认可获取到父子两Component使用到的Module所提供的对象
  • 注入时使用父Component并选择指定的子Component完成

注意:

  • 子Component所用到的Module必须提供无参构造器

懒加载与重新加载

懒加载(Lazy):调用get方法时候才去创建对象,后续获取都为同一个对象
重新加载(Provides):调用get方法时候会强制重新创建对象,对象是否为同一个取决于Module的实现或Scope注解

public class MainActivity extends AppCompatActivity {

    @Inject
    Lazy<ClassA> classALazy;
    @Inject
    Provider<String> provider;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerAppComponent.builder().build().activityComponent().inject(this);
        ClassA classA = this.classALazy.get();
        String string = this.provider.get();

    }
}

老样子,看看生成的代码

  // MainActivity_MembersInjector#injectMembers
  @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.classALazy = DoubleCheck.lazy(classALazyProvider);
    instance.provider = providerProvider;
  }

咱们主要就看MainActivity_MembersInjector类中的injectMembers方法,通常情况下injectMembers方法中都是直接通过相关的Provider的get方法提取对象。在使用了Lazy则通过DoubleCheck.check()处理后得到Lazy对象,而Provider则直接赋值


  // DoubleCheck 部分代码

  /** Returns a {@link Lazy} that caches the value from the given provider. */
  public static <T> Lazy<T> lazy(Provider<T> provider) {
    if (provider instanceof Lazy) {
      @SuppressWarnings("unchecked")
      final Lazy<T> lazy = (Lazy<T>) provider;
      // Avoids memoizing a value that is already memoized.
      // NOTE: There is a pathological case where Provider<P> may implement Lazy<L>, but P and L
      // are different types using covariant return on get(). Right now this is used with
      // DoubleCheck<T> exclusively, which is implemented such that P and L are always
      // the same, so it will be fine for that case.
      return lazy;
    }
    return new DoubleCheck<T>(checkNotNull(provider));
  }

  @SuppressWarnings("unchecked") // cast only happens when result comes from the provider
  @Override
  public T get() {
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          result = provider.get();
          /* Get the current instance and test to see if the call to provider.get() has resulted
           * in a recursive call.  If it returns the same instance, we'll allow it, but if the
           * instances differ, throw. */
          Object currentInstance = instance;
          if (currentInstance != UNINITIALIZED && currentInstance != result) {
            throw new IllegalStateException("Scoped provider was invoked recursively returning "
                + "different results: " + currentInstance + " & " + result + ". This is likely "
                + "due to a circular dependency.");
          }
          instance = result;
          /* Null out the reference to the provider. We are never going to need it again, so we
           * can make it eligible for GC. */
          provider = null;
        }
      }
    }
    return (T) result;
  }

由上面代码可以看出,Lazy其实是一个DoubleCheck对象,通过封装Provider使用双重检验机制完成单例实现。

总结

Component:必不可少的东西,本身不提供对象,主要用于链接对象提供者与注入目标,完成对象注入
Inject:可用于标记构造器,表明可通过该构造器获取对象;可用于标记需要注入的目标成员变量;
Module:提供各类对象,方法返回值与参数不可一致,可用@Name或者自定义注解区分相关返回值的方法
Scope:用于作用域标记,可用@Single或者自定义注解实现单例或者对象作用域范围控制
Component关联:dependencies 方式与 SubComponent 方式,类似组合与集成,区分使用
懒加载(Lazy)与重新加载(Provider):Lazy是Provider封装而成的DoubleCheck对象,单例
在相关的方法定义中方法名是没有严格要求的,只不过是为了规范统一,核心在返回值类型

Dagger2实现原理分析 Dagger是一个依赖注入框架,这个框架往往给人一种“高端,但是却很难用“的感觉。我们在使用或者阅读别人的项目时,往往会陷入dagger搭建的”迷宫里”绕不出来。导致Dagger被认为是android史上”最受冷落的优质库”。本文先通过理清依赖注入的概念,然后详细介绍Dagger一些常见的使用场景和注意事项,最后介绍一下Dagger主要功能实现的原理。帮助大家进行理解。 阅读详情

相关推荐

架构进阶Dagger2的原理及使用详解

目录 一:Dagger2是什么? 二:为什么要有Dagger2 三:Dagger2如何使用 基本的概念 如何使用Dagger2 高级用法 (1)构造方法需要其他参数时候 (2) 模块之间的依赖关系 (3) @Named注解使用 (4) @Singleton注解 (5)自定义Scoped (6)Subcomponent (7)lazy 和 Provider 四: MVP + Dagger2...

weixin_44339238的博客 423

Android开源框架--Dagger2详解

功名只向马上取,真是英雄一丈夫

袁震的博客 4691

Android神匕首—Dagger2依赖注入框架详解

简介 Dagger-匕首,鼎鼎大名的Square公司旗下又一把利刃(没错!还有一把黄油刀,唤作ButterKnife)  Dagger2 是一个Android依赖注入框架,由谷歌开发,最早的版本Dagger1 由Square公司开发。依赖注入框架主要用于模块间解耦,提高代码的健壮性和可维护性。Dagger 这个库的取名不仅仅来自它的本意“匕首”,同时也暗示了它的原理。  Android开发

程序病毒小队的博客 985

Dagger2 的使用与基本原理

使用 Dagger2 的最佳做法:如果有可能,通过@Inject进行构造函数注入,以向 Dagger 图中添加类型。使用@Binds告知 Dagger 接口应采用哪种实现使用@Provides告知 Dagger 如何提供你的项目所不具备的类只能在组件中声明一次模块根据注释的使用生命周期,为作用域注释命名,例如和依赖注入

tmacfrank 2004

Dagger2的简单使用及基本原理

前言    java注解除了解耦个人认为还有使代码简洁、逻辑更加突出业务逻辑代码、由于创建对象的集中,更加方便对象的维护与测试、对象的创建和生命周期的管控都交由框架等优点。 Dagger2的历史:   Dagger1是Square公司开发,Dagger2则由谷歌接收在dagger1的分支上开发而来,Dagger1用了反射,消耗了一些性能,Dagger2则被谷歌声称性能提高了13%,但是其灵活性减少 Dagger2的使用 添加依赖: implementation 'com.google.dagger:dagg

wangsheng5454的博客 4701

依赖注入框架Dagger2—1.入门

1.依赖注入 1.1.什么是依赖? 如果在 Class A 中,有 Class B 的实例,则称 Class A 对 Class B 有一个依赖。 例如下面类 A 中用到一个 B 对象,我们就说类 A 对类 B 有一个依赖。 同时,这也是一个典型的&amp;amp;amp;amp;amp;amp;amp;quot;依赖非注入&amp;amp;amp;amp;amp;amp;amp;quot;案例。 public class A { ... B b; .

点滴积累 544

依赖注入框架Dagger2详解(四),初级篇

什么是Dagger2 Dagger2是一种依赖注入框架,能够在编译时自动生成出一些代码,这些代码可以帮助对应的实例初始化。 ,它是鼎鼎大名的Square公司旗下又一把利刃,还有一把黄油刀,叫做ButterKnife。 Dagger2起源于Dagger,是一款基于Java注解来实现的完全在编译阶段完成依赖注入的开源库,主要用于模块间解耦、提高代码的健壮性和可维护性。Dagger2在编译阶段通过ap...

门心叼龙的专栏 8352

Android中的依赖注入框架的作用(如Dagger2

Dagger2是Google维护的一个依赖注入框架,它基于编译时注入,意味着依赖项在编译时就被确定,而不是在运行时。这种方式可以减少运行时的性能开销,并提高应用的性能。Dagger2使用注解来配置依赖注入,并通过代码生成在编译时创建必要的依赖注入代码。Dagger2的前身是Square公司开发的Dagger1。与Dagger1相比,Dagger2在多个方面进行了改进和优化,包括不再使用反射、提高了调试的易用性和性能等。

Dingdangr的博客 1531

Dagger2依赖注入框架

Dagger2简介: Dagger: “A fast dependency injector for Android and Java“,其最大的好处就是莫跨界见解耦,这个耦合是由类之间的以来引起的,依赖注入的配置独立于初始化出,配置更改方便,可提高代码的健壮性和可维护性 Dagger2相较于Dagger1的优势: (1)更好的性能:相较于Dagger1,Dagger2使用的预编译期间生成代码...

DLUT_WJX的博客 261

依赖注入框架 Dagger2 的全面讲解

写在前面,我不敢保证这篇 Chat 一定比别的文章好,但是我敢保证,关于 Dagger2,你有这篇 Chat 一定够了。这篇 Chat 主要讲 Dagger2 的全面使用。 文章适合人群:对框架有兴趣的开发者,有意向提高开发速度的同学。 Chat 主要内容有: 什么是依赖注入 Dagger2 的前世今生 为什么要学 Dagger2 Dagger2 的简单原理讲解 使用 Dagger2 之前做的准...

GitChat 437

Dagger2 依赖注入框架介绍

Dagger2 依赖注入框架介绍 Dagger2 是一个标准的依赖注入框架Dagger2Dagger1(Square 公司开发)的分支,由谷歌接手开发。 添加依赖 这里以 dagger 2.24 版本为例,在 app 的 build.gradle 添加 dagger 依赖如下: // Add Dagger dependencies dependencies { api 'com.goo...

okclouderx的博客 590

依赖注入框架-Dagger2 精炼详解

一、前期基础知识储备 1)依赖注入是什么? 依赖注入是面向对象编程的一种设计模式,其目的是为了降低程序耦合,这个耦合就是类之间的依赖引起的。 我们使用各种依赖注入框架比如View注入框架Butternife,Koltin注入框架kodein,更加简易的RoboGuice注入框架等等,都是为了降低类与类之间的耦合。实现依赖注入的方法通常有三种:①构造方法注入;②定义set方法注入;③注解注入。...

Chin_Style的博客 1072

依赖注入框架Dagger2详解(一),依赖注入和控制反转的深入理解

Java 依赖注入标准(JSR-330,Dependency Injection for Java)1.0 规范已于2009年10 月份发布。该规范主要是面向依赖注入使用者,而对注入器实现、配置并未作详细要求。Spring、Guice 已经开始兼容该规范,JSR-299(Contexts and Dependency Injection for Java EE platform,参考实现 Weld...

门心叼龙的专栏 8588

Dagger2依赖注入框架的使用简介

如何引入Dagger2 配置apt插件(在build.gradle(Project:xxx)中添加如下代码) dependencies { classpath 'com.android.tools.build:gradle:2.1.0' //添加apt插件 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } 添加依赖(在build.gradle(Module:app)中添加如下代码) apply pl

weixin_42324979的博客 199

依赖注入框架Dagger22.各注解用法

[@TOC] 0.前言 接上一篇入门文章,这篇主要是各属性实战。 1.Dagger2各属性了解 必要属性 @inject//注入,@Component,@Moudle,@Provider 为什么说这个几个是必要属性,因为只要想用dagger2这几个属性是绕不开的。 高级属性 @Named @Qualifier @Scope @Singleton; 这四个属性实际上可以分文俩组,@Named底层实...

点滴积累 980

Hilt vs Dagger2Android 依赖注入框架对比

Hilt是Dagger2Android优化版本,两者核心差异在于易用性。Hilt简化了依赖注入配置,通过注解自动处理Android组件(如@AndroidEntryPoint),大幅减少样板代码;而Dagger2需要手动管理Component和生命周期。Hilt深度集成Jetpack,提供标准化组件,适合新项目;Dagger2则更适用于需要精细控制或非Android项目。性能相当,但Hilt开发效率更高,推荐新项目优先采用,现有Dagger2项目可逐步迁移。

Mark Wu 的博客 821

Android 依赖注入框架(ButterKnife和Dagger2)

Android开发中,依赖注入是一种重要的设计模式,它有助于更好地管理和组织应用程序的组件,并提高了代码的可测试性和可维护性。两个常见的Android依赖注入框架是ButterKnife和Dagger 2

weixin_45568421的博客 540

依赖注入框架Dagger2详解(六),高级

这篇主要介绍Dagger单例,延迟加载,强制加载,以及使用Subcomponent等一些高级用法 实现单例 创建某些对象有时候是耗时浪费资源或者没有完全必要的,这时候Component没有必要重复地使用Module来创建这些对象。举个例子,当我们需要榨果汁时,我们榨苹果汁与榨香蕉汁可以使用相同一台榨果汁机器,我们只需要创建出一台榨果汁机器。我们可以使用@Singleton来缓存“榨果汁机器”,这样...

门心叼龙的专栏 8571
上一篇: Handler机制浅析
li锦锋
博客等级 码龄10年 2粉丝 12原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值