Andriod-Dagger2

作者: 薛之涛 | 来源:发表于2019-08-14 13:21 被阅读94次

参考资料:
https://www.jianshu.com/p/1d84ba23f4d2
https://mp.weixin.qq.com/s/lh3dgJK95cgbG-bUZfvbFA

听说好多开发者采用Dagger2+RxJava+Retrofit+mvp结构来进行开发,虽然Dagger2已经出来好久了,但一直没有进行总结,今天所以有必要总结一下Dagger2。

1.什么是Dagger2

Dagger中文意思是匕首,Dagger2是Dagger的第二个版本,之前的Dagger已经放弃维护了,Dagger2用官方的话来说就是:


翻译成中文就是:
Dagger2是Java和Android的快速依赖注入器,是依赖注入的编译时框架。它不使用反射或运行时字节码生成,在编译时进行所有分析,并生成纯Java源代码。一般的IOC框架都是通过反射来实现的,但Dagger2作为Android端的IOC框架,为了不影响性能,它是通过apt动态生成代码来实现的。其主要作用就是解耦和管理实例对象。

2.相关配置

我们先看看官网怎么说:

Android Gradle
// Add Dagger dependencies
dependencies {
compile 'com.google.dagger:dagger:2.x'
annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}
If you're using classes in dagger.android you'll also want to include:
compile 'com.google.dagger:dagger-android:2.x'
compile 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries
annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'

If you're using classes in dagger.android you'll also want to include:这句话的意思是:如果你要使用dagger.android里面的东西。你就需要添加dagger-android:xxx依赖。

2.1.为什么要添加dagger-android2.x依赖?

我们都知道Android应用使用Dagger最主要的困难就是一些Framework类(如Activity、Fragment)是由操作系统实例化的,而Dagger更好工作的前提是它可以构建所有的注入对象。所以,你只能在生命周期方法中进行成员变量注入,这样就会产生两个问题:

  • 代码冗余,会产生好多公用的代码,以后会很难维护。
  • 更重要的是,它要求注射类型(Activity)知道其注射器。 即使这是通过接口而不是具体类型完成的,它打破了依赖注入的核心原则:一个类不应该知道如何实现依赖注入。

虽然仅仅添加dagger-2x也可以实现依赖注入,但如果添加了dagger-android2.x的依赖就可以避免上面提到的问题了。

2.2 引入依赖:

//Dagger2相关依赖
implementation 'com.google.dagger:dagger:2.24'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
implementation 'com.google.dagger:dagger-android:2.24'
// if you use the support libraries
implementation 'com.google.dagger:dagger-android-support:2.24'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.24'

3.基本使用

Dagger主要是以下几个注入标签,我们来一一说明:

  • @Inject
    简介:如果在类上添加此依赖注入,Dagger 就会构造一个这个类的实例并满足他们的依赖。
    通过这个inject注解可以将依赖需求方对象送到Component类中,Component类就会根据依赖需求方对象中声明的依赖关系来注入依赖需求方对象中所需要的对象,注意:inject方法的参数不能用父类来接收,@Inject注解的字段不能是private和protected的
  • @Module
    简介:编写Module类时要在该类上声明@Module以表明该类是Module类,这样Dagger2才能识别,Modules 类里面的方法专门提供依赖,如返回你需要依赖的对象实例。
  • @Provide
    简介:在 modules 中我们定义的方法就用@Provide注解,作用是声明Module类中哪些方法是用来提供依赖对象的,当Component类需要依赖对象时,他就会根据返回值的类型来在有@Provides注解的方法中选择调用哪个方法。
  • @Component
    Components 从根本上来说就是一个注入器,也可以说是@Inject 和@Module 的桥梁,来连接@Inject 和@Module这两个部分。但@Component注解的作用可不是单单用来声明Component类,@Component注解有modules和dependencies两个属性,这两个属性的类型都是Class数组,modules的作用就是声明该Component含有哪几个Module;而dependencies属性则是声明Component类的依赖关系,这个我们之后详细讲解。
    不理解没关系,我们通过代码来说明一下:
    比如我们要做MainActivity类中调用选图类中的拍照方法takePhoto(),一般代码应该是这样的:
    PhotoUtil :选图工具类
public class PhotoUtil {
    private static final String TAG = "PhotoUtil";

    /**
     * 拍照
     * @param activity
     */
    public void takePhoto(Activity activity){
        //执行拍照的代码
        Log.d(TAG, "takePhoto:调用拍照方法");
    }
}

