Android基础 再看dagger2

dagger2三年前写项目就一直在用,今天再炒一次剩饭,总结归纳,便于以后复习

仙图

dagger2理解

一个很厉害的"工厂",它能提供各种对象,并通过@Inject将对象注入到目标类,目标类就能使用这个对象了,而不用new关键字去生成对象
理解dagger2需要知道下面三个最基础的东西

@Module

它通常与@Provides一起使用,用于"生产"对象,但是用@Inject标记构造函数就可以提供依赖了么,为什么还需要@Module?很多时候我们需要提供依赖的构造函数是第三方库的,我们没法给它加上@Inject注解,又比如说提供以来的构造函数是带参数的,如果我们之所简单的使用@Inject标记它,那么他的参数又怎么来呢?@Module正是帮我们解决这些问题的,我习惯形象的理解成药水

@ Component

用于标注接口,是依赖需求方和依赖提供方之间的桥梁。被Component标注的接口在编译时会生成该接口的实现类(Dagger+Component名字),我们通过调用这个实现类的方法完成注入;Component接口中主要定义一些提供依赖的声明,我理解为注射器

@Inject

一是用来标记需要依赖的变量,以此告诉Dagger2为它提供依赖;
二是用来标记构造函数,Dagger2通过@Inject注解可以在需要这个类实例的时候来找到这个构造函数并把相关实例构造出来,以此来为被@Inject标记了的变量提供依赖,我理解为针头

一般我们在项目中,都会有这三个Module和对应的Component,分别是AppModule,ActivityModule,FragmentModule以及对应的ActivityComponent,FragmentComponent,AppComponent,先分析AppModule,AppComponent,其他稍微简单一些

  • AppModule
    它负责生产全局的Application,和其他一些长生命周期(全局生命周期,因为它要被AppComponent所连接, AppComponent又在Applcation中初始化)的工具类等,代码如下
@Module
public class AppModule {
private Application mApplication;
    public AppModule(Application application) {
        this.mApplication = application;
    }

    @Singleton
    @Provides
    public Application provideApplication() {
        return mApplication;
    }

    @Singleton
    @Provides
    public Gson provideGson() {
        return new Gson();
    }

    @Provides
    @Singleton
    IDataBase provideIDataBase(DBHelper dbHelper) {
        return dbHelper;
    }

}

可以看到Module就是提供这些依赖的地方,dagger会根据@Provides标记的方法返回依赖对象,这个AppModule中提供了Application和Gson对象的创建,当然还可以有其他的对象创建都可以放进去

  • AppComponent
    这个是全局的连接器(注射器),它的里面装的就是AppModule产生的对象
@Singleton
@Component(modules = {AppModule.class, HttpModule.class})
public interface AppComponent {

    Application application();
    Gson gson();
    DataManager getDataManager();
    HttpHelper getHttpHelper();
    DBHelper getDBHelper();
}

上面代码中,可以看到装载了HttpHelper,DBHelper等对象,那再看看这两个生产者(Module)

@Module
public class HttpModule {

    @Singleton
    @Provides
    Retrofit.Builder provideRetrofitBuilder() {
        return new Retrofit.Builder();
    }


    @Singleton
    @Provides
    OkHttpClient.Builder provideOkHttpBuilder() {
        return new OkHttpClient.Builder();
    }


    @Singleton
    @Provides
    Retrofit provideRetrofit(Retrofit.Builder builder, OkHttpClient client) {
        return createRetrofit(builder, client);
    }

    @Singleton
    @Provides
    OkHttpClient provideClient(OkHttpClient.Builder builder) {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
        builder.addInterceptor(loggingInterceptor);
        builder.connectTimeout(10, TimeUnit.SECONDS);
        builder.readTimeout(20, TimeUnit.SECONDS);
        builder.writeTimeout(20, TimeUnit.SECONDS);
        //错误重连
        builder.retryOnConnectionFailure(true);
        return builder.build();
    }

    @Singleton
    @Provides
    UserAPI provideUserService(Retrofit retrofit) {
        return retrofit.create(UserAPI.class);
    }

