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

3,655 阅读14分钟

前言

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

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

首先简单申明下,Dagger2的好处不是本文的重点。大家可以自行百度。Dagger2是依赖注解框架,像我们之间的butterknife也是这样的框架,想这样的框架依赖一般都有2行。第二行是以annotationProcessor开头的。这其实是apt的工具,而且这样的依赖注解框架不会影响性能(不是反射机制),在编译的时候,apt把要用的代码生成。所以大可放心使用
再举我理解的例子(大家不要全信,哈哈稍微不恰当):@Component相当于一个注射器(记住是接口);@Module相当于注射液,就是数据源(记住这里是类或者抽象类),此时要把注射液放入指定哪个注射器如:@Component( modules = ... );@Inject 相当于标注被注射体。

之后的讲解都是走完简单的流程,实现功能,然后讲大概理解。贴在博客上的代码,可能会省略部分代码,便于理解。github上的Demo及注释,非常详细,接近完美0-0#
如果是完全没了解过,关于Dagger一些标注的具体介绍和理解,推荐这里有3篇介绍标注的意思和怎么工作的

首先添加依赖

implementation 'com.google.dagger:dagger:2.24'
annotationProcessor "com.google.dagger:dagger-compiler:2.24"

1、@Inject & @Component 的简单使用(不带@Module)

首先随便定义个类:Person,无参构造方法用@Inject标注:

public class Person {
    @Inject
    public Person() {

    }
}

然后定义我们的Component(这里稍微提一下,如果一个页面定义多个Component,你build的时候报错,是不是)
这里的命名规则最好是以我们页面类名+Component,这样比较清晰。用@Component标注,里面有个void方法,方法名随意定,建议用inject最好,当然也是清晰,参数是我们需要依赖注解的页面:
@Component
public interface AstudyActivityComponent {
    void injectTo(AstudyActivity astudyActivity);
}

做好上面步骤后,点开studio里build标签下的Make Project。让apt帮我们生成代码,一般生成代码为Dagger+你定义Component的类名。

之后这个步骤不再重复,就是你写完准备代码的时候一定要让apt生成代码,Make Project下

然后在我们的Activity里:

public class AstudyActivity extends BaseActivity {
    @Inject
    Person person;
    
    @Override
    //这里是我封装的onCreate,省略部分代码,只为理解,之后都请忽略!
    protected void processLogic() {
        //第一种
        DaggerAstudyActivityComponent.create().injectTo(this);
        //第二种
        //DaggerAstudyActivityComponent.builder().build().injectTo(this);
    }
}

在我们的Activity里build下我们的Component,然后注册在我们的Activity里,就可以使用通过我们的@Inject使用我们的person了。
这里初始化有2种:
1、DaggerAstudyActivityComponent.create().injectTo(this);
2、DaggerAstudyActivityComponent.builder().build().injectTo(this); 这个使用module传值一定要使用

那么一个简单的使用就实现了,这里忽略了new的过程,从这个过程就知道他解耦的实现了。

简单使用大致步骤( 看懂请略过 ):

  • 第一步:用Inject标注,告诉dagger2可以实例化这个类,如:Person
  • 第二步:使用注解Component,表示要将依赖注入到AstudyActivity里
  • 第三步:使用android studio build下的Make Project生成代码,使他自动生成DaggerComponent生成的类,类名是:Dagge+我们定义的Component的名字

2、带@Module的使用

为什么会有module的概念,比如上面的Person的构造方法可以用@Inject标注,但是引入的第三方库可是没有办法加的,所以这里使用@Module可以解决这个问题。
这里我们定义个Human,假装他是第三方类库,里面没有使用@Inject

public class Human {
    public Human() {

    }
}


接下的步骤先定义我们的数据源Module,也就是先定义初始化的地方,之前Person的构造方法是用@Inject。首先命名规则最好加上Module,用@Module标注。然后里面定义个方法,用 @Provides标注。返回值为我们需要初始化的类,方法名最好是以Provides结尾。其实这里可以定义多个方法,后面说

@Module
public class BstudyActivityModule {
    @Provides
    Human humanProvides(){
       return new Human();
    }
}


然后是我们的Component。这里与之前不同的是(modules = BstudyActivityModule.class),这就相当于把注射液放进注射器。这里可以有多个Module,后面说

@Component(modules = BstudyActivityModule.class)
public interface BstudyActivityComponent {
    void injectTo(BstudyActivity bstudyActivity);
}

Make Project后,在Activity里操作与之前一模一样。

带Module使用大致步骤( 看懂请略过 )

  • 1、假设Human不可随意更改,没有@Inject标注(第三方类库,不是你项目里的代码肯定没有@Inject)用@module标注BstudyActivityModule,用@Provides标注方法的返回值就是我们需要inject的类型
  • 2、编写Component接口使用@Component标注这个接口,并使用modules=的方法链接上第一步中编写的Module类;
  • 3、接下来就和AstudyActivity中的使用方式一样了

