Randall | 二、Dagge2

一、依赖注入是什么?

曾几何时,项目中每个依赖单例Manager的地方,getInstance()方法是必不可少的。

public class AccountManager {
  private static class Holder {
    private static final AccountManager instance = new AccountManager();
  }
  public static AccountManager getInstance() {
    return Holder.instance;
  }
  private AccountManager() {
    // Other instance
  }
  ...
}

每次像这样写一遍真是挺烦人的,如果内部再有一些依赖的话,会变得更加扑朔迷离。

public class App extend Application {
  private AccountManager mAccount;
  private NetworkManager mNet;
  private DatabaseManager mDb;
  ...
  @Override
  public void onCreate() {
    super.onCreate();
    mAccount = AccountManager.getInstance();
    mNet= NetworkManager.getInstance(this, mAccount);
    mDb= DatabaseManager.getInstance(this, "app.db", 1);
  }
  ...
}

这只是简单的例子,遇到大型项目的时候,需要初始化的全局实例更多,并且显得更加复杂。

那么为了解决依赖相关的问题,减少每个项目开启时重复的样板代码构建,Dagger2就派上了用场。

有一个用来解释【依赖注入是什么】的最佳案例就是:ButterKnife

它是这样用的:

public class MainActivity extends AppCompatActivity {
  @BindView(R.id.button1) View button1;
  @BindView(R.id.text1) View text1;

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

声明依赖的组件,通过注解确定id,使用bind方法传入this,完成依赖注入。

这就好像你告诉一个服务商,你需要什么,然后给出收货地址,而服务商则生产出来,逐一寄到提供的地址。

Dagger2的用法,也是上述的形式,唯一例外的是,你将同时扮演消费者和生产商。

这里不考虑复杂的情况,从入门的角度来看,对于使用Dagger2实现依赖注入,很有必要。


二、怎么使用Dagger2?

Dagger2是谷歌forked from square/dagger的一个分支,谷歌Dagger2开源框架的介绍是:

A fast dependency injector for Android and Java.

对于其与square的历史渊源,github上框架介绍已经写得非常清楚
——翻译过来,大概是:

  • 消除所有反射,提升运行时性能
  • 编译时处理,更快更好的构建速度

其实我是从square官网上发现的Dagger,然后在这个框架的github上看到介绍说,项目已经标记为不再维护,推荐使用Dagger2;而另一方面,网上找到的关于Dagger的资料,要么无法解释清楚为什么要用,要么就是用起来特别繁琐(还不如getInstance简单粗暴)。

因此转向了Dagger2,并且在很长的一段困惑期中,苦苦挣扎:

要用依赖注入吗?真的要用吗?为什么要用呢?用了有什么好处呀?……

后来干脆自己建立demo,一步步把玩,其他资料都不再作为参考,只留下官方sample作为注解的学习。迈出这一步之后,才终于发现Dagger2的神奇和便利。


1.打开你项目下的gradle文件,添加Dagger2项目的依赖管理
    // 依赖注入框架
    compile 'com.google.dagger:dagger:2.10'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.10'

PS:这个版本并非最新版,有需要的话,可以去官网依赖最新的版本,这里为了稳定性,将不做升级。

增加依赖注入框架
2.继承Application创建RandallApp,然后开始构建Dagger2部件和模型
AppComponent接口

使用javax的注解@Singleton标记为单例,即所实现的类只存在一个实例:

import javax.inject.Singleton;

@Singleton
public interface AppComponent {
    // add inject method
}

创建AppModule并依赖Application实例:

public final class AppModule {
  private final Application application;
  public AppModule(Application application) {
    this.application = application;
  }
  @Provides @Singleton Application provideApplication() {
    return application;
  }
}

创建AndroidModule,因为其他框架还没有添加依赖,所以这里用Android SDK中的SystemService举例:

@Module
public final class AndroidModule {
  @Provides @Singleton AudioManager provideAudioManager(Application application) {
    return (AudioManager) application.getSystemService(Context.AUDIO_SERVICE);
  }
  @Provides @Singleton SensorManager provideSensorManager(Application application) {
    return (SensorManager) application.getSystemService(Context.SENSOR_SERVICE);
  }
  @Provides @Singleton Sensor provideSensorAccelerometer(SensorManager sensorManager) {
    return sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
  }
  @Provides @Singleton ConnectivityManager provideConnectivityManager(Application application) {
    return (ConnectivityManager) application.getSystemService(Context.CONNECTIVITY_SERVICE);
  }
}

简单说明一下AndroidModule和AppModule的关系:

首先,AndroidModule是细节模型,事实上全部在AppModule中提供实例也没有关系,但为了明确功能和类型,所以就有了AndroidModule。

可以理解为,AndroidModule就是AppModule的分身、子模型,只要通过这样的语法就能导入:

@Module(includes = {
  AndroidModule.class,
})
public final class AppModule {
  ...
}

@Module和@Provides都是Dagger2的注解。前者用于类注解,标记这个类是一个模型;后者则用于方法注解,标记返回的实例可以提供依赖。

这两个注解,可以让“服务商”知道自己有哪些物品可以生产:

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
  // add inject method
}

AppComponent接口有两种形式的方法:

  • 当需要注入的依赖很多时,可以创建inject方法,传入需要被注入依赖的对象实例;
  • 当仅需要一个全局单例时,可以创建返回对应实例的方法。