    private Retrofit createRetrofit(Retrofit.Builder builder, OkHttpClient client) {
        return builder
                .baseUrl(URLS.URL_HOST)
                .client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
}

注意: provideUserService, provideClient这几个的生产者里面都是含有参数的,dagger2在使用编译时注解时,它寻找注解参数的顺序如下

步骤1:首先查找@Module标注的类中是否存在提供依赖的方法。
步骤2:若存在提供依赖的方法,查看该方法是否存在参数。
a:若存在参数,则按从步骤1开始依次初始化每个参数;
b:若不存在,则直接初始化该类实例,完成一次依赖注入。
步骤3:若不存在提供依赖的方法,则查找@Inject标注的构造函数,看构造函数是否存在参数。
a:若存在参数,则从步骤1开始依次初始化每一个参数
b:若不存在,则直接初始化该类实例,完成一次依赖注入。

这里拆分着看一下:
第一步,查找是否有@Module标注,意思就是从Module找是否有被@Provides标注并且返回值正好是需要的那个对象,比如在MainActivity中@Inject注入了UserAPI这个类,dagger就会去@Module中(也就是HttpModule)去找是不是有Provides标注,且返回值是UserAPI的
第二步,这一找就找到了,确实存在provideUserService这个方法,然后查看是否有参数
发现有参数Retrofit,这个时候要初始化这个Retrofit参数就又回到第一步了,一看,有provideRetrofit这个方法,有Provides且返回值正确,这就初始化参数完成依赖注入
那如果是上述步骤3的情况呢,那就是AppModule中provideIDataBase这个方法,返回IDataBase,它的参数DBHelper我这里没有定义Module去Provides注解生成,则就会去DBHelper中查找@Inject标注的构造函数

public class DBHelper implements IDataBase {

    @Inject
    public DBHelper(){

    }
}

下面总结一下除了上述三个注解外,其他我们用到的很重要的几个注解

@Provides

@Provides用于标注Module所标注的类中的方法,该方法在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject的变量赋值;

@Scope

@Scope用于自定义注解,我们可以通过@Scope自定义的注解来限定注解作用域,实现局部的单例;比如我们前面使用到的@ActivityScope:

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

如果需要提供局部单例支持,则需要在Component中和@provides注解的方法上@ActivityScope,这里说的局部单例的意思是在该Component中是唯一的,如果Component是全局唯一的话就是全局单例了,比如AppComponent。

@Singleton
@Singleton其实就是一个通过@Scope定义的注解,我们一般通过它来标记全局单例(AppComponent)。

@Qualifiers 和 @Name

Qualifiers 是修饰符的意思,那么它修饰的是什么呢?我们在一个 Module 中 @Provides 提供的依赖是由返回值决定的。这样就会出现问题,同一种类型不同实例,怎么去区别?比如

public class SecondActivity extends AppCompatActivity {
    @Inject
    String phone;
    @Inject
    String computer;

}

当我们注解的时候,分别要Module生成两个不同的字符串,我们很显然想到如下这样的代码,但是这样是编译不通过的

@Module
public class SecondActivityModule {

    @Provides
    @Singleton
    public TestSingleton provideTestSingleton(){
        return new TestSingleton();
    }

    @Provides
    public String providePhone() {
        return "手机";
    }

    @Provides
    public String providePhone() {
        return "电脑";
    }

}

那么dagger2使用@Name即可实现

 @Inject
@Named("phone")
String phone;

@Inject
@Named("computer")
String computer;

@Module
public class SecondActivityModule {

    @Provides
    @Singleton
    public TestSingleton provideTestSingleton(){
        return new TestSingleton();
    }

    @Provides
    @Named("phone")
    public String providePhone() {
        return "手机";
    }

    @Provides
    @Named("computer")
    public String provideComputer() {
        return "电脑";
    }

}

但是还是很麻烦,我们可以自定义注解,因为@Name只是被@Qualifier注解的一个注解

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Phone {
}


@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Computer {
}

使用的时候,在目标类上这么注解

@Inject
@Phone
String phone;

@Inject
@Computer
String computer;

在Module中这么写

@Module
public class SecondActivityModule {

    @Provides
    @Singleton
    public TestSingleton provideTestSingleton(){
        return new TestSingleton();
    }

    @Provides
    @Phone
    public String providePhone() {
        return "手机";
    }