3、通过Module传参

这个其实不重要,重要的引出4,5的概念。明白这步,后面才好理解。
首先我们假设2个类,女人类,和灵魂类:且灵魂类有个钱的属性。灵魂类又是女人的属性。灵魂类如下:

public class Soul {
    private int money;
    public Soul() {

    }
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
}

女人如下:

public class Woman {
    private Soul soul;

    public Soul getSoul() {
        return soul;
    }

    public Woman(Soul soul) {
        this.soul = soul;
    }
}


首先还是定义我们的Module先。既然可以传参,当然是有个money的属性。最终我们依赖注解是要使用Woman类。我们的providesWoman方法用@Provides标注,这个时候他回去找Soul的初始化,先通过@Provides去找Soul。这个时候找到了providesSoul。这样就形成了女人类。假如这个时候没有providesSoul。他会去找Soul类里有没有用@Inject标注的构造函数。如果还没有,那么不好意思。出错

@Module
public class CstudyModule {
    private int money;
    
    public CstudyModule(int money) {
        this.money = money;
    }

    @Provides
    Soul providesSoul() {
        Soul soul = new Soul();
        soul.setMoney(this.money);
        return soul;
    }

    @Provides
    Woman providesWoman(Soul soul) {
        return new Woman(soul);
    }
}

接下来是Component,没有变化

@Component(modules = CstudyModule.class)
public interface CstudyActivityComponent {
    void injectTo(CstudyActivity cstudyActivity);
}

Activity有些许变化,当然是传参了。我们给女人的灵魂传了100块,对,女人只值100块!

public class CstudyActivity extends BaseActivity {
    @Inject
    Woman woman;
    @Override
    protected void processLogic() {
        DaggerCstudyActivityComponent.builder()
            .cstudyModule(new CstudyModule(100))
            .build().injectTo(this);
    }
}

注意点( 看懂请略过 ):

  • 在Module的构造函数带有参数且参数被使用的情况下,所生产的Component类就没有create()方法了。
  • 在这里的module如果没有providesSoul()方法的话,还有一种情况只要在Soul的构造方法有@Inject也是可行的。

4、使用@Component.Builder(需先了解 3、通过Module传参)

我们把3、通过Module传参apt生成的代码点开DaggerCstudyActivityComponent;看下图是不是发现了一个Builder类,这是apt帮我们自动生成的,我们当然也能自己实现

还是以3、通过Module传参的例子,我们不用系统帮我们生成的Builder,自己定义。前面的步骤都一样,直接来看我们的Component。
自己定义个接口类Builder,并用@Component.Builder标注里面有2个方法:

  • 方法一:是返回值Builder的方法,这里如果传module就会以我们传的为主,否则他会帮我们生成一个money为0的module。当然你也随意传数据类型,只不过无效。可以试试,
  • 方法二:是返回值为当前Component的方法,方法名其实都可以自定义,当最好以规范为主,用习惯了就明白了
@Component(modules = CstudyModule.class)
public interface DstudyActivityComponent {
    void injectTo(DstudyActivity dstudyActivity);
    
    @Component.Builder
    interface Builder {
        Builder cstudyModule(CstudyModule cstudyModule);
        DstudyActivityComponent build();
    }
}


Activity里使用是一样的。只不过我们把系统自动帮我们生成的,自己去写了而已。还是贴下Activity代码吧

public class DstudyActivity extends BaseActivity {
    @Inject
    Woman dWoman;
    @Override
    protected void processLogic() {
        DaggerDstudyActivityComponent.builder()
            .cstudyModule(new CstudyModule(100))
            .build().injectTo(this);
    }
}

大致理解和总结为( 看懂请略过 ):

  • 通过我们cstudy的内容,你可以点开cstudyModule查看源码,可以看到有个Builder cstudyModule(CstudyModule cstudyModule){}。这是dagger2自动生成的(你还可以通过,app/build/generated/source/apt/debug/你的包名/DaggerAppComponent.java 目录下找到)


所以@Component.Builder的用法,用module传参的例子。其他都不用变,要变的是Component,定义个Builder并用@Component.Builder标注。这里有2个方法:

  • 方法一:是返回值Builder的方法,这里如果传module就会以我们传的为主,否则他会帮我们生成一个money为0的module。当然你也随意传数据类型,只不过无效。可以试试
  • 方法二:是返回值为当前Component的方法,方法名其实都可以自定义,当最好以规范为主,用习惯了就明白了

5、使用@BindsInstance(需先了解 4、使用@Component.Builder)