如何抉择,当由具体需求所决定。

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
  // add inject method
  void inject(LoginActivity activity);
  // create return method
  Picasso picasso();
}

完成Dagger2部件与模型的构建后,你的项目结构应当是这样:

Dagger2项目结构

随后,你应该make一下工程,使得Dagger2编译出你所需要的接口实现类。

make完成后,没有错误的话,你可以在RandallApp中,重写onCreate方法,然后输入Dagger...就会发现已经有了DaggerAppComponent这个编译生成的类。

public class RandallApp extends Application {
  @Override public void onCreate() {
    super.onCreate();
    DaggerAppComponent.builder().build();
  }
}

可以看看DaggerAppComponent的一些细节,其中可能存在一些困惑:

public final class DaggerAppComponent implements AppComponent {
  private DaggerAppComponent(Builder builder) {
    assert builder != null;
  }

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

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

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

    public AppComponent build() {
      return new DaggerAppComponent(this);
    }

    /**
     * @deprecated This module is declared, but an instance is not used in the component. This
     *     method is a no-op. For more, see https://google.github.io/dagger/unused-modules.
     */
    @Deprecated
    public Builder appModule(AppModule appModule) {
      Preconditions.checkNotNull(appModule);
      return this;
    }

    /**
     * @deprecated This module is declared, but an instance is not used in the component. This
     *     method is a no-op. For more, see https://google.github.io/dagger/unused-modules.
     */
    @Deprecated
    public Builder androidModule(AndroidModule androidModule) {
      Preconditions.checkNotNull(androidModule);
      return this;
    }
  }
}

为什么Module都被“过时”了呢?因为当前工程中,没有任何地方发出依赖需求。

提供全局的部件实例
public class RandallApp extends Application {
  private static AppComponent appcomponent;
  @Override public void onCreate() {
    super.onCreate();
    appcomponent = DaggerAppComponent.builder().build();
  }  
  public static AppComponent appComponent() {
    return appcomponent;
  }
}
使用部件实例注入实例
public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> {
  ...
  // dependency injection
  @Inject ConnectivityManager cm;
  @Inject AudioManager am;

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

    RandallApp.appComponent().inject(this);
    ...
  }
}

再make一下,此时所有依赖已经成功注入

public final class DaggerAppComponent implements AppComponent {
  private Provider<Application> provideApplicationProvider;

  private Provider<ConnectivityManager> provideConnectivityManagerProvider;

  private Provider<AudioManager> provideAudioManagerProvider;

  private MembersInjector<LoginActivity> loginActivityMembersInjector;

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

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

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

    this.provideApplicationProvider =
        DoubleCheck.provider(AppModule_ProvideApplicationFactory.create(builder.appModule));

    this.provideConnectivityManagerProvider =
        DoubleCheck.provider(
            AndroidModule_ProvideConnectivityManagerFactory.create(
                builder.androidModule, provideApplicationProvider));

    this.provideAudioManagerProvider =
        DoubleCheck.provider(
            AndroidModule_ProvideAudioManagerFactory.create(
                builder.androidModule, provideApplicationProvider));

    this.loginActivityMembersInjector =
        LoginActivity_MembersInjector.create(
            provideConnectivityManagerProvider, provideAudioManagerProvider);
  }

  @Override
  public void inject(LoginActivity loginActivity) {
    loginActivityMembersInjector.injectMembers(loginActivity);
  }

  public static final class Builder {
    private AppModule appModule;

    private AndroidModule androidModule;

    private Builder() {}

    public AppComponent build() {
      if (appModule == null) {
        throw new IllegalStateException(AppModule.class.getCanonicalName() + " must be set");
      }
      if (androidModule == null) {
        this.androidModule = new AndroidModule();
      }
      return new DaggerAppComponent(this);
    }

    public Builder appModule(AppModule appModule) {
      this.appModule = Preconditions.checkNotNull(appModule);
      return this;
    }

    public Builder androidModule(AndroidModule androidModule) {
      this.androidModule = Preconditions.checkNotNull(androidModule);
      return this;
    }
  }
}

看起来似乎有一个问题,当AppModule是null的时候,会抛出一个异常。原因在于,AppModule是需要Application实例去创建,但是Application是Android在应用打开时才被创建,因此需要在Applicaion的onCreate方法中,构建AppComponent时加入一个AppModule实例。

public class RandallApp extends Application {
  private static AppComponent appcomponent;
  @Override public void onCreate() {
    super.onCreate();
    appcomponent = DaggerAppComponent.builder()
            .appModule(new AppModule(this))
            .build();
  }
  public static AppComponent appComponent() {
    return appcomponent;
  }
}

AndroidModule已经自动new出来实例,无需多费功夫。

三、总结

这样就完成了整个Dagger2的构建工作,以后再有其他框架的类实例需要被依赖,只要建立对应的Module类,使用provides标记的方法提供对应的类实例,并包括在AppModule中,然后通过AppComponent添加inject方法注入需求类实例即可。

再说一点,维护期间,如果想改变框架,或者删除框架,只需要在AppModule中注释导入的对应Module,然后在需求类实例中,将Inject的依赖注释即可。

Dagger2的基本使用就到这里,后面开始建立基于DataBinding框架的MVVM设计模式。

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

推荐阅读更多精彩内容