1、前言
Dagger2
作为一个上手难度较高的框架,我也是看了许多相关的文章,经历了无数次的从入门到放弃。放弃的多了好像也有一点懂了,于是乎我也总结一下自己对Dagger2
使用的相关知识的理解。
2、依赖注入
关于Dagger2
首先要理解的就是依赖注入(DI)和控制反转(IOC),对这两个概念你如果已经有所了解,可以直接跳到下一节。
在理解依赖注入之前先了解依赖注入的目的,也是使用Dagger2
框架的目的,知道了目的才能更好地理解过程。依赖注入的目的就是二个字:解耦。
高内聚,低耦合,是面向对象编程提倡遵循的设计原则,而通过依赖注入的方式能实现控制反转,从而实现解耦的目的。
光这样说还是太理论了,不易理解,举个例子来帮助理解下,最近复联4大火,举个漫威英雄的例子。
首先想象一下,你是个自带柯南属性普通人,每次有外星人入侵或者是超能力变种人搞破坏,你都好巧不巧的能出现在现场,然而你并没有能力打败他们,你只有托尼史塔克的联系方式,所以你每次都联系他,由他来解决这些麻烦。然而托尼突然有一天告诉你他要带着小辣椒去度假,会有一段时间联系不上。你没办法出于求生本能,你得去寻找另一个超级英雄,你找到了美国队长,队长一口答应,说没问题,下次有事联系我,我来搞定。于是你获得了美国队长的联系方式。又过一段时间,队长也和你说他要和冬兵去度假,然而托尼还度假没回来。你没办法,又只能自己去找寡姐,获得了他的联系方式,下次遇到袭击就联系她。发现了没有,这里你每次都依赖与某一个超级英雄,一旦发生变故,你只能自己去找新的英雄获得更新他的联系方式。这样对某个英雄依赖非常严重,现在换一种方法,不具体依赖某个英雄,我直接去神盾局找尼克弗瑞得到他的联系方式,以后有事都联系他,至于是哪个超级英雄来解决又或者怎么去联系英雄,我都不用知道,交给尼克弗瑞去处理就行。
下面通过代码加深理解,先定义三个具体英雄类:
//抽象英雄类
public abstract class Hero {
public abstract String call();
}
复制代码
public class IronMan extends Hero {
@Override
public String call() {
return "贾维斯:已收到您的求救消息,正在联系托尼";
}
}
复制代码
public class CaptainAmerica extends Hero {
@Override
public String call() {
return "美国队长:我已收到您的求救消息,正在赶来的路上";
}
}
复制代码
public class BlackWidow extends Hero {
@Override
public String call() {
return "黑寡妇:我已收到您的求救消息,正在赶来的路上";
}
}
复制代码
最后是自己类:
public class Self {
private IronMan ironMan;
public Self() {
ironMan = new IronMan();
}
public void help() {
String call = ironMan.call();
Log.d("callMessage", call);
}
}
复制代码
调用:
//首先要有一个我
Self self = new Self();
//遇到危险
self.help();
复制代码
执行日志:
D/callMessage: 贾维斯:已收到您的求救消息,正在联系托尼
复制代码
托尼去度假了,于是我们只能联系美队,所以要修改Self
类:
public class Self {
// private IronMan ironMan;
private CaptainAmerica captainAmerica;
public Self() {
// ironMan = new IronMan();
captainAmerica = new CaptainAmerica();
}
public void help() {
// String call = ironMan.call();
String call = captainAmerica.call();
Log.d("callMessage", call);
}
}
复制代码
执行日志:
D/callMessage: 美国队长:我已收到您的求救消息,正在赶来的路上
复制代码
美队也去度假了,再次修改Self
类:
public class Self {
// private IronMan ironMan;
// private CaptainAmerica captainAmerica;
private BlackWidow blackWidowa;
public Self() {
// ironMan = new IronMan();
// captainAmerica = new CaptainAmerica();
blackWidowa = new BlackWidow();
}
public void help() {
// String call = ironMan.call();
// String call = captainAmerica.call();
String call = blackWidowa.call();
Log.d("callMessage", call);
}
}
复制代码
执行日志:
D/callMessage: 黑寡妇:我已收到您的求救消息,正在赶来的路上
复制代码
看到这里发现每次变动都要修改Self
类,这里Self
一直依赖一个英雄类,英雄更换了要修改,英雄的构造函数变了也要修改Self
类,这样耦合就非常严重。现在我们采用通过尼克弗瑞来联系英雄:
public class NickFury {
private Hero hero;
public Hero call() {
hero = new CaptainAmerica();
return hero;
}
}
复制代码
修改Self
类:
public class Self {
private NickFury nickFury;
public Self() {
nickFury = new NickFury();
}
public void help() {
Hero hero = nickFury.call();
String call = hero.call();
Log.d("callMessage", call);
}
}
复制代码
执行日志:
D/callMessage:美国队长:我已收到您的求救消息,正在赶来的路上
复制代码
这下Self
类不依赖于具体某个英雄类,而是通过三方NickFury
类来实现英雄对象的注入。一旦有所变动更换英雄,只需要修改NickFury
类的方法即可。其实这里的NickFury类,类似于工厂模式。
还记的依赖注入的目的吗?解耦,这里通过第三方工厂类使具体英雄类与Self
类不再耦合,原来是Self
主动去new
实例化一个英雄类,修改后变为被动通过调用第三方类方法注入一个英雄类,由主动到被动实现了控制反转,实现了解耦,达成了这个目的。
关于依赖注入的方法有以下几种:
- 基于接口注入
- 基于构造函数注入
- 基于 set 方法注入
- 基于注解注入
基于接口:
public interface InjectInterface {
void injectHero(Hero hero);
}
复制代码
public class MySelf implements InjectInterface {
Hero hero;
public void help() {
String call = hero.call();
Log.d("callMessage", call);
}
@Override
public void injectHero(Hero hero) {
this.hero = hero;
}
}
复制代码
定义一个接口,实现类实现接口方法注入。
基于构造函数:
public class MySelf {
private Hero hero;
public MySelf(Hero hero) {
this.hero = hero;
}
public void help() {
String call = hero.call();
Log.d("callMessage", call);
}
}
复制代码
在构造函数时传入。
基于set方法:
public class MySelf {
private Hero hero;
public void setHero(Hero hero) {
this.hero = hero;
}
public void help() {
String call = hero.call();
Log.d("callMessage", call);
}
}
复制代码
通过set方法注入对象。
基于注解注入:
Dagger2
中就用到了注解。所以这里用Dagger2
来实现一个了依赖注入的例子。再想象一个吃麻辣烫场景,麻辣烫里要加很多食材,比如牛肉、豆腐、香肠、鱼丸等而香肠又是由肠衣和肉馅组成,鱼丸是由鱼肉做成。所以代码一般是这样写:
public class TestActivity extends AppCompatActivity {
Fish fish;
FishBall fishBall;
Doufu doufu;
Potato potato;
Meat meat;
Casings casings;
SeeYouTomrrow seeYouTomrrow;
Sausage sausage;
SpicyHotPot spicyHotPot;
Beef beef;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
beef = new Beef();
fish = new Fish();
fishBall = new FishBall(fish);
doufu = new Doufu();
potato = new Potato();
meat = new Meat();
casings = new Casings();
sausage = new Sausage(casings, meat);
seeYouTomrrow = new SeeYouTomrrow();
spicyHotPot = new SpicyHotPot(potato, doufu, sausage, seeYouTomrrow, fishBall,beef);
spicyHotPot.eat();
}
}
复制代码
这里就是初始化了很多对象,其中有些对象中还引用了其他对象,像这样的初始化代码在平常开发中还是比较常见的,而使用了Dagger2
就可以这样写:
public class TestActivity extends AppCompatActivity {
@Inject
SpicyHotPot spicyHotPot;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
DaggerTestComponent.builder().build().inject(this);
spicyHotPot.eat();
}
}
复制代码
使用了Dagger2
是不是简单了不少,只要一个注解加一行代码,就完成了多个对象的初始化,而且无论对象如何修改,这里的代码都无需变动,完成了解耦。
3、Dagger2注解使用
既然Dagger2
是通过注解实现的依赖注入,那么学习使用Dagger2
就是要学习Dagger2
中的注解的使用。不过,在具体看Dagger2
中的注解之前,先要在项目中引入Dagger2
的依赖,按照Github
上Dagger2的文档引入:
dependencies {
implementation 'com.google.dagger:dagger:2.22.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
}
复制代码
接下来来看Dagger2
具体的注解。
3.1 @Inject和@Component注解
@Inject
注解的作用主要有两个:
- 一是标注在成员变量上,表示需要通过Dagger2为它提供依赖。
- 二是标注在构造函数上,表示为这个类型的成员变量提供依赖。
所以我们要使用Dagger2
初始化一个类的依赖首先要在这个类的构造函数上加上@Inject
注解,然后在需要依赖的地方的对应变量上也加上@Inject
注解。但是光加上@Inject注解完成所谓的依赖注入吗?答案是否定的,@Inject
只是标注了依赖的需求方和依赖的提供方,但是它们俩之间还没有建立关系桥梁。而@Component
就是干这个的,具体来看下面这个例子:
public class Cola {
@Inject
public Cola() {
}
public String returnName() {
return "百事可乐";
}
}
复制代码
先定义一个可乐类,在他的构造函数上加上@Inject
注解,表示提供该类的依赖。再在Activity
中定义一个Cola
类型变量同样加上@Inject
注解,表示需要该类依赖:
public class InjectAndComponentActivity extends AppCompatActivity {
private TextView mTextContent;
@Inject
Cola cola;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inject_and_component);
mTextContent = findViewById(R.id.textContent);
}
}
复制代码
接着新建一个接口ColaComponent
:
@Component
public interface ColaComponent {
void inject(InjectAndComponentActivity injectAndComponentActivity);
}
复制代码
接口上标注了@Component
注解,接着点击AndroidStudio上的Make Project
编译项目,此时Dagger2
会自动生成这个接口的实现类DaggerColaComponent
,由这个实现类的方法来完成依赖注入。接着在Activity
中通过实现调用他的inject
方法完成注入。
public class InjectAndComponentActivity extends AppCompatActivity {
private TextView mTextContent;
@Inject
Cola cola;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inject_and_component);
mTextContent = findViewById(R.id.textContent);
DaggerColaComponent.builder().build().inject(this);
mTextContent.setText(cola.returnName());
}
}
复制代码
注入之后就可以调用该对象的方法,运行效果:
上面举的麻辣烫的例子的实现也是一样的,每个食材类的构造函数上加了@Inject
注解,创建一个Component
接口。贴上部分代码:
//肠衣
public class Casings {
@Inject
public Casings() {
}
}
//肉
public class Meat {
@Inject
public Meat() {
}
}
//香肠
public class Sausage {
Casings casings;
Meat meat;
@Inject
public Sausage(Casings casings, Meat meat) {
this.casings = casings;
this.meat = meat;
}
}
//麻辣烫
public class SpicyHotPot {
Potato potato;
Doufu doufu;
Sausage sausage;
SeeYouTomorrow seeYouTomorrow;
FishBall fishBall;
Beef beef;
@Inject
public SpicyHotPot(Potato potato, Doufu doufu, Sausage sausage, SeeYouTomorrow seeYouTomorrow, FishBall fishBall, Beef beef) {
this.potato = potato;
this.doufu = doufu;
this.sausage = sausage;
this.seeYouTomorrow = seeYouTomorrow;
this.fishBall = fishBall;
this.beef = beef;
}
public void eat() {
Log.d("Dagger2", "我开动了");
}
}
复制代码
3.2 @Module和@Provides注解
无论是3.1中Cola
类还是之前的各种食材类都是我们自己定义的类,所以可以自己修改,想使用Dagger2
只需要在类的构造函数上加上@Inject
注解。但是实际开发中会遇到使用三方类库的情况,这些三方类库中的类代码我们无法修改,没法在其构造函数上加@Inject注解,那么是不是没法使用Dagger2
了呢?答案还是否定的,Dagger2
中的@Module
和@Provides
注解就是用来处理只种情况。
看下面这个例子,这回不吃麻辣烫了,来吃炸鸡,于是定义了一个德克士类。
public class Dicos {
String friedDrumstick;
public Dicos() {
friedDrumstick = "脆皮手枪腿";
}
public String returnDicos() {
return "德克士:" + friedDrumstick;
}
}
复制代码
这回不添加任何注解,就是一个正常的Dicos
对象类。接着同样新建一个DicosComponent
接口:
@Component
public interface DicosComponent {
void inject(ModuleAndProvidesActivity moduleAndProvidesActivity);
}
复制代码
接着在Activity
中定义Dicos
类型变量:
public class ModuleAndProvidesActivity extends AppCompatActivity {
private TextView mTextContent;
@Inject
Dicos dicos;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_module_and_provides);
mTextContent = findViewById(R.id.textContent);
}
}
复制代码
至此为止都和之前是一样的,接下来新建一个DicosModule
类,因为没法在三方类的构造函数上加@Inject
注解,所以要通过Module
类来提供依赖。
@Module
public class DicosModule {
@Provides
public Dicos getDicos() {
return new Dicos();
}
}
复制代码
这里首先创建了一个DicosModule
类,并在DicosModule
类上加上@Module
注解,接着在这个类中只写了一个getDicos()
方法,调用Dicos
的构造方法创建对象然后返回。通过在方法上加上@Provides
注解,表示由这个方法为Dicos
类型提供依赖。最后记得还要在DicosComponent
接口注解上加上DicosModule
。这样在Activity
中@Inject
标记需要依赖时,才能找到。
@Component(modules = DicosModule.class)
public interface DicosComponent {
void inject(ModuleAndProvidesActivity moduleAndProvidesActivity);
}
复制代码
Make Project
后在Activity
中同样调用DaggerDiscosComponent
的inject
方法注入依赖即可。
public class ModuleAndProvidesActivity extends AppCompatActivity {
private TextView mTextContent;
@Inject
Dicos dicos;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_module_and_provides);
mTextContent = findViewById(R.id.textContent);
DaggerDicosComponent.builder().dicosModule(new DicosModule()).build().inject(this);
mTextContent.setText(dicos.returnDicos());
}
}
复制代码
运行结果:
3.3 @Named和@Qualifier注解
接下来考虑一下这种情况,如果一个类有多个构造方法,或者有两个相同依赖时,它们都继承同一个父类或者实现同一个接口,那么怎么区分呢?这就要用到@Named
或者@Qualifier
注解了。这次定义一个小肥羊火锅类。
public class LittleSheep {
String mutton = "没有肉";
String vegetables = "没有菜";
public LittleSheep(String mutton) {
this.mutton = mutton;
}
public LittleSheep(String mutton, String vegetables) {
this.mutton = mutton;
this.vegetables = vegetables;
}
public String retrunCotent() {
return "小肥羊火锅:" + mutton + " " + vegetables;
}
}
复制代码
这个类有两个构造函数,接着同样是创建Component
和Module
类。
@Component(modules = LittleSheepModule.class)
public interface LittleSheepComponent {
void inject(NamedActivity namedActivity);
}
复制代码
@Module
public class LittleSheepModule {
@Named("all")
@Provides
public LittleSheep provideLittleSheepAll() {
return new LittleSheep("十盘羊肉", "一盘蔬菜");
}
@Named("mutton")
@Provides
public LittleSheep provideLittleSheepSingle() {
return new LittleSheep("二十盘羊肉吃个够");
}
}
复制代码
LittleSheepComponent
和之前的没什么区别,看到LittleSheepModule
,这个Module
有两个@Provides
注解方法,分别对应两个不同传参的构造函数,但是这两个方法返回类型都是LittleSheep
都提供同一类型的依赖,在需求依赖的时候Dagger2
就分不清是哪个了,所以这里要用@Named
注解来做个区分,这里分别设置两个不同的name
,all
和mutton
做区分。除此之外在Activity
中,需求依赖的时候也要使用@Named
做区分。
public class NamedActivity extends AppCompatActivity {
private TextView mTextContent;
@Named("all")
@Inject
LittleSheep allLittleSheep;
@Named("mutton")
@Inject
LittleSheep littleSheep;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_named);
mTextContent = findViewById(R.id.textContent);
DaggerLittleSheepComponent.builder().build().inject(this);
mTextContent.setText(allLittleSheep.retrunCotent() + "\n" + littleSheep.retrunCotent());
}
}
复制代码
NamedActivity
中同样在@Inject
注解上还要加上@Named
区分。运行结果:
接下来看继承同一个父类的情况,首先定义一个快餐类FastFood
,再定义他的两个子类KFC
和BurgerKing
。
public abstract class FastFood {
public abstract String returnContent() ;
}
复制代码
public class KFC extends FastFood {
public KFC() {
}
@Override
public String returnContent() {
return "KFC全家桶";
}
}
复制代码
public class BurgerKing extends FastFood {
String beefBurger = "三层牛肉汉堡";
public BurgerKing() {
}
@Override
public String returnContent() {
return "汉堡王:" + beefBurger;
}
}
复制代码
接着还是创建Component
和Module
类。
@Component(modules = FastFoodQualifierModule.class)
public interface FastFoodQualifierComponent {
void inject(FastFoodQualifierActivity fastFoodQualifierActivity);
}
复制代码
@Module
public class FastFoodQualifierModule {
@Provides
public FastFood getKFC() {
return new KFC();
}
@Provides
public FastFood getBurgerKing() {
return new BurgerKing();
}
}
复制代码
主要的问题还是在Module
中,这样写还是无法区分到底是KFC
还是BurgerKing
的依赖。这回不使用@Named
使用@Qualifier
处理。@Qualifier
比@Named
更加灵活和强大,用于自定义注解。接下来我们定义两个注解。
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface KFC {
}
复制代码
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BurgerKing {
}
复制代码
一个KFC
和一个BurgerKing
注解,存活时长都选择RUNTIME
,再添加上@Qualifier
注解限定,接下来就可以用这两个自定义注解来区分Module
中方法的类型。
@Module
public class FastFoodQualifierModule {
@com.sy.dagger2demo.annotations.KFC
@Provides
public FastFood getKFC() {
return new KFC();
}
@com.sy.dagger2demo.annotations.BurgerKing
@Provides
public FastFood getBurgerKing() {
return new BurgerKing();
}
}
复制代码
FastFoodQualifierModule
中两个方法上分别添加刚才的两个注解,接下来Activity
中和使用@Named
并没什么不同,只是把@Named
注解分别换成刚才定义的注解。
public class FastFoodQualifierActivity extends AppCompatActivity {
private TextView mTextContent;
@KFC
@Inject
FastFood kfc;
@BurgerKing
@Inject
FastFood burgerKing;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fast_food_qualifier);
mTextContent = (TextView) findViewById(R.id.textContent);
DaggerFastFoodQualifierComponent.builder().build().inject(this);
mTextContent.setText(kfc.returnContent()+"\n"+burgerKing.returnContent());
}
}
复制代码
运行结果:
3.4 @Singleton和@Scope注解
@Singleton
看名字就知道这个注解是用来实现单例的。再次定义一个NetworkClient
类。采用@Module
和@Provides
注解实现依赖注入。
public class NetworkClient {
String baseUrl;
public NetworkClient() {
baseUrl = "http://www.baidu.com/";
}
public void init() {
Log.d("NetworkClient", "网络初始化");
}
}
复制代码
@Component(modules = NetworkModule.class)
public interface NetworkComponent {
void inject(SingletonAndScopeActivity singletonAndScopeActivity);
}
复制代码
@Module
public class NetworkModule {
@Provides
public NetworkClient getClient() {
return new NetworkClient();
}
}
复制代码
public class SingletonAndScopeActivity extends AppCompatActivity {
private TextView mContentTextView;
@Inject
NetworkClient networkClient1;
@Inject
NetworkClient networkClient2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_singleton_and_scope);
mContentTextView = (TextView) findViewById(R.id.contentTextView);
DaggerNetworkComponent.create().inject(this);
mContentTextView.setText(networkClient1.toString() + "\n" + networkClient2.toString());
}
}
复制代码
这里的代码和之前的完全相同,只是在Activity
中添加了一个对象,同时有两个NetworkClient
对象,调用hashCode
方法查看是否为同一个对象。 运行结果:
可以看到这里hashCode
不同,明显是两个对象。接下来使用@Singleton
实现单例。首先在NetworkComponent
上加上@Singleton
注解:
@Singleton
@Component(modules = NetworkModule.class)
public interface NetworkComponent {
void inject(SingletonAndScopeActivity singletonAndScopeActivity);
}
复制代码
接着在NetworkModule
中的方法上也加上@Singleton
注解:
@Module
public class NetworkModule {
@Singleton
@Provides
public NetworkClient getClient() {
return new NetworkClient();
}
}
复制代码
这样就ok了,就是这么简单,再次运行程序查看hashCode
。这时hashCode
相同已经是同一个对象了。
运行结果:
注意这里其实@Singleton
实现的单例只是在同个Activity
下的单例,在其他Activity
下,再次创建这了类的对象就不再是同一个对象了。这里我们在新建一个Activity
测试下:
public class SecondActivity extends AppCompatActivity {
private TextView mContentTextView;
@Inject
NetworkClient networkClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
mContentTextView = (TextView) findViewById(R.id.contentTextView);
DaggerNetworkComponent.create().inject(this);
mContentTextView.setText(networkClient.toString());
}
}
复制代码
在Component
中添加inject
方法类型为SecondActivity
:
@Singleton
@Component(modules = NetworkModule.class)
public interface NetworkComponent {
void inject(SingletonAndScopeActivity singletonAndScopeActivity);
void inject(SecondActivity secondActivity);
}
复制代码
运行结果:
可以看到虽然使用了@Singleton
注解,但是也只能保证在同一个Activity
中是单例同一个对象,在多个Activity
中就无法保证了。那么怎么实现全局的单例呢?这可以用@Scope
注解。
@Scope
同样用来自定义注解用来限定注解,因为我们知道Application
是单例的,所以可以使用@Scope
结合Application
实现全局的单例模式。先定义一个新注解ApplicationScope
。
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}
复制代码
这个和上面类似,只是用了@Scope
注解,接着修改之前的NetworkModule
,将@Singleton
换成新注解@ApplicationScope
:
@Module
public class NetworkModule {
@ApplicationScope
@Provides
public NetworkClient getClient() {
return new NetworkClient();
}
}
复制代码
接着创建一个新的ActivityComponent
:
@ApplicationScope
@Component(modules = NetworkModule.class)
public interface ActivityComponent {
void inject(SingletonAndScopeActivity singletonAndScopeActivity);
void inject(SecondActivity secondActivity);
}
复制代码
这里同样是使用了@ApplicationScope
注解,接着创建MyApplicatin
类在其中去获取ActivityComponent
的实例。
public class MyApplication extends Application {
ActivityComponent activityComponent;
@Override
public void onCreate() {
super.onCreate();
activityComponent = DaggerActivityComponent.builder().build();
}
public static MyApplication getApplication(Context context){
return (MyApplication) context.getApplicationContext();
}
public ActivityComponent getActivityComponent(){
return activityComponent;
}
}
复制代码
这里看到在MyApplication
中通过Dagger2
获取到ActivityComponet
的实例再给出public
方法获取这个ActivityComponent
实例。这样在Activity
中就可以通过这个ActivityComponent
注入,获得单例的对象了。修改Activity
中代码:
public class SingletonAndScopeActivity extends AppCompatActivity {
private TextView mContentTextView;
@Inject
NetworkClient networkClient1;
@Inject
NetworkClient networkClient2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_singleton_and_scope);
mContentTextView = (TextView) findViewById(R.id.contentTextView);
// DaggerNetworkComponent.create().inject(this);
//通过Application中的Component注入
MyApplication.getApplication(this).getActivityComponent().inject(this);
mContentTextView.setText(networkClient1.hashCode() + "\n" + networkClient2.hashCode());
mContentTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(SingletonAndScopeActivity.this,SecondActivity.class));
}
});
}
}
复制代码
public class SecondActivity extends AppCompatActivity {
private TextView mContentTextView;
@Inject
NetworkClient networkClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
mContentTextView = (TextView) findViewById(R.id.contentTextView);
// DaggerNetworkComponent.create().inject(this);
//通过Application中的Component注入
MyApplication.getApplication(this).getActivityComponent().inject(this);
mContentTextView.setText(networkClient.hashCode()+"");
}
}
复制代码
运行结果:
再次运行查看结果,发现这是已经全是同一个对象了。
3.5 @Component的dependencies
Component
还可以通过dependencies
依赖于别的Component
。这里再重新定义一个PizzaHut
类:
public class PizzaHut {
String SuperSupremePizza = "超级至尊披萨";
public PizzaHut() {
}
public String returnContent() {
return "必胜客超级至尊套餐:" + SuperSupremePizza;
}
}
复制代码
@Module
public class PizzaHutModule {
@Provides
public PizzaHut getPizzaHut() {
return new PizzaHut();
}
}
复制代码
@Component(modules = PizzaHutModule.class)
public interface PizzaHutComponent {
void inject(SingletonAndScopeActivity singletonAndScopeActivity);
void inject(SecondActivity secondActivity);
}
复制代码
同时还创建了对应的Module
和Componet
,还是和之前没什么区别。接下来在ActivityComponent
中使用dependencies
将PizzaHutComponent
引入。
@ApplicationScope
@Component(modules = NetworkModule.class,dependencies = PizzaHutComponent.class)
public interface ActivityComponent {
void inject(SingletonAndScopeActivity singletonAndScopeActivity);
void inject(SecondActivity secondActivity);
}
复制代码
接着到MyApplication
中引入PizzaHutComponent
:
public class MyApplication extends Application {
ActivityComponent activityComponent;
@Override
public void onCreate() {
super.onCreate();
activityComponent = DaggerActivityComponent.builder().pizzaHutComponent(DaggerPizzaHutComponent.builder().build()).build();
}
public static MyApplication getApplication(Context context) {
return (MyApplication) context.getApplicationContext();
}
public ActivityComponent getActivityComponent() {
return activityComponent;
}
}
复制代码
最后再到刚才的Activity
中添加一个PizzaHut
类型变量:
public class SingletonAndScopeActivity extends AppCompatActivity {
private TextView mContentTextView;
@Inject
NetworkClient networkClient1;
@Inject
NetworkClient networkClient2;
@Inject
PizzaHut pizzaHut;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_singleton_and_scope);
mContentTextView = (TextView) findViewById(R.id.contentTextView);
// DaggerNetworkComponent.create().inject(this);
MyApplication.getApplication(this).getActivityComponent().inject(this);
mContentTextView.setText(networkClient1.hashCode() + "\n" + networkClient2.hashCode()+"\n"+pizzaHut.returnContent());
mContentTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(SingletonAndScopeActivity.this,SecondActivity.class));
}
});
}
}
复制代码
运行结果:
可以看到这里已经成功注入PizzaHut
,并且调用了returnContent
方法。
3.6 懒加载
Dagger2
也支持懒加载模式,就是@Inject
的时候不初始化,而到使用的时候调用get
方法获取实例。
public class SingletonAndScopeActivity extends AppCompatActivity {
private TextView mContentTextView;
@Inject
NetworkClient networkClient1;
@Inject
NetworkClient networkClient2;
@Inject
Lazy<PizzaHut> pizzaHut;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_singleton_and_scope);
mContentTextView = (TextView) findViewById(R.id.contentTextView);
// DaggerNetworkComponent.create().inject(this);
MyApplication.getApplication(this).getActivityComponent().inject(this);
//使用的时候调用get方法获取处理
mContentTextView.setText(networkClient1.hashCode() + "\n" + networkClient2.hashCode()+"\n"+pizzaHut.get().returnContent());
mContentTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(SingletonAndScopeActivity.this,SecondActivity.class));
}
});
}
}
复制代码
4、Dagger2与MVP
在对Dagger2
中的注解有了一定的了解之后,继续学习Dagger2+MVP
。将Dagger2
与MVP
结构结合起来,可以使MVP
架构中的依赖更加清晰更加易于管理。学习MVP+Dagger2
自然是去看Google
官方提供的Demo
。
先看一下工程目录:
看到具体模块包下除了基础MVP
的
Presenter
、
Contract
、
Fragment
等类之外,还有
Component
和
Module
这些类都是使用了
Dagger2
会用到的。接着先来具体看
ToDoApplication
类:
public class ToDoApplication extends Application {
private TasksRepositoryComponent mRepositoryComponent;
@Override
public void onCreate() {
super.onCreate();
mRepositoryComponent = DaggerTasksRepositoryComponent.builder()
.applicationModule(new ApplicationModule((getApplicationContext())))
.tasksRepositoryModule(new TasksRepositoryModule()).build();
}
public TasksRepositoryComponent getTasksRepositoryComponent() {
return mRepositoryComponent;
}
}
复制代码
看到这里就是构建了一个TasksRepositoryComponent
,并提供了一个获得的方法。先找到TaskRepositoryComponent
:
@Singleton
@Component(modules = {TasksRepositoryModule.class, ApplicationModule.class})
public interface TasksRepositoryComponent {
TasksRepository getTasksRepository();
}
复制代码
首先看到这里用了单例的注解,接着看到有两个Module
,还提供了一个获取TaskRepository
的方法,这个TaskRepository
是用来获取数据的,在Presenter
构造中传入,Presenter
调用其中方法获得数据。先来看TaskRepositoryModule
:
@Module
public class TasksRepositoryModule {
@Singleton
@Provides
@Local
TasksDataSource provideTasksLocalDataSource(Context context) {
return new TasksLocalDataSource(context);
}
@Singleton
@Provides
@Remote
TasksDataSource provideTasksRemoteDataSource() {
return new FakeTasksRemoteDataSource();
}
}
复制代码
看到这个Module
中有两个@Provides
标注的方法,是用来提供测试数据返回的。一个方法是本地数据,一个是模拟远程数据。返回的都是TasksDataSource类型,所以用了自定义注解@Local
和@Remote
做了区分。点进去看这两个注解,其定义时都用了@Qualifier
注解。
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Local {
}
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Remote {
}
复制代码
接着返回看ApplicationModule
:
@Module
public final class ApplicationModule {
private final Context mContext;
ApplicationModule(Context context) {
mContext = context;
}
@Provides
Context provideContext() {
return mContext;
}
}
复制代码
看到其中只提供了一个上下文的mContext
方法。回到ToDoApplication
中看到这里创建ApplicationModule
传入的是ApplicationContext
。下面进入tasks
这个具体模块页面查看Dagger2
具体是怎么和MVP
结合的。
public class TasksActivity extends AppCompatActivity {
private static final String CURRENT_FILTERING_KEY = "CURRENT_FILTERING_KEY";
private DrawerLayout mDrawerLayout;
@Inject TasksPresenter mTasksPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tasks_act);
......
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}
// Create the presenter
DaggerTasksComponent.builder()
.tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
.tasksPresenterModule(new TasksPresenterModule(tasksFragment)).build()
.inject(this);
......
}
......
}
复制代码
这段是TasksActivity
中的代码,其中省略掉了一些无关的代码。可以看到这和Google
官方MVP
的Demo
一样,是在Activity
里放了一个Fragment
将Fragment
作为View
使用。看到这里在TasksPresenter
上加了@Inject
注解,也就是说这里是要用Dagger2
初始化Presenter
。在onCreate
方法中通过DaggerTasksComponent
的inject
方法注入TasksPresenter
依赖,创建TasksPresenter
。接下来来看TasksComponent
接口的代码:
@FragmentScoped
@Component(dependencies = TasksRepositoryComponent.class, modules = TasksPresenterModule.class)
public interface TasksComponent {
void inject(TasksActivity activity);
}
复制代码
其中除了设置了TasksPresenterModule
而且还依赖了TasksRepositoryComponent
这个Component
。这就让之前的TasksRepository
在这里也可以使用。接着进入TasksPresenterModule
查看:
@Module
public class TasksPresenterModule {
private final TasksContract.View mView;
public TasksPresenterModule(TasksContract.View view) {
mView = view;
}
@Provides
TasksContract.View provideTasksContractView() {
return mView;
}
}
复制代码
TasksPresenterModule
中标注了mView
的@Provides
方法,为注入View
提供了方法。最后看到TasksPresenter
:
final class TasksPresenter implements TasksContract.Presenter {
private final TasksRepository mTasksRepository;
private final TasksContract.View mTasksView;
private TasksFilterType mCurrentFiltering = TasksFilterType.ALL_TASKS;
private boolean mFirstLoad = true;
@Inject
TasksPresenter(TasksRepository tasksRepository, TasksContract.View tasksView) {
mTasksRepository = tasksRepository;
mTasksView = tasksView;
}
@Inject
void setupListeners() {
mTasksView.setPresenter(this);
}
......
}
}
复制代码
TasksPresenter
的构造方法上加上@Inject
注解提供了依赖。至此所有对象具能由Dagger2
提供依赖,在TaskActivity
注入依赖,完成了Dagger2
与MVP
的结合,完成了解耦。
DaggerTasksComponent.builder()
.tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
.tasksPresenterModule(new TasksPresenterModule(tasksFragment)).build()
.inject(this);
复制代码
5、总结
1. 使用Dagger2的目的是解耦。使用Dagger2更好更清晰地管理项目中类之间的依赖。
2. 关于Dagger2的使用我的理解是:
- 在大型项目中,Dagger2无疑是解耦利器,特别是在项目由多人合作开发时,无需关注个各类的依赖和构造初始化等实现和变动,全部交给Dagger2处理。
- 在小型项目中,考虑到类之间的相互依赖关系简单,并且多为单人开发,开发周期较短等因素,可以不引入Dagger2。
- 如果项目的业务逻辑复杂,各个类之间的相互依赖复杂,初始化构造复杂,还是应该使用Dagger2。虽然有可能开始不熟练,但是多用用就会了。