MainActivity直接调用:

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

        PhotoUtil photoUtil =new PhotoUtil();
        photoUtil.takePhoto(this);
    }

那我们看一个用Dagger2如何实现?

第一步:

首先我们要先创建一个Module,我们上面说了Module 类里面的方法专门提供返回对象依赖,所以我们定义一个类,用@Module 注解,这样 Dagger 在构造类的实例时候,就知道从哪里去找到需要的依赖。我们取名为PhotoMudule.
相关代码:

@Module //Module 注解类里面的方法专门提供依赖,所以我们定义一个类,用@Module 注解
public class PhotoMudule {


    //module类中要提供依赖的注解方法用@Provides注解声明,以此来告诉Dagger要构造对象并提供这些依赖
    @Provides
    public PhotoUtil  photoInstance (){
         return new PhotoUtil();
    }
}

接下来用在modules中,我们定义的方法用@Provides注解,以此来告诉Dagger我们想要构造对象并提供这些依赖,注意:用@Provides注解的方法必须是public,不然外部怎么访问。

第二步:

我们上面提到了Component说它的作用主要是桥梁,那我们现在就来把Component和Module关联,取名photoComponent,代码如下:

//通过@Component注解来绑定我们的PhotoMudule(从{}这个符号我们就可以看到,他可以同时依赖多个 module)
@Component(modules = {PhotoMudule.class})
public interface photoComponent {

    //定义inject方法,参数是MainActivity,因为我们想在这个类中使用我们实例PhotoUtil
    void inject(MainActivity mainActivity);
}

注意:Component注解的类是一个接口
注解写的都很明白,这样 module 和 MainActivity 通过 Component 就关联起来了,切记我们还要执行以下Rebuild Progect,。

image.png
在执行Rebuild Progect之后,会生成一个DaggerPhotoComponet 类,这个类命名是以 Dagger 开头接上我们 PhotoComponet 类名。此类的主要作用就是将我们的 MainActivity 和 Component通过我们定义的inject()方法传参Activity关联起来,我们接下来的调用也是调用这个生成的DaggerPhotoComponet类:

在MainActivity直接调用:

@Inject
    PhotoUtil photoUtil;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //一般写法
//        PhotoUtil photoUtil =new PhotoUtil();
//        photoUtil.takePhoto(this);
        //加入dagger2写法
       DaggerPhotoComponent.create().inject(MainActivity.this);
        photoUtil.takePhoto(MainActivity.this);

    }

注意 这里我们 @Inject注解的对象不能用 privite 修饰。在我们的想要的创建的对象上加@Inject 注解并且调用 DaggerPhotoComponet.create().inject(this)关联; 后我们就可以拿到这个对象的实例了。
结果打印:


image.png

可以看到我们调用相关方法成功,但我们要思考一个问题,这样并不能很好的解耦,什么意思呢,我们来思考个问题?

  • 我们只是简单的调用了PhotoUtil中的 takePhoto方法,那万一我们还要调用其它类中的其他方法该怎么写呢?
    答:我们需要创建一个管理类,来管理我们的相关类,我们就叫做PhotoClsMannger吧
public class PhotoClsManager {

    PhotoUtil photoUtil;
    Activity mActivity;

    public PhotoClsManager(Activity activity,PhotoUtil photoUtil) {
        this.photoUtil = photoUtil;
        this.mActivity=activity;
    }

    public void startMethod(){
        //执行拍照方法
        photoUtil.takePhoto(mActivity);
    }

}

假如我们调用完拍照还需要调用裁剪方法,我们先创建相关类:

public class PhotoTailor {
    private static final String TAG = "PhotoTailor";
    
    /**
     * 
     * @param photoUrl
     */
    public void photoTailor(String photoUrl){
        //执行裁剪的代码
        Log.d(TAG, "photoTailor:调用裁剪相关方法传入参数+"+photoUrl);
    }
}

那么我们的管理类就要做相关的变动,加入裁剪类相关代码:

public class PhotoClsManager {

    PhotoUtil photoUtil;
    Activity mActivity;
    
    PhotoTailor photoTailor;
    String photoUrl;

    public PhotoClsManager(Activity activity,PhotoUtil photoUtil,PhotoTailor photoTailor,String photoUrl) {
        this.photoUtil = photoUtil;
        this.mActivity=activity;
        this.photoTailor = photoTailor;
        this.photoUrl=photoUrl;
        
    }

