前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >带你解析Dagger2

带你解析Dagger2

作者头像
老马的编程之旅
发布2022-06-22 10:01:05
7380
发布2022-06-22 10:01:05
举报
文章被收录于专栏:深入理解Android

1. Dagger2简介:

Dagger2是Dagger1的分支,由谷歌公司接手开发,目前的版本是2.0。Dagger2是受到AutoValue项目的启发。 刚开始,Dagger2解决问题的基本思想是:利用生成和写的代码混合达到看似所有的产生和提供依赖的代码都是手写的样子。

Dagger2具有以下好处: 1) 依赖的注入和配置独立于组件之外,注入的对象在一个独立、不耦合的地方初始化,这样在改变注入对象时,我们只需要修改对象的实现方法,而不用大改代码库。 2) 依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。 3) app中的组件不需要知道有关实例创建和生命周期的任何事情,这些由我们的依赖注入框架管理的。 dagger2这样的依赖注入框架对MVP架构来说,是最好的解耦工具,可以进一步降低modle-view-presenter之间的耦合度。

2. 什么是依赖注入

如果在 Class A 中,有 Class B 的实例,则称 Class A 对 Class B 有一个依赖。例如下面类 Human 中用到一个 Father 对象,我们就说类 Human 对类 Father 有一个依赖。

代码语言:javascript
复制
public class Human {
    ...
    Father father;
    ...
    public Human() {
        father = new Father();
    }
}

那什么又是依赖注入呢,依赖注入就是非自己主动初始化依赖,而通过外部来传入依赖的方式,简单来说就是不使用 new 来创建依赖对象。使用 Dagger2 创建依赖对象,我们就不用手动初始化了。个人认为 Dagger2 和 MVP 架构是比较不错的搭配,Activity 依赖的 Presenter 可以使用该DI框架直接生成,实现解耦,简单的使用方式如下:

代码语言:javascript
复制
public class MainActivity extends BaseActivity {
      @Inject
       MainActivityPresenter presenter;

     ...  

}

上面这些主要是对DI框架有一个初步全局的了解,下面来看看Dagger2的基本内容。Dagger2 通过注解来生成代码,定义不同的角色,主要的注解有:@Inject、@Module 、@Component 、@Provides 、@Scope 、@SubComponent 等。

3. Dagger2注解解释: 接下来介绍Dagger2中的每一个概念:

@Inject: 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。 @Module: Modules类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的 依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的app中可以有多个组成在一起的modules)。

@Provide: 在modules中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。

@Component: Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。 Components可以提供所有定义了的类型的实例,比如:我们必须用@Component注解一个接口然后列出所有的@Modules组成该组件,如 果缺失了任何一块都会在编译的时候报错。所有的组件都可以通过它的modules知道依赖的范围。

@Scope: Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。后面会演示一个例子,这是一个非常强大的特点,因为就如前面说的一样,没 必要让每个对象都去了解如何管理他们的实例。在scope的例子中,我们用自定义的@PerActivity注解一个类,所以这个对象存活时间就和 activity的一样。简单来说就是我们可以定义所有范围的粒度(@PerFragment, @PerUser, 等等)。

Qualifier: 当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示。例如:在Android中,我们会需要不同类型的context,所以我们就可以定义 qualifier注解“@ForApplication”和“@ForActivity”,这样当注入一个context的时候,我们就可以告诉 Dagger我们想要哪种类型的context。

4. 如何使用Dagger2

首先还是要在我们的build.gradle文件中如下配置:

代码语言:javascript
复制
apply plugin: 'com.neenbedankt.android-apt'

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
  }
}

android {
  ...
}

dependencies {
  apt 'com.google.dagger:dagger-compiler:2.0'
  compile 'com.google.dagger:dagger:2.0'

  ...
}

我们添加了编译和运行库,还有必不可少的apt插件,没有这插件,dagger可能不会正常工作,特别是在Android studio中。

Application Component: 生命周期跟Application一样的组件。可注入到AndroidApplication和BaseActivity中类中。

代码语言:javascript
复制
@Singleton // Constraints this component to one-per-application or unscoped bindings.
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
  void inject(BaseActivity baseActivity);

  //Exposed to sub-graphs.
  Context context();
  ThreadExecutor threadExecutor();
  PostExecutionThread postExecutionThread();
  UserRepository userRepository();
}

使用了@Singleton注解,使其保证唯一性。也许你会问为什么我要将context和其他成员暴露出去。这正是Dagger中 components工作的重要性质:如果你不想把modules的类型暴露出来,那么你就只能显示地使用它们。在这个例子中,我把这些元素暴露给子图, 如果你把他们删掉,编译的时候就会报错。

Application Module: 这里提供了Application Component里的需要注入的对象。这也是为什么@Provide注解的方法要用@Singleton限定。

