Daggger2 概念解读、使用姿势及源码分析(2)

上一篇文章Daggger2 使用姿势及源码分析(1)
讲述了Dagger2的使用姿势,以及连接器component、提供者 Provider、工厂生产者Factory、成员注入器 MembersInjector,这些组件的相互作用揭示了Dagger2背后工作的原理。这篇文章我将继续解读Dagger2,为大家展示以下几个关键词背后的机制:
Inject, Singleton, Scope, Qualifier

[TOC]

Inject

Inject,即注入,该注解标示地方表示需要通过DI框架来注入实例。Inject有三种方式,分别是Constructor injection、Fields injection、Methods injection。

Constructor injection
如果在构造器中声明了@Inject,Dagger2会尝试在创建实例时注入构造所需的参数,这就必须为这些参数提供一种途径使得dagger可以创建这些实例。这就需要再Module中显式的@Provides参数。关系简单图示如下:
Constructor Inject --> Create parameters --> Component --> Module

以TasksRepository为例:

@Inject
TasksRepository(@Remote TasksDataSource tasksRemoteDataSource,
            @Local TasksDataSource tasksLocalDataSource) {
    mTasksRemoteDataSource = tasksRemoteDataSource;
    mTasksLocalDataSource = tasksLocalDataSource;
}

在TasksRepositoryModule提供两个实例的创建方式

@Singleton
@Provides
@Local
TasksDataSource provideTasksLocalDataSource(Context context) {
    return new TasksLocalDataSource(context);
}

@Singleton
@Provides
@Remote
TasksDataSource provideTasksRemoteDataSource() {
    return new FakeTasksRemoteDataSource();
}

Dagger会为TasksRepository创建一个工厂类:TasksRepository_Factory

public static Factory<TasksRepository> create(
    Provider<TasksDataSource> tasksRemoteDataSourceProvider,
    Provider<TasksDataSource> tasksLocalDataSourceProvider) {
    return new TasksRepository_Factory(tasksRemoteDataSourceProvider, tasksLocalDataSourceProvider);
}

以上,可以看出,Constructor injection影响的是类实例的创建工厂。

这里涉及到两个相同的实例TasksDataSource,一个是remote,一个是local,它们通过scope来区分,后面会讲述scope的原理。

Fields injection
这是最常用的注入方式,这种注入方式要了解的是Dagger是怎样注入的,是什么时候注入的。

第一个问题,如何注入?

1.拥有Fields injection行为的类A,Dagger会为它生成一个注入器A_MembersInjector;
注入器的结构如下:

public interface MembersInjector<T> {
  void injectMembers(T instance);}

2.注入器A_MembersInjector需要实现方法:injectMembers(T instance),该方法通过Provider注入具体的实例

TaskDetailActivity通过Fields injection注入TaskDetailPresenter

public class TaskDetailActivity extends AppCompatActivity {

    @Inject TaskDetailPresenter mTaskDetailPresenter;

}

注入器TaskDetailActivity_MembersInjector的定义如下:

public final class TaskDetailActivity_MembersInjector
    implements MembersInjector<TaskDetailActivity> {

      public static MembersInjector<TaskDetailActivity> create(
      Provider<TaskDetailPresenter> mTaskDetailPresenterProvider) {
    return new TaskDetailActivity_MembersInjector(mTaskDetailPresenterProvider);
  }

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

第二个问题:什么时候注入?

注入时机有两种:

  1. 当对应的A_Factory中的get() 方法被调用的时候;
  2. 主动调用Component中定义的inject()方法时

第一种方式,查看TaskDetailPresenter_Factory可知,简单的理解就是,当Dagger主动通过工厂类创建实例的时候,需要调用注入器注入该类依赖的属性和方法。源码如下:

public final class TaskDetailPresenter_Factory implements Factory<TaskDetailPresenter> {
  @Override
  public TaskDetailPresenter get() {
    return MembersInjectors.injectMembers(
        taskDetailPresenterMembersInjector,
        new TaskDetailPresenter(
            taskIdProvider.get(), tasksRepositoryProvider.get(), taskDetailViewProvider.get()));
  }
}

第二种方式,这种方式的典型应用是,在Activity中创建Component实例,然后通过调用Component中inject()方式注入属性和方法。源码如下:

调用方式:
DaggerTaskDetailComponent.builder()
                .taskDetailPresenterModule(new TaskDetailPresenterModule(taskDetailFragment, taskId))
                .tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent()).build()
                .inject(this);


