阅读 450

google四件套之Dagger2。从入门到爱不释手,之:Dagger2基础知识及在Java中使用(2)

前言

网上都说Dagger2是比较难上手的,我在看了大量资料和使用时也遇到了很多不懂或者模糊的知识点,而且大部分博客资料都比较古老。突然有那么一瞬间,突然明白了所以然,故总结了4篇文章。话说在java中使用还是很繁琐的,不要怕带你真正上手,并运用到我们的Android项目中去。

本次Dagger2讲解总共分4篇:
1、Dagger2基础知识及在Java中使用(1)
2、Dagger2基础知识及在Java中使用(2)
3、Dagger2进阶知识及在Android中使用
4、Dagger2华丽使用在MVP框架中

通过第一篇文章的学习,已经对Dagger2大致了解了。接下来这篇在Java里的使用。主要讲一些其他标注。注重的是细节

还是老样子,博客贴的代码,有部分省略,只为便于理解。

1、@Named的用法

它的用法有点像Tag,提前透露下,@Named是@Qualifier的一种。这里我们举个例:假如我们有个生成狗的机器。通过传入不同type生成不同的狗狗


狗狗的类

public class Dog  {
    private String Type;
    
    public Dog(String type) {
        this.Type = type;
    }

    public String getType() {
        return Type;
    }

    public void setType(String type) {
        Type = type;
    }
}
复制代码


接下来是Module,意思用@Named标识2种不同的初始化路径。假如这个时候不使用@Named的话,你运行的会报错,报错。Dagger2不知道,到底使用哪个

@Module
public class DogModule {
    @Named("tag_1")
    @Provides
    Dog providesJinDog(){
        return new Dog("金毛");
    }

    @Named("tag_2")
    @Provides
    Dog providesEhaDog(){
        return new Dog("二哈");
    }
}
复制代码


Component还是正常的,还是贴下代码吧

@Component(modules = DogModule.class)
public interface DogComponent {
    void inject(IstudyActivity istudyActivity);
}
复制代码


Activity里的使用

public class IstudyActivity extends BaseActivity {
    @Inject
    @Named("tag_1")
    Dog dog1;
    @Inject
    @Named("tag_2")
    Dog dog2;
    
    @Override
    protected void processLogic() {
        DaggerDogComponent.create().inject(this);
        LogUtils.i("看看狗的种类",dog1.getType());
        LogUtils.i("看看狗的种类",dog2.getType());
    }
}
复制代码


看看运行的结果,知道@Named的用法了吧


2、@Qualifier的用法

@Qualifier是限定符,它的作用很像函数的重载。之前说了@Named是@Qualifier的一种。看下@Named的源码:

是不是清晰了。这里我们自定义一个自己的Named

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface MyQualifier {
    String value() default "";
}
复制代码

这样就好了。接下来使用就和@Named一模一样。直接使用我们的标注@MyQualifier就行了。
当然你也可以不要String value() default "";方法。那么久没有Tag了。只能标识一个


接下来说个比较特殊的,直接贴Module代码。他可以初始化数据。没错!牛逼!呸呸呸,是强悍。

@Module
public class DogOtherModule {
 
    @MyQualifier("tag_1")
    @Provides
    String providesChina(){
        return "中华田园犬";
    }

    @MyQualifier("tag_2")
    @Provides
    Dog providesfun(@MyQualifier("tag_1")String dogType){
        return new Dog(dogType);
    }
}
复制代码

3、@Singleton & @Scope 的用法

首先我们看看@Singleton的源码,这么明显,@Singleton是@Scope的一种

@Scope  //注明是Scope
@Documented   //标记文档提示
@Retention(RUNTIME)  //运行时候级别
public @interface Singleton {}
复制代码


在接下来的讲解,我想先说下概念:@Singleton 是作用域单例,是作用域单例,是作用域单例(也可以理解为生命周期。Component在哪build就跟那个类的生命周期绑定,也就说只在那个绑定的实例类里起作用)

  • 1、Component在Activity里build,那么这个单例只限于在这个Activity里。意思退出这个Activity,再进这个Activity时,这个单例会被重新new
  • 2、如果Component在Application里build,那么这个单例就是全局的。注意这里涉及到了我们之前的Component依赖Component。

简单使用介绍:
1、不适用Module的局域单例:

不使用Module,构造函数当然要加上@Inject。然后在要单例的类上加上@Singleton标注;
接下来在Component里:在@Component上加上@Singleton。做好这些,在Activity里使用就和之前一样。但是此时已经是局域单例了


2、使用Module的情况:

使用Module有点类似使用第三库,你需要单例的类,什么都不需要加。记住是什么标注都别加。然后在Module的@Provides上加上@Singleton,然后在@Component上加上@Singleton。即可,也是局域单例


这里稍微来个例子,定义个Children小孩类:

public class Children {

}
复制代码


对应Module如下

@Module
public class ActivityModule {
    @Singleton
    @Provides
    Children ProvidesChildren(){
        return new Children();
    }
}
复制代码


对应Component如下

@Singleton
@Component(modules = ActivityModule.class)
public interface ActivityComponent {
    void inject(KstudyActivity kstudyActivity);
}
复制代码