代码语言:javascript
复制
@Module
public class ApplicationModule {
  private final AndroidApplication application;

  public ApplicationModule(AndroidApplication application) {
    this.application = application;
  }

  @Provides @Singleton Context provideApplicationContext() {
    return this.application;
  }

  @Provides @Singleton Navigator provideNavigator() {
    return new Navigator();
  }

  @Provides @Singleton ThreadExecutor provideThreadExecutor(JobExecutor jobExecutor) {
    return jobExecutor;
  }

  @Provides @Singleton PostExecutionThread providePostExecutionThread(UIThread uiThread) {
    return uiThread;
  }

  @Provides @Singleton UserCache provideUserCache(UserCacheImpl userCache) {
    return userCache;
  }

  @Provides @Singleton UserRepository provideUserRepository(UserDataRepository userDataRepository) {
    return userDataRepository;
  }
}

Activity Component: 生命周期跟Activity一样的组件。

代码语言:javascript
复制
@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
  //Exposed to sub-graphs.
  Activity activity();
}

@PerActivity是一个自定义的范围注解,作用是允许对象被记录在正确的组件中,当然这些对象的生命周期应该遵循activity的生命周期。

Activity Module: 在对象图中,这个module把activity暴露给相关联的类。比如在fragment中使用activity的context。

代码语言:javascript
复制
@Module
public class ActivityModule {
  private final Activity activity;

  public ActivityModule(Activity activity) {
    this.activity = activity;
  }

  @Provides @PerActivity Activity activity() {
    return this.activity;
  }
}

User Component: 继承于ActivityComponent的组件,并用@PerActivity注解。我通常会在注入用户相关的fragment中使用。因为 ActivityModule把activity暴露给图了,所以在任何需要一个activity的context的时候,Dagger都可以提供注入, 没必要再在子modules中定义了。

代码语言:javascript
复制
@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = {ActivityModule.class, UserModule.class})
public interface UserComponent extends ActivityComponent {
  void inject(UserListFragment userListFragment);
  void inject(UserDetailsFragment userDetailsFragment);
}

User Module: 提供跟用户相关的实例。基于我们的例子,它可以提供用户用例。

代码语言:javascript
复制
@Module
public class UserModule {
  @Provides @PerActivity GetUserListUseCase provideGetUserListUseCase(GetUserListUseCaseImpl getUserListUseCase) {
    return getUserListUseCase;
  }

  @Provides @PerActivity GetUserDetailsUseCase provideGetUserDetailsUseCase(GetUserDetailsUseCaseImpl getUserDetailsUseCase) {
    return getUserDetailsUseCase;
  }
}

整合: Dagger给了我们一堆选择用来注入依赖:

构造方法注入:在类的构造方法前面注释@Inject 成员变量注入:在类的成员变量(非私有)前面注释@Inject 函数方法注入:在函数前面注释@Inject

这个顺序是Dagger建议使用的,因为在运行的过程中,总会有一些奇怪的问题甚至是空指针,这也意味着你的依赖在对象创建的时候可能还没有初始化 完成。这在Android的activity或者fragment中使用成员变量注入会经常遇到,因为我们没有在它们的构造方法中使用。

看一下我们是如何在BaseActivity中注入一个成员变量。在这个例子中,我们注入了一个叫Navigator的类,它是我们应用中负责管理导航的类。

代码语言:javascript
复制
public abstract class BaseActivity extends Activity {

  @Inject Navigator navigator;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.getApplicationComponent().inject(this);
  }

  protected ApplicationComponent getApplicationComponent() {
    return ((AndroidApplication)getApplication()).getApplicationComponent();
  }

  protected ActivityModule getActivityModule() {
    return new ActivityModule(this);
  }
}

Navigator类是成员变量注入的,由ApplicationModule里面@Provide注解显示提供的。最终我们初始化 component然后调用inject()方法注入成员变量。我们通过在Activity的onCreate()方法中调用 getApplicationComponent(),完成这些操作。getApplicationComponent()方法放在这儿是为了复用性,它 的主要作用是为了获取实例化的ApplicationComponent对象。

Fragment的presenter中我们也做了同样的事情,这儿的获取方法有一点不一样,因为问我们使用的是per-activity范围限 定的component。所以我们注入到UserDetailsFragment中的UserComponent其实是驻留在 UserDetailsActivity中的。

代码语言:javascript
复制
private UserComponent userComponent;

我们必须在activity的onCreate()方法中用下面的方式初始化。

代码语言:javascript
复制
private void initializeInjector() {
  this.userComponent = DaggerUserComponent.builder()
      .applicationComponent(getApplicationComponent())
      .activityModule(getActivityModule())
      .build();
}