    @Provides
    @Computer
    public String provideComputer() {
        return "电脑";
    }

}

dependencies 和 @SubComponent

上面我们说要将Module提供给调用者进行注入,那么就需要一根注射器@Component,dagger2除了这种直接写一个@Component来提供连接器的形式,还支持连接器依赖
Component 依赖 是通过 @Component 的注解中 dependencies 选项来标识的,意思是指 该 Component依赖 dependencies 指定的 Component

@ActivityScope
@Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
    void inject(MainActivity target);
    void inject(LoginActivity target);
}
@Singleton
@Component(modules = {AppModule.class, HttpModule.class})
public interface AppComponent {
    App getContext();
    DataManager getDataManager();
    HttpHelper getHttpHelper();
    DBHelper getDBHelper();
}

上面的代码通过dependencies关键字,让ActivityComponent依赖AppComponent
注意:如果ActivityComponent中要能提供注入上述DataManager,HttpHelper,DBHelper等的能力,那么就必须如上代码所示,显示提供getXXX方法,否则编译会报错
这样在Activity中就能够注入上述对象了

@BindLayout(R.layout.activity_main)
public class MainActivity extends BaseActivity<MainPresenter> implements MainContract.MainView {
    @Inject
    DataManager dataManager;
...
}

再看SubComponent
@SubComponent 也是管理 Component 间的依赖,不同的是这种方式不需要 在被依赖的 Component 中显式的声明可以获取相应类实例的方法。通过 @SubComponent 来管理的 Component 之间是一种 继承关系,子 Component 理所当然的可以使用父 Component 的可以提供的类实例。
具体用法,这里只举例,我用的很少
TestActivityComponent

@Subcomponent(modules = TestModule.class)
public interface TestActivityComponent {
    void injectActivity(TestActivity Activity);
}

AppComponent

@Component(modules = APPModule.class)
public interface AppComponent {
    //注意这里的写法,就是显示关联二者
    TestActivityComponent addSub(TestModule testModule);
}

注意:这个格式是固定的

子Component 方法名 (子Component 对应的 Module);

在Activity中使用

public class TestActivity extends AppCompatActivity {

    @Inject
    TestBean ;//这个TestBean是APPModule中提供的
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_component_sub);
        DaggerAppComponent  daggerAppComponent= DaggerAppComponent.builder().build();
        daggerAppComponent.addSub(new TestModule()). injectActivity(this);
    }
}

那么dependencies 和 @SubComponent有什么区别了,官方文档显示

In general we have two ways to do this - with @Subcomponent annotation or with Components dependencies. The main difference between them is an objects graph sharing. Subcomponents have access to entire objects graph from their parents while Component dependency gives access only to those which are exposed in Component interface.
And what is more important all scoping stuff happens here. All instances taken from UserComponent inherited from AppComponent still are singletons (in Application scope). But those which are produced by UserModule (which is a part of UserComponent) will be “local singletons” which live as long as this UserComponent instance.

翻译过来:

Subcomponent 可以访问到 父 Component 的全部对象图,而 Component 只可以访问到在所依赖的 Component 中暴露出来的类。
更重要的是,所有范围内的事情都发生在这里。从AppComponent继承的UserComponent获取的所有实例仍然是单例(在应用程序范围内)。但是由UserModule(它是UserComponent的一部分)生成的那些将是“本地单例”,其存在与此UserComponent实例一样长。

总结区别就是:

  1. Subcomponent 可以访问到 父 Component 的全部对象图,而 Component 只可以访问到在所依赖的 Component 中暴露出来的类。
  2. Subcomponent继承得到的子组件可以访问到父组件中提供的实例,并且该实例的scope 和父组件定义的scope 相同。

还有一点要注意,多 Component 与 Scope 使用时有如下限制

  1. Component 和他所依赖的 Component 不能公用相同的 Scope,每个Component 都要有自己的 Scope,编译时会报错,因为这有可能破坏Scope的范围。
  2. @Singleton 的 Component 不能依赖其他 Component。正常来说使用 @Singleton 注解的Component应为全局的Component 。
  3. 无Scope的Component不能依赖有Scope的Component,因为这也会导致Scope 被破坏。
  4. Module提供 或通过构造函数注入依赖图的类和其 Component 必须使用相同的Scope 。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349