    public void startMethod(){
        //执行拍照方法
        photoUtil.takePhoto(mActivity);
        //执行裁剪相关方法
        photoTailor.photoTailor(photoUrl);
    }

}

PhotoClsManager这么写也有个问题,如果PhotoClsManager中的类需要好多参数,那我岂不是要在PhotoClsManager中的构造方法中写好多参数,我们说了PhotoClsManager只是管理相关类,并不负责类中方法需要的参数,那么这些参数要怎么传递呢?通常我们都是在PhotoClsManager管理类中的具体类的构造方法中执行传参操作,那么上面的代码就可以被我们改为:

public class PhotoUtil {
    private static final String TAG = "PhotoUtil";
    
    Context context;

    public PhotoUtil(Context context) {
        this.context = context;
    }

    /**
     * 拍照
     */
    public void takePhoto(){
        //执行拍照的代码
        Log.d(TAG, "takePhoto:调用拍照方法,接受的参数是:"+context);
    }
}

public class PhotoTailor {
    private static final String TAG = "PhotoTailor";
    
    String photoUrl ;

    public PhotoTailor(String photoUrl) {
        this.photoUrl = photoUrl;
    }

    /**
     *  裁剪方法
     */
    public void photoTailor(){
        //执行裁剪的代码
        Log.d(TAG, "photoTailor:调用裁剪相关方法传入参数+"+photoUrl);
    }
}

那么PhotoClsManager中的代码也需要调整了

public class PhotoClsManager {

    PhotoUtil photoUtil;
    PhotoTailor photoTailor;

    public PhotoClsManager(PhotoUtil photoUtil,PhotoTailor photoTailor) {
        this.photoUtil = photoUtil;
        this.photoTailor = photoTailor;
    }

    public void startMethod(){
        //执行拍照方法
        photoUtil.takePhoto();
        //执行裁剪相关方法
        photoTailor.photoTailor();
    }

}

那我们来看看PhotoMudule类中要怎么写:
首先我们在PhotoMudule中传入需要的参数,要通过PhotoMudule的构造参数来写:

@Module //Module 注解类里面的方法专门提供依赖,所以我们定义一个类,用@Module 注解
public class PhotoMudule {
    Context context;
    String photoUrl;

    public PhotoMudule(Context context, String photoUrl) {
        this.context = context;
        this.photoUrl = photoUrl;
    }

    //module类中要提供依赖的注解方法用@Provides注解声明,以此来告诉Dagger要构造对象并提供这些依赖
    @Provides
    public PhotoUtil  photoInstance (){
         return new PhotoUtil(context);
    }

    @Provides
    public PhotoTailor  photoTailorInstance (){
        return new PhotoTailor(photoUrl);
    }


    @Provides
    public PhotoClsManager providePhotoClsManager(PhotoUtil photoUtil, PhotoTailor photoTailor) {
        return new PhotoClsManager(photoUtil, photoTailor);
    }

MainActivity调用:


image.png

执行结果:


image.png

另一种写法

假设我们拍照之后裁剪之后还需要上传,所以我们创建一个上传类

public class PhotoUp {

    @Inject
    public PhotoUp() {
    }

    @Override
    public String toString() {
        return "调用了图片上传方法";
    }
    
}

这次我们创建的方式和之前有点不一样了,我们直接在构造函数上声明了@Inject注解,这个注解有什么用呢?当Component在所拥有的Module类中找不到依赖需求方需要类型的提供方法时,Dagger2就会检查该需要类型的有没有用@Inject声明的构造方法,有则用该构造方法创建一个,注意:这次我没是没有在Module类中写返回PhotoUp 类实例的方法的。
调用:


    //@Inject
    //PhotoUtil photoUtil;
    @Inject
    PhotoClsManager photoClsManager;
    @Inject
     PhotoUp photoUp;

    String photoUrl ="android/xxx.com/1123.jpg";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //一般写法
//        PhotoUtil photoUtil =new PhotoUtil();
//        photoUtil.takePhoto(this);
        //加入dagger2写法
//        DaggerPhotoComponent.create().inject(MainActivity.this);
//        photoUtil.takePhoto(MainActivity.this);
         DaggerPhotoComponent.builder().photoMudule(new PhotoMudule(MainActivity.this,photoUrl))
         .build().inject(this);
         photoClsManager.startMethod();
        Log.d(TAG, "onCreate:"+photoUp);
    }