这个时候你又说了,传参,我们总是要new CstudyModule(100)。本来说Dagger2在使用的时候省略new的过程,解耦。但这里还要new,很low是不是。不急不急,强大的google把一切都想好了。这个时候遇到一个新的标注@BindsInstance。
@BindsInstance 大致这里可以理解为帮我们省去写类的构造方法,而直接去赋值

省掉构造方法,那么当然是首先改我们的Module。我们去掉Module的构造方法及money成员变量属性,把money加到providesSoul里成型参。看到这里,这里又可理解为@BindsInstance 其实去找@Provides标记的方法的参数,假如类型一致就去初始化

@Module
public class EstudyModule {
    @Provides
    Soul providesSoul(int money) {
        Soul soul = new Soul();
        soul.setMoney(money);
        return soul;
    }

    @Provides
    Woman providesWoman(Soul soul) {
        return new Woman(soul);
    }
}


然后是修改的Component,改完Module,当然是modules = EstudyModule.class。这些我就忽略了,看了上面的步骤你也明白,我就直接说关键地方了。用@BindsInstance标注我们返回值为Builder的方法。里面的参数改成我们的int Money。当然改成我们用@Provides标注的类型其实都可以,只不过这里你如果改成Soul soul,当然你初始化还是要传new Soul。过程就是这个过程

@Component(modules = EstudyModule.class)
public interface EstudyActivityComponent {
    void injectTo(EstudyActivity estudyActivity);
    
    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder initMoney(int money);
        EstudyActivityComponent build();
    }
}


最后是我们的Activity

public class EstudyActivity extends BaseActivity {
    @Inject
    Woman woman;
    @Override
    protected void processLogic() {
        DaggerEstudyActivityComponent.builder()
            .initMoney(100)
            .build().injectTo(this);
    }
}

看到这里,是不是觉得Dagger2还比较有意思。更有意思的在后面。当然也越来越绕了,但是你得兴奋起来,精髓啊。

6、Component依赖Component,使用dependence

这里我们以Activity和Fragment为例。假设我们再Activity依赖注入Human类,此时在Fragment里使用
先看定义Module,和之前一样,没什么区别

@Module
public class FstudyActivityModule {
    @Provides
    Human providesHuman() {
        return new Human();
    }
}


再建ActivityComponent当然这里,你也可以加上void inject(FstudyActivity fstudyActivity)。重要一点是我们要把依赖注入的类返回出去,定义方法provideHuman,因为是Component依赖Component。所以也能理解

@Component(modules = FstudyActivityModule.class)
public interface FstudyActivityComponent {
    Human provideHuman();
}


再使用dependencies建FragmentComponent暂且可以理解为子Component,因为后面真的有子Component。dependencies = FstudyActivityComponent.class写上我们的父Component。后面是注入到Fragment里

@Component(dependencies = FstudyActivityComponent.class)
public interface TestFragmentComponent {
    void inject(TestFragment testFragment);
}


在Activity里,要先生成ActivityComponent,然后提供个方法,把父Component提供给Fragment

public class FstudyActivity extends BaseActivity {
    private FstudyActivityComponent fstudyActivityComponent;
    @Override
    protected void processLogic() {
        fstudyActivityComponent = DaggerFstudyActivityComponent.create();
    }

    public FstudyActivityComponent getFstudyActivityComponent() {
        return fstudyActivityComponent;
    }
}


在Fragment里

public class TestFragment extends BaseFragment {
    @Inject
    Human human;
    @Override
    protected void processLogic(Bundle savedInstanceState) {
        FstudyActivityComponent fstudyActivityComponent = ((FstudyActivity) getActivity()).getFstudyActivityComponent();
        DaggerTestFragmentComponent.builder()
                .fstudyActivityComponent(fstudyActivityComponent)
                .build().inject(this);
    }

}

好了,在fragment可以使用human了。在java里使用,确实很绕,代码多的让你难以接受。建议先理解,后面出的一篇在Android中使用,你会很爽。

大致理解和总结为( 看懂请略过 ):

  • 1、假设我们用Human注入,这里的FstudyActivityModule 和之前的Module一样,正常
  • 2、FstudyActivityComponent把要注入的类返回
  • 3、在TestFragment方面,我们新建一个TestFragmentComponent 依赖 FstudyActivityComponent;
  • 4、在FstudyActivity自定义一个方法把FstudyActivityComponent提供出去,供TestFragment使用
  • 5、在TestFragment,注册下就OK了。很绕,个人建议先明白这个流程就好了

7、Component依赖Component,使用@subComponent(这个和 【标题6】 实现的是同一个效果)

虽然是实现同一个效果,但是方式不同,目的是让你更多了解Dagger2。同样以上面的例子。Module和上面一样不变(我这里是为了Demo区域化,虽然类名不同,但是内容是一致的)
首先建子Component,FragmenComponent,用@Subcomponent标注,并注入我们的Fragment里。为什么先建子Component呢。因为子Component要在父Component返回,绕不绕!!