做完一上操作就好了。在Activity里的操作和普通的@Inject一样。此时chidren就在KstudyActivity是局域单例了。意思退出KstudyActivity页面再重新进入KstudyActivity页面,它会被重新实例化,和之前的chidren不是一个,它被重新new了。


实现全局单例

这里为了更加理解Dagger2,这个时候我打算把局域单例,和全局单例放在一起,放在一个页面展示,并打印他们的HashCode值。这样更加能比较出来,看出区别。之前的局域单例代码留着,先保持不变。

回想一下我们之前的Component依赖Component。这个时候我们要让全局单例的Component在Application里进行Build,然后提供一个方法,提供给需要使用的子页面使用。定义超人类SurperMan

public class SurperMan {

}
复制代码


定义全局单例Module

@Module
public class ApplicationMoule {
    @Singleton
    @Provides
    SurperMan provideSurperMan() {
        return new SurperMan();
    }
}
复制代码


定义全局单例Component,还记得Component依赖Component的知识吗,这里要把SurperMan返回出去(void inject(Application application)你加上也可以)

@Singleton
@Component(modules = ApplicationMoule.class)
public interface ApplicationComponent {
    SurperMan provideSurperMan();
}
复制代码


在Application里把我们的全局单例的Component build好。然后提供一个方法,返回给子页面

public class MyApplication extends Application {

    private ApplicationComponent applicationComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        applicationComponent = DaggerApplicationComponent.builder().build();
    }

    public ApplicationComponent getApplicationComponent() {
        return applicationComponent;
    }
}
复制代码


重点来了,小伙伴们。首先是KstudyActivity我们的要用我们的全局单例。我第一篇文章也说了,一个页面不能有多个Component去注册,否则报错。那么怎么办呢。还记得dependencies吗。

这个时候父Component也准备好了:ApplicationComponent

这个时候我们之前的子Component,局域单例Component:ActivityComponent。需要依赖我们的父Component

@Singleton
@Component(modules = ActivityModule.class,dependencies = ApplicationComponent.class)
public interface ActivityComponent {
    void inject(KstudyActivity kstudyActivity);
}
复制代码

我先告诉你,看上去一切正常,但我告诉你会报错:

This @Singleton component cannot depend on scoped components

这个时候我也是懵逼的,在查阅大量资料。说是局域单例和全局单例在一个页面时,不能一起用@Singleton标注。这里我还是不理解为啥,有明白的小伙伴,望告知。


那么我们自定义@Scope,其实和@Singleton一模一样,只不过用了我们的类名

@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}
复制代码

然后把我们的局域单例的子Component: ActivityComponent 和 子Module:ActivityModule 的@Singleton标注,改成我们的自定义Scope,@ApplicationScope。


然后看Activity里的代码,成果啊

public class KstudyActivity extends BaseActivity {
    //作用域只在Activity里的单例
    @Inject
    Children children_1;
    @Inject
    Children children_2;
    //作用域在全局的单例
    @Inject
    SurperMan surperMan;
    
    @Override
    protected void processLogic() {
        ApplicationComponent applicationComponent = ((MyApplication) getApplication()).getApplicationComponent();
        DaggerActivityComponent.builder().applicationComponent(applicationComponent)
                .build().inject(this);
        //全局单例
        LogUtils.i("看看hashCode值", "作用域在activity里的单例   children_1  hashCode ==>  " + children_1.hashCode());
        LogUtils.i("看看hashCode值", "作用域在activity里的单例   children_2  hashCode ==>  " + children_2.hashCode());
        LogUtils.i("看看hashCode值", "作用域在Application里的全局单例   surperMan  hashCode ==>  " + surperMan.hashCode() + "");

    }
}
复制代码


最后一步,我们进2次KstudyActivity页面,以红线分割,看打印,打印如下:

是不是明显了,Children是局域单例,每次进页面都会被实例化。SurperMan是全局单例,hashCode值一直不会变。这个时候如果你还不清楚,我之前说了看Component在哪build:


我们的SurperMan的ApplicationComponent,在Application里build的

    @Override
    public void onCreate() {
        super.onCreate();
        applicationComponent = DaggerApplicationComponent.builder().build();
    }
复制代码


而我们的Children的ActivityComponent,在哪build呢

protected void processLogic() {
        ApplicationComponent applicationComponent = ((MyApplication) getApplication()).getApplicationComponent();
        DaggerActivityComponent.builder().applicationComponent(applicationComponent)
                .build().inject(this);
    }
复制代码

明白了吗?不明白再简化,是不是在Activity里build的

protected void processLogic() {
        ApplicationComponent applicationComponent = ((MyApplication) getApplication()).getApplicationComponent();
        ActivityComponent activityComponent  = DaggerActivityComponent.builder().applicationComponent(applicationComponent)
                .build();
        activityComponent.inject(this)
    }
复制代码

4、Provider和Lazy的介绍和用法以及和@Inject的区别

Dagger2在Java中的使用,在我总结里,是最后一个知识点了。