结果:


image.png

那么又有小伙伴要问了,后一种直接在构造方法上添加 @Inject注解要方便好多,为什么还需要@Module注解呢?
答:
项目中我们会用到别人的jar包,我们无法修改别人的源码,就更别说在人家的类上添加注解了,所以我们只能通过Module类来提供

总结:
我们有两种方式可以提供依赖,一个是注解了@Inject的构造方法,一个是在Module里提供的依赖,规则是这样的:查找Module中是否存在创建该类的方法,如果没有则查找Inject注解的构造函数

4.高级使用

说完了基本使用,我们来看看Dagger2高级使用的注解

  • @Named和@Qualifier: 要作用是用来区分不同对象实例
    @Named 其实是@Qualifier 的一种实现
    **@Qulifier是自定义注解用的,可以替代@Named **
  • @Singleton和@Scope
    **Singleton其实是继承@Scope注解的 **
    @Scope和@Qulifier一样,需要我们自定义注解才能使用
  • Subcomponent
  • Lazy与Provider

4.1 @Named和@Qulifier注解的使用

4.1.1 @Named

上面我们模拟了图片的选取-裁剪-上传通过注解如何获取依赖对象,然后调用相关方法,那么现在假设我要在图片本身做分类,将图片类型分为人物,景色等呢?
首先我们先创建Photo类:

public class Photo {
    
    String photoType;

    public String getPhotoType() {
        return photoType;
    }

    public void setPhotoType(String photoType) {
        this.photoType = photoType;
    }

    @Override
    public String toString() {
        return"选取了"+photoType;
    }
}

然后在我们的Module类PhotoMudule中增加两个获取不同类别照片的方法:

 /**
     * 获取人物图片对象
     * @return
     */
    @Provides
    public Photo getfigurePhoto(){
        Photo photo = new Photo("人物图片");
        return photo;
    }

    /**
     * 获取景物图片对象
     * @return
     */
    @Provides
    public Photo getsceneryPhoto(){
        Photo photo = new Photo("景物图片");
        return photo;
    }

这个时候我们的问题就来了,Dagger2是通过返回值类型来确定的,当你需要人物图片时,它又怎么知道哪个是人物图片呢?
答:还好有@Named注解。
@Named注解有一个value值,用来标识这个方法是给谁用的.修改我们的代码:

  /**
     * 获取人物图片对象
     * @return
     */
    @Provides
    @Named("figure")
    public Photo getfigurePhoto(){
        Photo photo = new Photo("人物图片");
        return photo;
    }

    /**
     * 获取景物图片对象
     * @return
     */
    @Provides
    @Named("scenery")
    public Photo getsceneryPhoto(){
        Photo photo = new Photo("景物图片");
        return photo;
    }

调用:注意在 Moudle 用了@Named 标签,在调用时也需要加上@Named 标签

  @Inject
  @Named(value = "figure")
  Photo figurePhoto;
  @Inject
  @Named(value = "scenery")
  Photo sceneryPhoto;
//省略:xxxx
DaggerPhotoComponent.builder().photoMudule(new PhotoMudule(MainActivity.this,photoUrl))
                .build().inject(this);
        Log.d(TAG, "figurePhoto:"+figurePhoto);
        Log.d(TAG, "sceneryPhoto:"+sceneryPhoto);

结果:


image.png
4.1.2@Qulifier

@Qulifier功能和@Named一样,并且@Named就是继承@Qulifier的,没错,@Qulifier就是自定义注解用的。接下来我们定义一个@CustomeQualifier来替代@Named:



   
    //自定义注解名为:CustomeQualifier
  //@Qualifier :注明是Qualifier(关键词)
 // @Documented :标记在文档(可不写)
 // @Retention(RUNTIME) :运行时级别 
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CustomeQualifier {
        String value() default "";
    }
  /**
     * 采用Qualifier自定义注解实现标记区分
     * @return
     */
    @Provides
    @CustomeQualifier("scenery")
    public Photo customeQualifierPhoto(){
        Photo photo = new Photo("景物图片");
        return photo;
    }
   
   //执行相关代码
    @Inject
    @PhotoMudule.CustomeQualifier(value = "scenery")
    Photo customeQualifier;
     Log.d(TAG, "customeQualifier:"+customeQualifier);

4.2 @Singleton和@Scope的使用

