依赖注入概念和Dagger2框架使用介绍

一、 依赖注入基本概念

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。

依赖注入(Dependency Injection)是指程序运行时,若需要调用另一个对象,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序,依赖注入是目前最佳的解耦方式。

如果一个类A的功能实现需要借助于类B,那么就称类B是类A的依赖。如果在类A的内部去实例化类B,那么两者之间会出现较高的耦合,一旦类B出现了问题,类A也需要进行改造,如果这样的情况较多,每个类之间都有很多依赖,那么就会出现牵一发而动全身的情况,程序会极难维护,并且很容易出现问题。

要解决这个问题,就要把A类对B类的控制权抽离出来,交给一个第三方去做,把控制权反转给第三方就称作控制反转。控制反转是一种思想,是能够解决问题的一种可能的结果。
依赖注入(Dependency Injection)就是其最典型的实现方法。由第三方(我们称作IOC容器)来控制依赖,把他通过构造函数、属性或者工厂模式等方法,注入到类A内,这样就极大程度的对类A和类B进行了解耦。

二、 如何使用Dagger2

Dagger2的使用比较复杂,先来看最简单的用法,主要分成三步:

  • 使用@Inject注解需要生成的目标对象和其构造方法;
  • 创建Component接口,作用是将产生的对象注入到需要对象的容器中;
  • 容器调用Component 的creat()和inject()方法将产生的对象注入到自己体内。
public class Company {

    @Inject
    public Company() {
    }
}

注意:构造方法需要使用@Inject注解修饰

MainActivity使用@Inject注解myCompany:

1.  public class MainActivity extends AppCompatActivity {  
2.    
3.      private static final String TAG = "Dagger2_Test";  
4.      @Inject  
5.      public Company myCompany;  
6.    
7.      @Override  
8.      protected void onCreate(Bundle savedInstanceState) {  
9.          super.onCreate(savedInstanceState);  
10.         setContentView(R.layout.activity_main);  
11.         //onCreate()中添加如下语句:  
12.         DaggerDagger2TestComponent.create()  
13.                 .inject(this);  
14.         Log.d(TAG,"myCompany = " + myCompany.toString());  
15.     }  
16. }

添加Component注解:

@Component
public interface Dagger2TestComponent {
    void inject(MainActivitymainActivity);
}

运行结果,看到myCompany已经实例化成功:

Dagger2_Test: myCompany = com.example.xxx.dagger2ver.Company@50e3e70

三、Dagger2 的原理介绍

Dagger2的原理主要分析自动生成代码的逻辑,通过<Java注解原理详细介绍>介绍,已经了解注解的基本使用,Dagger2注解处理器会通过@Inject和@Component会自动生成如下三个java文件:
Company_Factory.java

1.  public final class Company_Factory implements Factory<Company> {  
2.    private static final Company_Factory INSTANCE = new Company_Factory();  
3.    
4.    @Override  
5.    public Company get() {  
6.      return provideInstance();  
7.    }  
8.    
9.    public static Company provideInstance() {  
10.     return new Company();  
11.   }  
12.   
13.   public static Company_Factory create() {  
14.     return INSTANCE;  
15.   }  
16.   
17.   public static Company newCompany() {  
18.     return new Company();  
19.   }  
20. }  

MainActivity_MembersInjector.java:

1.  public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {  
2.    private final Provider<Company> myCompanyProvider;  
3.    
4.    public MainActivity_MembersInjector(Provider<Company> myCompanyProvider) {  
5.      this.myCompanyProvider = myCompanyProvider;  
6.    }  
7.    
8.    public static MembersInjector<MainActivity> create(Provider<Company> myCompanyProvider) {  
9.      return new MainActivity_MembersInjector(myCompanyProvider);  
10.   }  
11.   
12.   @Override  
13.   public void injectMembers(MainActivity instance) {  
14.     injectMyCompany(instance, myCompanyProvider.get());  
15.   }  
16.   
17.   public static void injectMyCompany(MainActivity instance, Company myCompany) {  
18.     instance.myCompany = myCompany;  
19.   }  
20. }  

DaggerDagger2TestComponent.java:

1.  public final class DaggerDagger2TestComponent implements Dagger2TestComponent {  
2.    private DaggerDagger2TestComponent(Builder builder) {}  
3.    
4.    public static Builder builder() {  
5.      return new Builder();  
6.    }  
7.    
8.    public static Dagger2TestComponent create() {  
9.      return new Builder().build();  
10.   }  
11.   
12.   @Override  
13.   public void inject(MainActivity mainActivity) {  
14.     injectMainActivity(mainActivity);  
15.   }  
16.   
17.   private MainActivity injectMainActivity(MainActivity instance) {  
18.     MainActivity_MembersInjector.injectMyCompany(instance, new Company());  
19.     return instance;  
20.   }  
21.   
22.   public static final class Builder {  
23.     private Builder() {}  
24.   
25.     public Dagger2TestComponent build() {  
26.       return new DaggerDagger2TestComponent(this);  
27.     }  
28.   }  
29. }  

四、Dagger2 详细使用介绍

目标对象构造方法带参数

上面是介绍通过Daggers框架的@Inject和@Component 注解,目标对象的简单方法。
现在如果Company构造方法需要传参,该如何实现呢?

1.  public class Company {  
2.      private String mName = null;  
3.    
4.      public Company(String name) {  
5.          this.mName = name;  
6.      }
7.  }

答案是通过Dagger2提供的@Module注解,@Module注解的作用是提供Component需要的依赖。
创建Dagger2TestModule01.java

1.  @Module  
2.  public class Dagger2TestModule01 {  
3.    
4.      @Provides  
5.      public Company provideCompany(){  
6.          return  new Company("google");  
7.      }  
8.  }

此时查看generated文件夹中自动生成的类DaggerDagger2TestComponent.java:

1.  DaggerDagger2TestComponent.java  
2.    @Override  
3.    public void inject(MainActivity activity) {  
4.      injectMainActivity(activity);  
5.    }  
6.    
7.    private MainActivity injectMainActivity(MainActivity instance) {  
8.      MainActivity_MembersInjector.injectMyCompany(  
9.          instance,    /*MainActivity对象*/  
10.         /*需要注入的company对象*/  
11.        Dagger2TestModule01_ProvideCompanyFactory.proxyProvideCompany(dagger2TestModule01));  
12.     return instance;  
13.   }  
14.   
15.   public static final class Builder {  
16.     private Dagger2TestModule01 dagger2TestModule01;  
17.   
18.     private Builder() {}  
19.   
20.     public Dagger2TestComponent build() {  
21.       if (dagger2TestModule01 == null) {  
22.         this.dagger2TestModule01 = new Dagger2TestModule01();  
23.       }  
24.       return new DaggerDagger2TestComponent(this);  
25.     }  
26.   
27.     public Builder dagger2TestModule01(Dagger2TestModule01 dagger2TestModule01) {  
28.       this.dagger2TestModule01 = Preconditions.checkNotNull(dagger2TestModule01);  
29.       return this;  
30.     }  
31.   } 

Dagger2TestModule01_ProvideCompanyFactory.java

1.  public final class Dagger2TestModule01_ProvideCompanyFactory implements Factory<Company> {  
2.    private final Dagger2TestModule01 module;  
3.    
4.    public Dagger2TestModule01_ProvideCompanyFactory(Dagger2TestModule01 module) {  
5.      this.module = module;  
6.    }  
7.    
8.    @Override  
9.    public Company get() {  
10.     return provideInstance(module);  
11.   }  
12.   
13.   public static Company provideInstance(Dagger2TestModule01 module) {  
14.     return proxyProvideCompany(module);  
15.   }  
16.   
17.   public static Dagger2TestModule01_ProvideCompanyFactory create(Dagger2TestModule01 module) {  
18.     return new Dagger2TestModule01_ProvideCompanyFactory(module);  
19.   }  
20.   
21.   public static Company proxyProvideCompany(Dagger2TestModule01 instance) {  
22.     return Preconditions.checkNotNull(  
23.         //这里就会调用到Dagger2TestModule01添加的provideCompany()方法  
24.         instance.provideCompany(), "Cannot return null from a non-@Nullable @Provides method");  
25.   }  
26. }  