首先我们看下定义3个类,我这里区分便于理解:猫,猪,羊。里面都一样,还是贴下代码。更清晰,都跟猫这样:

public class Cat {
}
复制代码


然后是Module

@Module
public class AnimModule {
    @Provides
    Cat catProvides() {
        return new Cat();
    }

    @Provides
    Pig pigProvides() {
        return new Pig();
    }

    @Provides
    Sheep sheepProvides() {
        return new Sheep();
    }
}
复制代码


然后是Component

@Component(modules = AnimModule.class)
public interface AnimComponent {
    void inject(LstudyActivity lstudyActivity);
}
复制代码


在Activity里我们打印他们的haschCode值

public class LstudyActivity extends BaseActivity {
    //使用@Inject正常的。
    @Inject
    Cat cat_1;
    @Inject
    Cat cat_2;

    //使用Provider
    @Inject
    Provider<Pig> pig_1;
    @Inject
    Provider<Pig> pig_2;

    //使用Lazy
    @Inject
    Lazy<Sheep> sheep_1;
    @Inject
    Lazy<Sheep> sheep_2;

    @Override
    protected void processLogic() {
        DaggerAnimComponent.builder().build().inject(this);
        LogUtils.i("看看hashCode值", "@Inject cat_1的hashCode ==> " + cat_1.hashCode());
        LogUtils.i("看看hashCode值", "@Inject cat_2的hashCode ==> " + cat_2.hashCode());
        LogUtils.i("看看hashCode值", "Provider pig_1的hashCode ==> " + pig_1.hashCode());
        LogUtils.i("看看hashCode值", "Provider pig_2的hashCode ==> " + pig_2.hashCode());
        LogUtils.i("看看hashCode值", "Lazy sheep_1的hashCode ==> " + sheep_1.hashCode());
        LogUtils.i("看看hashCode值", "Lazy sheep_2的hashCode ==> " + sheep_2.hashCode());
    }
}
复制代码


我同样进2次页面,以红线分割,看结果

首先我们可以看到这里的进2次页面,所有的hashCode都发生了变化,就是红线上和红线下一一对比,都发生改变了。


先别急,这里还看不出区别,因为Provider和Lazy还没有调用get(),我们单独,点击打印。看看情况: Activity里的代码

public class LstudyActivity extends BaseActivity {
    @Inject
    Provider<Pig> pig_spec;
    @Inject
    Lazy<Sheep> sheep_spec;

    @OnClick(R.id.btn)
    public void specOnClick() {
        LogUtils.i("看看hashCode值", "Provider pig_spec不通过get的hashCode ==> " + pig_spec.hashCode());
        LogUtils.i("看看hashCode值", "Provider pig_spec的hashCode ==> " + pig_spec.get().hashCode());
        LogUtils.i("看看hashCode值", "Lazy sheep_spec的hashCode ==> " + sheep_spec.get().hashCode());
    }
}
复制代码


我们点击2次按钮,以红线分割2次点击结果。看结果

  • 可以看到不调用get()方法的Provider的的HashCode值。一直不变
  • 调用get()方法的Provider的HashCode值,一直在改变
  • 调用get()方法的Lazy的HashCode值,一直不变。

我们回看上一张图。provider修饰的pig_1、pig_2。不调用get()的是一直不变的。Lazy修饰的sheep_1,sheep_2,不调用get()方法。确是一直在变得。到底怎么理解呢

Provider和Lazy和@Inject的总结:

1、@Inject:在依赖注入对象的时候,每次都是实例化的。

2、Lazy:官方介绍是懒加载,调用.get()。才生成实例,而且一旦实例生成,在调用。会一直使用已经生成的实例。你去看生成的代码, Lazy也是Provider的一种,只不过用了LstudyActivity_MembersInjector.injectSheep_1(instance, DoubleCheck.lazy(sheepProvidesProvider));
  那为什么我们第一张图,不调用get()方法的Lazy修饰的sheep_1和sheep_2的HashCode值,确不一样呢。这个时候我们看下代码

    @Inject
    Lazy<Sheep> sheep_1; //这里不通过get()方法,打印的不是Sheep实例,而是Lazy<Sheep>容器的hashCode
    @Inject
    Lazy<Sheep> sheep_2;
复制代码

是不是明白了。调用get(),方法才会生成Sheep实例。这里的标注只是Lazy<T>,所以说这里定义了2个容器,所以才出现不调用get()方法的容器hashCode值才会不一样,调用了get()方法才会一样

3、Provider:其实是一种工厂模式,每次调用.get()。都会生成不同的实例;不调用不会生成实例。那么为什么不调用get()方法,hashCode反而一样呢

//这里一样,不调用get()方法是容器Provider<T>,说是容器,这里更可以说成是工厂
//因为每次调用get()方法,都实例化Pig的实例。所以这里只需要一个工厂(我是这么理解的)
@Inject
Provider<Pig> pig_1; 
复制代码

结束语:在Java里使用Dagger2还是很麻烦的。下片介绍在Android中使用,你会瞬间爱上Dagger2

本文github Demo地址

花了老大劲,别误会,不是要钱。能不能留下个足迹star啊