学习@Singleton注解之前,我们先想这么一个需求,有没有一个依赖类以另外一个类作为依赖类呢?比如,我的裁剪对象需要一个图片对象,我们先在PhotoMudule中写一下相关代码:

 @Provides
    @Named("phototailorbyphoto")
    public PhotoTailor  photoTailorByPhoto (){
        Photo photo =new Photo("景物图片");
        return new PhotoTailor(photo);
    }

但是这么写有点麻烦,Dagger2提供了这样的功能,我们只要在photoTailorByPhoto 方法中添加Photo 参数,Dagger2就会像帮依赖需求方找依赖对象一样帮你找到该方法依赖的Photo 实例,所以我们代码可以这样改:

 @Provides
    public Photo getPhoto(){
        return  new Photo("景物图片");
    }

  @Provides
    @Named("phototailorbyphoto")
    public PhotoTailor  photoTailorByPhoto (Photo photo){
        return new PhotoTailor(photo);
    }

打印:


image.png
4.2.1@Singleton

关于@Singleton ,我们需要接着上面的例子看:

  @Provides
    public Photo getPhoto(){
        return  new Photo("景物图片");
    }

    @Provides
    @Named("phototailorbyphoto")
    public PhotoTailor  photoTailorByPhoto (Photo photo){
        return new PhotoTailor(photo);
    }
   @Inject
    Photo photo;
    @Inject
    @Named("phototailorbyphoto")
    PhotoTailor photoTailor;

        Log.d(TAG, "photo:" + photo);
        Log.d(TAG, "photoTailor:" + photoTailor);
        Log.d(TAG, "photo和photoTailor.getPhoto()是一个photo吗?" + (photoTailor.getPhoto() == photo));

打印:


image.png

为什么会出现这种情况呢?
答:,注入过程中,对Photo注入时会调用一次getPhoto方法,创建了一个Photo对象;注入PhotoTailor 时又会调用一次getPhoto方法,这时又会创建一个Cloth对象,所以才会出现上面的结果,那如果我们想Photo和PhotoTailor 中的是一个Photo改怎么办呢?这就要用到@Singleton注解了,顾名思义@Singleton字面意思就是声明单例模式。怎么用呢?改两个地方:Module中获取实例的方法和Component都加上@Singleton,代码如下:

   @Singleton
    @Provides
    public Photo getPhoto(){
        return  new Photo("景物图片");
    }

@Singleton
@Component(modules = {PhotoMudule.class})
public interface PhotoComponent {

    //定义inject方法,参数是MainActivity,因为我们想在这个类中使用我们实例PhotoUtil
    void inject(MainActivity mainActivity);
}

@Singleton在使用时调用处正常书写:

   @Inject
    Photo photo;
    @Inject
    PhotoToTailor photoToTailor;
    Log.d(TAG, "photo:" + photo);
    Log.d(TAG, "photoTailor:" + photoTailor);
    Log.d(TAG, "photo和photoTailor.getPhoto()是一个photo吗?" + (photoTailor.getPhoto() == photo));

打印:


image.png

小总:

  • module 的 provide 方法使用了 scope ,那么 component 就必须使用同一个注解
  • @Singleton 的生命周期依附于 component,也就是说同一个 module 被不同的@Component 依赖获取的实例不会是同一个, @Singleton属于 Activity级别单例。那如果我想定义为全局的生命周期单例该怎么办呢?我们稍后会将,别急。
4.2.2Scope

@Scope就是用来声明作用范围的.@Scope和@Qulifier一样,需要我们自定义注解才能使用,我们先自定义一个注解:

//定义@Scope注解名为:CustomeScope
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CustomeScope {
    }

这个自定义注解的作用是:声明作用范围,当我们将这个注解使用在Module类中的Provide方法上时,就是声明这个Provide方法是在CustomeScope 作用范围内的,并且当一个Component要引用这个Module时,必须也要声明这个Component是CustomeScope 作用范围内的,否则就会报错,声明方法也很简单,就是在Component接口上使用这个注解。那我们把之前的@Singleton替换一下看看效果:

 //@Singleton
    @CustomeScope
    @Provides
    public Photo getPhoto(){
        return  new Photo("景物图片");
    }

 //定义@Scope注解名为:CustomeScope,作用相当于@Singleton
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CustomeScope {
        String value() default "";
    }

 //@Singleton
   @PhotoMudule.CustomeScope
   @Component(modules = {PhotoMudule.class})
   public interface PhotoComponent {
    //定义inject方法,参数是MainActivity,因为我们想在这个类中使用我们实例PhotoUtil
    void inject(MainActivity mainActivity);
    }