Module中方法带参数

上一节是构造方法带参数,如果是Module中提供目标对象的方法需要传参,该如何处理?
如下情形:

1.  @Module  
2.  public class Dagger2TestModule01 {  
3.  @Provides  
4.  //provideCompany()方法需要传入company name的参数  
5.  public Company provideCompany(String name){  
6.      return  new Company(name);  
7.  }  
8.  
9.  @Provides  
10. //在Module中提供一个返回值为String的方法  
11. public String companyName(){  
12.     return "facebook";  
13. }  
14. } 

Dagger在Module中执行provideCompany()时,发现需要一个String类型的参数,接着Dagger就会在Module中搜索返回值为String的方法并执行。

@Named注解的使用

如果现在Company中有两个构造方法,MainActivity需要生成两个不同的对象,该如何实现呢?

1.  public Company(String name) {  
2.      this.mName = name;  
3.  }  
4.    
5.  public Company(String name, int number) {  
6.      this.mName = name;  
7.      this.mNumber = number;  
8.  }  

方法是通过@Named注解,在Dagger2TestModule01中定义两个不同的方法,来返回不同的Company对象,并且加上@Named("TypeX")区分:

1.  @Module  
2.  public class Dagger2TestModule01 {  
3.    
4.      @Named("Type1")  
5.      @Provides  
6.      public Company provideCompany(){  
7.          return  new Company("Google");  
8.      }  
9.        
10.     @Named("Type2")  
11.     @Provides  
12.     public Company provideCompany2(){  
13.         return  new Company("Facebook",20000);  
14.     }  
15. } 

MainActivity中也通过@Named("TypeX")来声明不同的Company对象:

1.  @Named("Type1")  
2.  @Inject  
3.  public Company myCompany;  
4.    
5.  @Named("Type2")  
6.  @Inject  
7.  public Company myCompany2;  

局部单例和全局单例

开发中,经常会遇到创建Activity/Fragment级单例或者Application级单例的情形,Activity/Fragment级单例一般称为局部单例,Application级单例称为全局单例。
通过@Scope注解,就可以实现此功能,@Scope可以管理所创建对象的「生命周期」,这里的「生命周期」是与Component 相关联,与Activity等任何Android组件没有任何关系。

局部单例

  1. 首先,声明一个自定义@Scope注解:
1.  ActivityScope.java  
2.  @Scope  
3.  @Retention(RUNTIME)  
4.  public @interface ActivityScope {  
5.  } 
  1. MainActivity中新增两departmen对象,并用@ActivityScope 来修饰,用来和Company对象做对比验证:
    MainActivity.java
1.  @Inject public Company myCompany;  
2.  @Inject public Company myCompany3;  
3.    
4.  @Inject public Department de1;  
5.  @Inject public Department de2;  
6.  @Provides  
7.  public Company provideCompany(){  
8.      return  new Company();  
9.  }  
10.   
11. @ActivityScope  
12. @Provides  
13. public Department provideDepartment(){  
14.     return  new Department();  
15. } 
  1. Dagger2TestComponent 也添加@ActivityScope 修饰:
1.  @ActivityScope  
2.  @Component(modules = Dagger2TestModule01.class)  
3.  public interface Dagger2TestComponent {  
4.      void inject(MainActivity activity);  
5.  }

查看运行结果:

可以看到,两个company对象不同,但两个department是同一对象,通过上面的代码发现,只是增添了@ActivityScope注解就实现了Activity中的局部单例。

全局单例

新增SubActivity,以及其Module和Component,SubActivity中也声明Department对象,用来验证是否全局单例:
SubActivity.java

1.  @Inject public Department de1;

通过log打印发现SubActivity和MainActivity的department并不是同一对象,原因是因为两者的Component不同,现在要实现全局单例应该怎么做呢?

如上文提到,因为「生命周期」是与Component相关联,所以如果要实现全局单例,就应该创建全局的Component,一个应用中只有Application对象是全局唯一的,我们从这里入手。
先定义全局Module和Component:

1.  @Module  
2.  public class ApplicaionModule {  
3.    
4.      private MyApplication application;  
5.    
6.      public ApplicaionModule(MyApplication application) {  
7.          this.application = application;  
8.      }  
9.    
10.     @Singleton  
11.     @Provides  
12.     Company provideCompany(){  
13.         return new Company();  
14.     }  
15. }
1.  @Singleton //Dagger2提供的单例注解
2.  @Component (modules = ApplicaionModule.class)  
3.  public interface ApplicationComponent {  
4.      //这里告知需要返回的全局单例对象,不然会报错  
5.      Company getCompany();  
6.  } 

新建ComponentHolder.java,给各个Activity/Fragment提供全局唯一ApplicationComponent:

1.  public class ComponentHolder {  
2.      private static ApplicationComponent myAppComponent;  
3.    
4.      public static void setAppComponent(ApplicationComponent component) {  
5.          myAppComponent = component;  
6.      }  
7.    
8.      public static ApplicationComponent getAppComponent() {  
9.          return myAppComponent;  
10.     }  
11. }
12.   
13. public class MyApplication extends Application {  
14.     private static final String TAG = "Dagger2_Test";  
15.   
16.     @Override  
17.     public void onCreate() {  
18.         super.onCreate();  
19.         //生成ApplicationComponent对象,并设置到ComponentHolder中,供Activity获取  
20.         ApplicationComponent appComponent = DaggerApplicationComponent.builder()  
21.                 .applicaionModule(new ApplicaionModule(this))  
22.                 .build();  
23.         ComponentHolder.setAppComponent(appComponent);  
24.     }  
25. }

修改Dagger2TestModule01.java和Dagger2TestModule02.java,注释掉其中返回company对象的方法,因为已经通过ApplicaionModule.java来提供:

1.  @Module  
2.  public class Dagger2TestModule01 {  
3.  //    @ActivityScope  
4.  //    @Provides  
5.  //    public Company provideCompany(){  
6.  //        return  new Company();  
7.  //    }  
8.  }  
9.  @Module  
10. public class ModuleSubActivity {  
11.   //同上  
12. } 

因为现在依赖ApplicationComponent提供目标对象,所以要在MainActivity和SubActivity对应的Component中添加@ActivityScope和dependencies = ApplicationComponent.class:

1.  @ActivityScope  
2.  @Component(modules = Dagger2TestModule01.class, dependencies = ApplicationComponent.class) 
3.  public interface Dagger2TestComponent {  
4.      void inject(MainActivity activity);  
5.  } 

MainActivity.java 和SubActivity.java 的添加一句:

1.  DaggerDagger2TestComponent.builder()  
2.           // 添加如下语句,把ApplicationComponent传入到自己的Component中  
3.          .applicationComponent(ComponentHolder.getAppComponent())  
4.          .build()  
5.          .inject(this); 

查看运行结果:

可以看到,两个Activity中的company都是同一对象,实现了全局单例。
现在来分析@Singleton注解的实现和含义:
Singleton.java

1.  @Scope  
2.  @Documented  
3.  @Retention(RUNTIME)  
4.  public @interface Singleton {}  

从实现看@Singleton也是一个Scope,并且和上面我们自定义的ActivityScope.java并无区别,只是两者名字不一样,有助于理解代码而已。

如果说有差异的话,就是@Singleton修饰的Component不能再依赖其它Component,如上面我们定义的Dagger2TestComponent依赖于ApplicationComponent,但是ApplicationComponent不能依赖Dagger2TestComponent

再来看MainActivity的Component:Dagger2TestComponent,和之前相比,多了dependencies = ApplicationComponent.class,这一句的作用就是告知Dagger2框架,MainActivity的component需要依赖ApplicationComponent,在自动生成的DaggerDagger2TestComponent.java中就会多出一个applicationComponent()的方法。

1.  @ActivityScope  
2.  @Component(modules = Dagger2TestModule01.class, dependenci2es = ApplicationComponent.class)  public interface Dagger2TestComponent {  
3.      void inject(MainActivity activity);  

至此,Daggers的基本用法已经介绍完成。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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