@Subcomponent
public interface DemoFragmentComponent {
    void inject(DemoFragment demoFragment);
}


然后是父Component,ActivityComponent,父Component一切正常,返回值是子Component

@Component(modules = GstudyActivityModule.class)
public interface GstudyActivityComponent {
    DemoFragmentComponent demoFragmentComponent();
}


在Activity里的操作一样,初始化我们的父Component,并提供方法,返回父Component,供Fragment使用。
然后是Fragment里

public class DemoFragment extends BaseFragment {
    @Inject
    Human human;
    @Override
    protected void processLogic(Bundle savedInstanceState) {
        GstudyActivityComponent gstudyActivityComponent = ((GstudyActivity) getActivity()).getGstudyActivityComponent();
        gstudyActivityComponent
            .demoFragmentComponent()
            .inject(this);
    }

}

这样就成功了,可以在Fragment使用human了。看明白了标题6,其实标题7原理是一样的。

大致理解和总结为( 看懂请略过 ):

  • 1、先建一个子类Component,用@subComponent标注,DemoFragmentComponent
  • 2、然后建父类Component: GstudyActivityComponent,定义个方法,返回子类Component。
  • 3、在GstudyActivity自定义一个方法把GstudyActivityComponent提供出去,供DemoFragment使用
  • 4、在DemoFragment,注册下,就好了。大致和dependencies类似
  • 注意:但注册的时候写法不同,之前是通过子Component传入父Component;而这里是从父Component中获取子Component,然后直接inject

8、Component依赖Component,使用 @Subcomponent.Builder(和【标题6】&【标题7】实现的是一样的效果)

效果一样,方式不同,目的还是更了解Dagger2。可以看到这里的标注是@Subcomponent.Builder。所以和使用@Subcomponent类似。


好了,还是以上面的例子为例。这里需要改的是父Component和子Componet。这个时候我们不免想到@Component.Builder的用法。是不是一样呢。这个时候我只能说类似,但是又不一样。毕竟这里多了个sub。我们先看下@Component.Builder的用法,拷贝之前代码(不知道再去回顾下【标题4】)

@Component(modules = CstudyModule.class。)
public interface DstudyActivityComponent {
    void injectTo(DstudyActivity dstudyActivity);

    @Component.Builder
    interface Builder {
        Builder cstudyModule(CstudyModule cstudyModule);
        DstudyActivityComponent build();
    }
}


我们按照找个方式去写@Subcomponent.Builder。,@Subcomponent.Builder要使用肯定是在@Subcomponent下,毋庸置疑。首先发现没有modules = CstudyModule.class。被@Subcomponent取代了。没有Module我们就使用无参

@Subcomponent
public interface OtherFragmentComponent {
    void inject(OtherFragment otherFragment);
    @Subcomponent.Builder
    interface Builder {
        Builder noModule();    
        OtherFragmentComponent build();
    }
}

我先告诉告诉你运行结果把,运行结果报错了。

@Subcomponent.Builder types must have exactly one zero-arg method, and that method must return the @Subcomponent type. Already found: hstudyActivityModule()

报错信息如下:意思是不需要Build返回值方法,通过Already found: hstudyActivityModule()知道,已经发现了我们的Module。我们再想想这个标注的名称sub,不就是子Component继承父Componet吗。而且Dagger2内部已经默认了,所以这里没有Builder返回值方法。所以正确的子Component

@Subcomponent
public interface OtherFragmentComponent {
    void inject(OtherFragment otherFragment);
    @Subcomponent.Builder
    interface Builder {
        OtherFragmentComponent build();
    }
}


接下来是父Component,返回值当然是我们的Builder。

@Component(modules = HstudyActivityModule.class)
public interface HstudyActivityComponent {
    OtherFragmentComponent.Builder sonbuilder();
}

有人就疑惑了不可以返回子Component吗。我们假如此时返回子Component,我先告诉你运行报错,信息如下:

Components may not have factory methods for subcomponents that define a builder.

大概意思是:用了@Subcomponent.Builder的话,Component没有工厂模式方法去创建我们的子Component。好了,就这样,请原谅我的英语四级!!


Activity还是和之前一样,初始化我们的父Component,并通过方法返回。Fragment里使用依赖如下

public class OtherFragment extends BaseFragment {
    @Inject
    Human human;
    @Override
    protected void processLogic(Bundle savedInstanceState) {
        HstudyActivityComponent hstudyActivityComponent = ((HstudyActivity) getActivity()).getHstudyActivityComponent();
        hstudyActivityComponent.
                sonbuilder().build().inject(this);
    }

}

好了,绕来绕去,功能实现了!看到这里对Dagger2大致了解了吧。

由于在java里使用比较多。临时决定java分2篇。不然博客太长,也没人看。

本文github Demo地址

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