//调用执行:
   @Inject
    Photo photo;
    @Inject
    @Named("phototailorbyphoto")
    PhotoTailor photoTailor;

    Log.d(TAG, "photo:" + photo);
    Log.d(TAG, "photoTailor:" + photoTailor);
    Log.d(TAG, "photo和photoTailor.getPhoto()是一个photo吗?" + (photoTailor.getPhoto() == photo));

打印:


image.png

那么为什么这么做也可以实现Activity级别的单例效果呢?
答:
Dagger2有这样一个机制:在同一个作用范围内,Provide方法提供的依赖对象就会变成单例,也就是说依赖需求方不管依赖几次Provide方法提供的依赖对象,Dagger2都只会调用一次这个方法.

4.3 组件依赖dependencies的使用

我们刚才通过自定义@CustomeScope或者@Singleton的方法都实现了Photo对象是同一个,但现在我们要创建一个单例的工具类PhotoToTailor,目的是将我们的Photo通过该工具类直接转化为PohtoTailor,相关代码如下:
MainActivity相关代码:
创建PhotoToTailor类:

public class PhotoToTailor {
    
    public PhotoTailor getPhotoTailor(Photo photo){
        return new PhotoTailor(photo);
    }
}

//PhotoMudule中的代码:

   @Singleton
    @Provides
    public Photo getPhoto(){
        return  new Photo("景物图片");
    }

    @Provides
    @Named("phototailorbyphoto")
    public PhotoTailor  photoTailorByPhoto (Photo photo){
        return new PhotoTailor(photo);
    }

    @Singleton
    @Provides
    public PhotoToTailor getPhotoToTailor(){
        return new PhotoToTailor();
    }

MainActivty界面代码:

    @Inject
    Photo photo;
    @Inject
    PhotoToTailor photoToTailor;
    TextView tvOne;

    DaggerPhotoComponent.builder().photoMudule(new PhotoMudule(MainActivity.this, photoUrl))
                .build().inject(this);
        tvOne.setText(photoToTailor.getPhotoTailor(photo) + "photoToTailor地址:" + photoToTailor);

  /**
     * 监听事件,跳转下一个页面
     * @param v
     */
    public void onClickListener(View v){
        Intent intent =new Intent(MainActivity.this,SecondActivity.class);
        startActivity(intent);

    }

结果展示:


image.png

SecondActivity类内容:

@Inject
    Photo photo;
    @Inject
    PhotoToTailor photoToTailor;
    TextView textView;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        textView =findViewById(R.id.textView);

        DaggerSecondActivity_SecondeComponent.builder().secondModule(new SecondModule())
                .build().inject(this);
        textView.setText(photoToTailor.getPhotoTailor(photo) + "photoToTailor地址:" + photoToTailor);

    }

    @Module
    public class SecondModule {

        public SecondModule() {
        }

        @Singleton
        @Provides
        public Photo getPhoto(){
            return  new Photo("人物图片");
        }

        @Provides
        @Named("phototailorbyphoto")
        public PhotoTailor  photoTailorByPhoto (Photo photo){
            return new PhotoTailor(photo);
        }

        @Singleton
        @Provides
        public PhotoToTailor getPhotoToTailor(){
            return new PhotoToTailor();
        }

    }

    @Singleton
    @Component(modules = {SecondModule.class})
    public interface SecondeComponent {
        void inject(SecondActivity secondActivity);
    }

结果展示:


image.png

我们来对比一下:


image.png
image.png

你会发现,虽然我们成功的将PhotoToTailor 注入到了这两个Activity中,但是你会发现,这两个Activity中的PhotoTailor 实例不是一样的,为什么我们的Singleton失效了?而且假如有多个Activity都需要用到PhotoToTailor ,我么就会产生好多冗余代码,怎么办呢?
在面向对象的思想中,我们碰到这种情况一般都要抽取父类,Dagger2也是用的这种思想,我们先创建一个BaseModule,用来提供工具类:

@Module
public class BaseModule {

    @Singleton //单例
    @Provides
    public PhotoToTailor getPhotoToTailor(){
        return new PhotoToTailor();
    }
}

在创建一个BaseComponent接口:

@Singleton //对应Module中声明的单例
@Component(modules = BaseModule.class)
public interface BaseComponent {
    