Dagger会处理我们的注解,为components生成实现并重命名加上“Dagger”前缀。因为这个是一个组合的component,所以在构建 的时候,我们必须把所有的依赖的传进去(components和modules)。现在我们的component已经准备好了,接着为了可以满足 fragment的依赖需求,我们写一个获取方法:

代码语言:javascript
复制
@Override public UserComponent getComponent() {
  return userComponent;
}

我们现在可以利用get方法获取创建的component,然后调用inject()方法将Fragment作为参数传进去,这样就完成了绑定UserDetailsFragment依赖。

代码语言:javascript
复制
@Override public void onActivityCreated(Bundle savedInstanceState) {
  super.onActivityCreated(savedInstanceState);
  this.getComponent.inject(this);
}
代码语言:javascript
复制
public interface HasComponent<C> {
  C getComponent();
}

因此,客户端(例如fragment)可以获取并且使用component(来自activity):

代码语言:javascript
复制
@SuppressWarnings("unchecked")
protected <C> C getComponent(Class<C> componentType) {
  return componentType.cast(((HasComponent<C>)getActivity()).getComponent());
}

这儿使用了强制转换,不论这个客户端不能获取到能用的component,但是至少很快就会失败。

代码语言:javascript
复制
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerApplicationComponent implements ApplicationComponent {
  private Provider<Navigator> provideNavigatorProvider;
  private MembersInjector<BaseActivity> baseActivityMembersInjector;

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

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

  private void initialize(final Builder builder) {  
    this.provideNavigatorProvider = ScopedProvider.create(ApplicationModule_ProvideNavigatorFactory.create(builder.applicationModule));
    this.baseActivityMembersInjector = BaseActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideNavigatorProvider);
  }

  @Override
  public void inject(BaseActivity baseActivity) {  
    baseActivityMembersInjector.injectMembers(baseActivity);
  }

  public static final class Builder {
    private ApplicationModule applicationModule;

    private Builder() {  
    }

    public ApplicationComponent build() {  
      if (applicationModule == null) {
        throw new IllegalStateException("applicationModule must be set");
      }
      return new DaggerApplicationComponent(this);
    }

    public Builder applicationModule(ApplicationModule applicationModule) {  
      if (applicationModule == null) {
        throw new NullPointerException("applicationModule");
      }
      this.applicationModule = applicationModule;
      return this;
    }
  }
}

有两个重点需要注意。第一个:由于我们要将依赖注入到activity中,所以会得到一个注入这个比成员的注入器(由Dagger生成的BaseActivity_MembersInjector):

代码语言:javascript
复制
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class BaseActivity_MembersInjector implements MembersInjector<BaseActivity> {
  private final MembersInjector<Activity> supertypeInjector;
  private final Provider<Navigator> navigatorProvider;

  public BaseActivity_MembersInjector(MembersInjector<Activity> supertypeInjector, Provider<Navigator> navigatorProvider) {  
    assert supertypeInjector != null;
    this.supertypeInjector = supertypeInjector;
    assert navigatorProvider != null;
    this.navigatorProvider = navigatorProvider;
  }

  @Override
  public void injectMembers(BaseActivity instance) {  
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    supertypeInjector.injectMembers(instance);
    instance.navigator = navigatorProvider.get();
  }

  public static MembersInjector<BaseActivity> create(MembersInjector<Activity> supertypeInjector, Provider<Navigator> navigatorProvider) {  
      return new BaseActivity_MembersInjector(supertypeInjector, navigatorProvider);
  }
}

这个注入器一般都会为所有activity的注入成员提供依赖,只要我们一调用inject()方法,就可以获取需要的字段和依赖。 第二个重点:关于我们的DaggerApplicationComponent类,我们有一个Provider,它不仅仅是一个提供实例的接口,它还是被ScopedProvider构造出来的,可以记录创建实例的范围。 Dagger还会为我们的Navigator类生成一个名叫ApplicationModule_ProvideNavigatorFactory的工厂,这个工厂可以传递上面提到的范围参数然后得到这个范围内的类的实例。

代码语言:javascript
复制
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class ApplicationModule_ProvideNavigatorFactory implements Factory<Navigator> {
  private final ApplicationModule module;

  public ApplicationModule_ProvideNavigatorFactory(ApplicationModule module) {  
    assert module != null;
    this.module = module;
  }

  @Override
  public Navigator get() {  
    Navigator provided = module.provideNavigator();
    if (provided == null) {
      throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
    }
    return provided;
  }

  public static Factory<Navigator> create(ApplicationModule module) {  
    return new ApplicationModule_ProvideNavigatorFactory(module);
  }
}

这个类非常简单,它代表我们的ApplicationModule(包含@Provide方法)创建了Navigator类。 总之,上面的代码看起来就像是手敲出来的,而且非常好理解,便于调试。其余还有很多可以去探索,你们可以通过调试去看看Dagger如何完成依赖绑定的。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016-09-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档