DaggerTaskDetailComponent中的源码:
@Override
public void inject(TaskDetailActivity taskDetailActivity) {
    taskDetailActivityMembersInjector.injectMembers(taskDetailActivity);
}

Methods injection

同Fields injection类似,Methods injection也是通过注入器在实例创建好的时候调用injectMembers() 方法注入的,需要注意的是,方法注入可能涉及到参数的注入,这些参数要求可以在Dagger的Provider中查找到。用Dagger2的原文文档就是 “All method parameters are provided from dependencies graph.”

Singleton

单例的实现依赖于特殊的Provider机制,在Module中以@Provides注解声明的方法都会创建一个工厂类以提供实例,比如在TasksRepositoryModule
中的TasksDataSource provideTasksLocalDataSource(Context context),生成了TasksRepositoryModule_ProvideTasksLocalDataSourceFactory;在TaskDetailPresenterModule
中TaskDetailContract.View provideTaskDetailContractView()
生成了TaskDetailPresenterModule_ProvideTaskDetailContractViewFactory;
它们的本质是一样的,都是一样的工厂类提供者。

不同的地方在于,在Module中以@Singleton声明的方法,在提供实例的时候,并不是直接调用工厂类的get() 方法,而是通过ScopedProvider的get()方法类创建实例,而这个ScopedProvider就保证了实例的单例:

ScopedProvider源码如下, 其get方法就是一个典型的单例模式实现:

public final class ScopedProvider<T> implements Provider<T>, Lazy<T> {
 @SuppressWarnings("unchecked") // cast only happens when result comes from the factory
  @Override
  public T get() {
    // double-check idiom from EJ2: Item 71
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          instance = result = factory.get();
        }
      }
    }
    return (T) result;
  }

  /** Returns a new scoped provider for the given factory. */
  public static <T> Provider<T> create(Factory<T> factory) {
    if (factory == null) {
      throw new NullPointerException();
    }
    return new ScopedProvider<T>(factory);
  }
}

Scope

Scope是一种作用域的描述。

@Singleton就是一种Scope,并且是最长的scope。

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

@Singleton能起到作用得益于Dagger2底层的实现,也就是上文中提到的ScopedProvider的应用。

这里有一个误区,很多文章喜欢用Scope来控制对象的作用域,其实是有问题的。拿@Singleton来讲,并不是用@Singleton标注一个类之后,你在任何地方注入该类的时候都是单例的。Scope真正要表达的是类、Component、Module一体的关系。

这里,我简单做了一个实验,代码如下:

@Singleton
public class User {
}

public class Person {
}


@Module
public class UserModule {

    @Provides
    @Singleton
    User provideUser() {
        return new User();
    }

    @Provides
    Person providePerson() {
        return new Person();
    }
}

@Singleton
@Component(modules = UserModule.class)
public interface UserComponent {
    User getUser();
    Person getPerson();

    void inject(MainActivity activity);
}

@Singleton
@Component(modules = UserModule.class)
public interface PresenterComponent {
    User getUser();
}

public class MainActivity extends AppCompatActivity {

    @Inject
    User user1;
    @Inject
    Person person1;

    @Inject
    Presenter presenter1;

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

        UserComponent component = DaggerUserComponent.create();
        User user2 = component.getUser();
        Person person2 = component.getPerson();
        component.inject(this);

        PresenterComponent presenterComponent = DaggerPresenterComponent.create();
        User user3 = presenterComponent.getUser();
    }

}

实验的表现:
user1 和 user2 是同一个实例,user3 不同于user1是另外一个实例;
person1 和person2 不是同一个实例。

现象解释:
User声明为@Singleton, user1和user2来源于同一个component(UserComponent),所以是同一个实例;
user3来源于PresenterComponent,尽管User被声明为@Singleton,但它却是一个新的实例;
Person是个普通的实例,没有声明为@Singleton,尽管person1和person2 来源于同一个component,但是却创建了两次,所以不是同一个实例。

所以结论就是scope没有想象中的那么牛逼,它并不能控制实例的生命周期,并不是@Singleton就是全局单例,@FragmentScope @ActivityScope @ApplicationScope就是其字面表达的意思。它的本质都是依赖于component的生命周期的。

Qualifier

@Qualifier annotation helps us to create “tags” for dependencies which have the same interface。这句话说的很形象,@Qualifier就是一个tag。想象一下,如果在Module中你需要provide两个TasksDataSource,你就需要通过@Qualifier来区分了。示例如下:

定义Qualifier:

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Remote {

}

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Local {

}

在Module中标识:

@Module
public class TasksRepositoryModule {

    @Singleton
    @Provides
    @Local
    TasksDataSource provideTasksLocalDataSource(Context context) {
        return new TasksLocalDataSource(context);
    }

    @Singleton
    @Provides
    @Remote
    TasksDataSource provideTasksRemoteDataSource() {
        return new FakeTasksRemoteDataSource();
    }

}

使用的时候通过Qualifier来区分:

    @Inject
    TasksRepository(@Remote TasksDataSource tasksRemoteDataSource,
            @Local TasksDataSource tasksLocalDataSource) {
        mTasksRemoteDataSource = tasksRemoteDataSource;
        mTasksLocalDataSource = tasksLocalDataSource;
    }

实际上,在Dagger2自动生成的代码中,它会对Qualifier标识的方法生成不同的工厂类,如上就分别对应TasksRepositoryModule_ProvideTasksRemoteDataSourceFactory

TasksRepositoryModule_ProvideTasksLocalDataSourceFactory,最终在引用的时候,就分别通过这两个工厂类提供实例。

Subcomponent

如果一个Component的功能不能满足你的需求,你需要对它进行拓展,一种办法是使用Component(dependencies=××.classs)。另外一种是使用@Subcomponent,Subcomponent用于拓展原有component。同时,这将产生一种副作用——子component同时具备两种不同生命周期的scope。子Component具备了父Component拥有的Scope,也具备了自己的Scope。

Subcomponent其功能效果优点类似component的dependencies。但是使用@Subcomponent不需要在父component中显式添加子component需要用到的对象,只需要添加返回子Component的方法即可,子Component能自动在父Component中查找缺失的依赖。

下面用实际例子来理解:

方式1: Component(dependencies=××.classs)

@Module
public class BBaseModule {

    @Provides
    Person providePerson() {
        return new Person();
    }

    @Provides
    User provideUser() {
        return new User();
    }
}

@Component(modules = BBaseModule.class)
public interface BBaseComponent {

    Person getPerson();
}

@Component(dependencies = BBaseComponent.class, modules = BModule.class)
public interface BComponent {

    void inject(BActivity activity);
}

在BActivity中你会发现只能@Inject Person person. 你不能User,因为BBaseComponent并没有显式的提供User对象。

方式2:采用Subcomponent

@Component(modules = BBaseModule.class)
public interface BBaseComponent {

    Person getPerson();

    BComponent bcomponent(BModule bModule);
}

@Subcomponent(modules = BModule.class)
public interface BComponent {

    void inject(BActivity activity);
}

使用Subcomponent,你需要将子Component用@Subcomponent标注,并在父Component中显式的提供子Component的创建方式。做完这两个修改之后,就可以在BActivity中注入Person和User了。
那么,采用Subcomponent之后,@Subcomponent标注之后的Component不再生成DaggerXXXComponent的实现类,要获取BComponent实例,你需要通过DaggerBBaseComponent.builder().build().bcomponent(new BModule()); 即需要通过父Component实例来获取。究竟发生什么了呢?观察源码可知:

public final class DaggerBBaseComponent implements BBaseComponent {
  private Provider<Person> providePersonProvider;

  private Provider<User> provideUserProvider;

  @Override
  public BComponent bcomponent(BModule bModule) {
    return new BComponentImpl(bModule);
  }

  private final class BComponentImpl implements BComponent {
    private final BModule bModule;

    private MembersInjector<BActivity> bActivityMembersInjector;

    private BComponentImpl(BModule bModule) {
      this.bModule = Preconditions.checkNotNull(bModule);
      initialize();
    }

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

      this.bActivityMembersInjector =
          BActivity_MembersInjector.create(
              DaggerBBaseComponent.this.providePersonProvider,
              DaggerBBaseComponent.this.provideUserProvider);
    }

    @Override
    public void inject(BActivity activity) {
      bActivityMembersInjector.injectMembers(activity);
    }
  }
}

BComponent的实现类在BBaseComponent中,并且BComponent用到的两个实例Person和User,是通过BBaseComponent中的providePersonProvider 和 provideUserProvider获取的。这就解释了上文中提到的“子Component具备了父Component拥有的Scope,也具备了自己的Scope”。

结论

知其然更要知其所以然,了解了背后的机制,你会发现dagger2也没那么复杂和神秘。

上一篇:Daggger2 概念解读、使用姿势及源码分析(1)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容