    //它的作用就是告诉依赖于BaseComponent的Component,BaseComponent能为你们提供PhotoToTailor对象
    PhotoToTailor getPhotoToTailor();
}

删除PhotoMudule和SecondModule中原有的photoTailorByPhoto方法:

@Module
public class PhotoMudule {
    @Singleton
    @Provides
    public Photo getPhoto(){
        return  new Photo("景物图片");
    }

@CustomeScope
    @Provides
    public PhotoToTailor getPhotoToTailor(){
        return new PhotoToTailor();
    }
}
@Module
    public class SecondModule {

        public SecondModule() {
        }

        @Singleton
        @Provides
        public Photo getPhoto(){
            return  new Photo("人物图片");
        }

     @CustomeScope
    @Provides
    public PhotoToTailor getPhotoToTailor(){
        return new PhotoToTailor();
    }

    }

接下来在MainComponent和SecondComponent中声明依赖,就要用到@Component中的dependencies属性了:

@Singleton
@Component(modules = PhotoMudule.class,dependencies = BaseComponent.class)
public interface PhotoComponent {

    //定义inject方法,参数是MainActivity,因为我们想在这个类中使用我们实例PhotoUtil
    void inject(MainActivity mainActivity);
}
 @Singleton
    @Component(modules = SecondModule.class,dependencies = BaseComponent.class)
    public interface SecondeComponent {
        void inject(SecondActivity secondActivity);
    }

执行Android Studio中build菜单下的Rebuild Object后,你会发现创建MainComponent和SecondComponent实例时多了一个baseComponent方法,

image.png
我们一般都会自定义一个Application类,用它来提供BaseComponent实例,因为在整个App生命周期内都只有一个Application实例,所以其中的BaseComponent实例也不会变.我们自定义一个BaseApplication类
public class BaseApplication extends Application {
    BaseComponent baseComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        baseComponent = DaggerBaseComponent.builder().baseModule(new BaseModule()).build();
    }
    
    public BaseComponent  getBaseComponent(){
        return baseComponent;
    }
}

继续修改MainActivity和SecondActivity的相关代码,注意我们在创建相关Component的时候多了.baseComponent()方法,其参数就是我们在Application中创建的BaseComponent对象,来确保全局单例

 DaggerPhotoComponent.builder()
                .baseComponent(((BaseApplication) getApplication()).getBaseComponent())
                .photoMudule(new PhotoMudule(MainActivity.this, photoUrl))
                .build()
                .inject(this);
        tvOne.setText(photoToTailor.getPhotoTailor(photo) + "photoToTailor地址:" + photoToTailor);
    DaggerSecondActivity_SecondeComponent.builder()
                .baseComponent(((BaseApplication)getApplication()).getBaseComponent())
                .secondModule(new SecondModule())
                .build()
                .inject(this);
        textView.setText(photoToTailor.getPhotoTailor(photo) + "photoToTailor地址:" + photoToTailor);

结果打印:


image.png image.png

这次获取的PhotoToTailor对象地址一致了,但有个细节不知道大家注意了没?如图:

image.png

我们的子组件PhotoComponent和父组件BaseComponent没有使用同一个单例注解关键字,子组件用的是自定义的@Scope为什么呢?原因是:Singleton 的组件不能依赖其他 scope 的组件,只能其他 scope 的组件可以依赖 Singleton的组件 。demo中我们的BaseComponent已经用@Singleton 修饰就不能再去依赖别的Component,但子别的 Component,即其他scope的组件 可以依赖其他组件。

4.4 Subcomponent

从注解关键字可以看出Subcomponent和Component是上下级关系,@Subcomponent注解的功能和Dependencies类似,但是使用方法有点不同,我们通过代码来看一下,首先定义一个@Subcomponent注解的子组件SubMainComponent :

@PhotoMudule.CustomeScope //单例
@Subcomponent(modules = PhotoMudule.class)//组件声明
public interface SubMainComponent {
    void inject(MainActivity mainActivity);
}

在父组件也就是BaseComponent中定义一个返回值为子组件的方法,当子组件需要什么Module时,就在该方法中添加该类型的参数:

@Singleton //对应Module中声明的单例
@Component(modules = BaseModule.class)
public interface BaseComponent {
    //getPhotoToTailorgetClothHandler方法,PhotoToTailor
    //它的作用就是告诉依赖于BaseComponent的Component,BaseComponent能为你们提供PhotoToTailor对象

    PhotoToTailor getPhotoToTailor();

   //定义一个返回值为子组件的方法,当子组件需要什么Module时,就在该方法中添加该类型的参数
    SubMainComponent getSubMainComponent(PhotoMudule module);
}

在MainActivity中替代原来的PhotoComponent看:

  ((BaseApplication) getApplication()).getBaseComponent()
                .getSubMainComponent(new PhotoMudule(MainActivity.this, photoUrl))
                .inject(this);
        tvOne.setText(photoToTailor.getPhotoTailor(photo) + "photoToTailor地址:" + photoToTailor);

效果查看:


image.png image.png

首先我们先创建BaseComponent,他属于App级别的。我们BaseApplication创建它。BaseComponent中调用者提供SubMainComponent 。这和我们之前使用有些不同。
总结一下@Subcomponent的使用:

子组件的声明方式由@Component改为@Subcomponent
在父组件中要声明一个返回值为子组件的方法,当子组件需要什么Module时,就在该方法中添加该类型的参数
注意:用@Subcomponent注解声明的Component是无法单独使用的,想要获取该Component实例必须经过其父组件

4.5 Lazy与Provider

Lazy和Provider都是用于包装Container中需要被注入的类型,Lazy用于延迟加载,所谓的懒加载就是当你需要用到该依赖对象时,Dagger2才帮你去获取一个;Provide用于强制重新加载,也就是每一要用到依赖对象时,Dagger2都会帮你依赖注入一次。
相关代码:
PhotoMudule中获取人物或者景物图片的方法


    /**
     * 获取人物图片对象
     * @return
     */
    @Provides
    @Named("figure")
    public Photo getfigurePhoto(){
        Log.d(TAG, "调用了module中的getfigurePhoto方法");

        Photo photo = new Photo("人物图片");
        return photo;
    }

    /**
     * 获取景物图片对象
     * @return
     */
    @Provides
    @Named("scenery")
    public Photo getsceneryPhoto(){
        Log.d(TAG, "调用了module中的getsceneryPhoto方法");

        Photo photo = new Photo("景物图片");
        return photo;
    }

MainActivity中的调用:

//Lazy声明方式
@Inject
@Named(value = "figure")
Lazy<Photo> figurePhoto;
//Provider声明方式
@Inject
@Named(value = "scenery")
Provider<Photo> sceneryPhoto;

PhotoComponent photoComponent =DaggerPhotoComponent.builder().baseComponent(((BaseApplication) getApplication()).getBaseComponent())
               .photoMudule(new PhotoMudule(MainActivity.this, photoUrl))
               .build();
        photoComponent.inject(MainActivity.this);

        Log.d(TAG, "figurePhoto:"+figurePhoto.get());
        Log.d(TAG, "sceneryPhoto:"+sceneryPhoto.get());
        Log.d(TAG, "figurePhoto:"+figurePhoto.get());
        Log.d(TAG, "sceneryPhoto:"+sceneryPhoto.get());

打印:


image.png

注意我们上面是没有声明作用域内单例的,我们现在在PhotoModule中加一下看看:

/**
     * 获取人物图片对象
     * @return
     */
    @CustomeScope //这里声明作用域内单例
    @Provides
    @Named("figure")
    public Photo getfigurePhoto(){
        Log.d(TAG, "调用了module中的getfigurePhoto方法");

        Photo photo = new Photo("人物图片");
        return photo;
    }

    /**
     * 获取景物图片对象
     * @return
     */
    @CustomeScope //这里声明作用域内单例
    @Provides
    @Named("scenery")
    public Photo getsceneryPhoto(){
        Log.d(TAG, "调用了module中的getsceneryPhoto方法");

        Photo photo = new Photo("景物图片");
        return photo;
    }

打印:


image.png

总结:
声明单例后,使用getsceneryPhoto()时也不会每次都去调用module中的方法了,这是因为Provider的作用是每次使用时都对依赖对象重新注入,但是getsceneryPhoto()在Component中是单例的,所以每次注入的都是同一个实例,所以只会调用一次module中的方法。

demo 已上传github
Dagger2基本就这么多,完毕!

相关文章

  • Andriod-Dagger2

    参考资料:https://www.jianshu.com/p/1d84ba23f4d2https://mp.wei...

网友评论

    本文标题:Andriod-Dagger2

    本文链接:https://www.haomeiwen.com/subject/nhvljctx.html