前言
Dagger2是一个用于Android和Java的快速依赖注入框架。Dagger开始由Square公司开发并维护,后来Google接手该项目开发了Dagger2,Dagger2消除了所有的反射,并且使代码变清晰,提高了其可读性。Dagger2作为由Google开发维护的框架,在项目中使用的频率越来越高,所以有必要熟悉Dagger2的使用,并且了解其源码的实现原理。
依赖注入
在面向对象的开发过程中,我们一个对象通常会依赖其他的一个或者多个对象来完成任务。假设有一个类为Hand手,那它肯定要依赖Finger手指类完成Hand的构建,如果Finger类的构造改变了,这时也要去修改Hand类的实现,这显然不符合开闭原则。这样由于不同类的组合,类之间就有了耦合,而使用Dagger2依赖注入框架的目的就是降低程序的耦合,达到解耦的目的。
常见的几种依赖方式
-
构造方法注入
public class Hand { private Finger finger; public Hand(Finger finger) { this.finger = finger; } }
-
setter方法注入
public class Hand { private Finger finger; public void setFinger(Finger finger) { this.finger = finger; } }
-
接口注入
public interface FingerInject { void setFinger(Finger finger); } public class Hand implements FingerInject { private Finger finger; @Override public void setFinger(Finger finger) { this.finger = finger; } }
Dagger2的使用
Dagger2使用注解标注的形式,在编译时apt工具会根据这些注解自动生成特定依赖注入的代码。
@Inject和@Component的使用
下面使用Dagger2完成上面Finger的注入过程
- 使用@Inject标注被注入类的构造方法
public class Finger {
@Inject
public Finger() {
Log.e("m1ku","构建手指了");
}
public void assemble(){
Log.e("m1ku","手是由五根手指组成的");
}
}
public class Hand {
@Inject
public Hand(Finger finger) {
finger.assemble();
Log.e("m1ku", "构建手手类");
}
public void shakeHand() {
Log.e("m1ku", "我在握手了");
}
}
- 使用@Component标注Component接口
@Component
public interface MainComponent {
void inject(MainActivity activity);
}
make项目,让代码重新编译,编译过程Dagger2会生成依赖注入实际起作用的代码,这些代码后面再看。
- 在注入目标类MainActivity中,使用@Inject标注要注入的属性,最后调用MainComponent的实现类的inject方法完成注入。
public class MainActivity extends AppCompatActivity {
@Inject
Hand hand;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent
.create()
.inject(this);
hand.shakeHand();
}
}
调用注入对象的方法后,控制台打印如下,这就证明依赖注入成功了。
E/m1ku: 构建手指了
E/m1ku: 手是由五根手指组成的
E/m1ku: 构建手手类
E/m1ku: 我在握手了
@Inject
-
标注在构造方法上
- 表示Dagger2可以使用这个构造方法构造对象,如Finger类。
- 可以用来注入当前构造方法所需要的依赖,如Hand类构造参数依赖Finger类。
- 如果存在多个构造方法,@Inject注解仅可以标注其中一个。
-
标注在属性上
表示这个属性是需要被注入的,该属性不能用private来修饰。
-
标注在方法上
除了属性注入,Dagger2也可以使用方法注入,方法注入会在目标类构造方法执行后执行。
上面MainActivity的hand也可以使用方法注入,如下
@Inject public void setHand(Hand hand){ this.hand = hand; }
@Component
用来标注在一个接口上,接口中定义inject方法,方法的参数就是目标类对象。编译后,会生成其的实现类,主要注入逻辑在该实现类中完成,后面会看其代码。
@Module和@Provides的使用
在上面的注入过程中,我们都是在依赖的构造方法上使用@Inject标注,那么如果我们想注入系统提供的类或者第三方的类该怎么办呢?这时就不能标注其构造方法了,此时就要借助@Module和@Provides注解来完成依赖的注入了。
下面使用Dagger2将系统的Date日期类注入到MainActivity中
- 使用@Module标注Module类
@Module
public class MainModule {
}
- 在Module类中定义一个用@Provides标注的方法,在方法中实例化Date类,其返回值为Date类型
@Module
public class MainModule {
@Provides
public Date provideDate() {
return new Date();
}
}
- 为Component指定使用MainModule类
@Component(modules = MainModule.class)
public interface MainComponent {
void inject(MainActivity activity);
}
4.最后在MainActivity注入方式还是相同的
public class MainActivity extends AppCompatActivity {
@Inject
Date date;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent
.create()
.inject(this);
Log.e("m1ku",date.toString());
}
代码运行后,在Logcat输出:
E/m1ku: Mon May 27 17:43:41 GMT+08:00 2019
@Module
- 由@Module标注的类
- 按照约定,该类名的后缀为Module
- 该类的作用是提供依赖
@Provides
-
该注解用来标注定义在Module类中的方法,方法的返回值为需要的依赖
-
用@Provides标注的方法也可以有依赖,比如
//该方法需要的字符串参数由provideFormat提供 @Provides public SimpleDateFormat provideDateFormat(String format) { return new SimpleDateFormat(format); } @Provides String provideFormat() { return "yyyy-MM-dd"; }
@Named和@Qualifier的使用
有时候仅靠依赖的类型不足以让程序分辨出使用哪一个依赖。这时就需要使用限定符注解来标识,其中@Named注解是可以直接用String类型参数标识的注解,也可以使用@Qualifier这个元注解来自定义标识注解,如下。
@Module
public class LayoutManagerModule {
@Named("vertical")
@Provides
public LinearLayoutManager provideVerticalManager(Context context) {
return new LinearLayoutManager(context);
}
@Named("horizontal")
@Provides
public LinearLayoutManager provideHorizontalManager(Context context) {
return new LinearLayoutManager(context, OrientationHelper.HORIZONTAL, false);
}
@Provides
public Context provideContext() {
return DaggerApp.getContext();
}
}
LayoutManagerModule提供横向和纵向布局管理器的依赖,依赖类型相同,如果不用限定符标识就会报错。在目标类注入时,也要指定标识符,以告诉程序我们需要的是哪个依赖。
public class MainActivity extends AppCompatActivity {
@Named("vertical")
@Inject
LinearLayoutManager layoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent
.create()
.inject(this);
Log.e("m1ku",layoutManager.toString());
}
}
同样在构造方法上依赖它时也要使用限定符标识。
@Singleton和@Scope的使用
如果想要注入的依赖对象为单例的,可以使用@Singleton注解来实现。@Singleton为@Scope的默认实现,使用@Scope注解可以达到管理依赖对象生命周期的目的,同样也可以通过@Scope来自定义scope注解。
还是上面的例子,在MainActivity中注入两个LinearLayoutManager
public class MainActivity extends AppCompatActivity {
@Named("vertical")
@Inject
LinearLayoutManager layoutManager;
@Named("vertical")
@Inject
LinearLayoutManager layoutManager1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent
.create()
.inject(this);
//打印注入对象的地址
Log.e("m1ku","layoutManager = " + layoutManager.toString());
Log.e("m1ku","layoutManager1 = " + layoutManager1.toString());
}
这两个对象地址打印如下
E/m1ku: layoutManager = android.support.v7.widget.LinearLayoutManager@8aa6111
E/m1ku: layoutManager1 = android.support.v7.widget.LinearLayoutManager@e44b276
地址不同,证明这是两个不同的对象,初始化了两次。
如果想实现单例,我们需要在Module类中使用@Singleton标注提供依赖的方法,同时也要用@Singleton标注使用这个Module的Component类,如下
@Module
public class LayoutManagerModule {
@Named("vertical")
@Provides
@Singleton
public LinearLayoutManager provideVerticalManager(Context context) {
return new LinearLayoutManager(context);
}
@Named("horizontal")
@Provides
public LinearLayoutManager provideHorizontalManager(Context context) {
return new LinearLayoutManager(context, OrientationHelper.HORIZONTAL, false);
}
@Provides
public Context provideContext() {
return DaggerApp.getContext();
}
}
@Singleton
@Component(modules = {MainModule.class, LayoutManagerModule.class})
public interface MainComponent {
void inject(MainActivity activity);
}
此时这两个对象地址相同,实现了单例。
E/m1ku: layoutManager = android.support.v7.widget.LinearLayoutManager@ee42077
E/m1ku: layoutManager1 = android.support.v7.widget.LinearLayoutManager@ee42077
再新建一个SecondActivity,为其定义Component,并注入同样的对象
@Singleton
@Component(modules = LayoutManagerModule.class)
public interface SecondComponent {
void inject(SecondActivity secondActivity);
}
public class SecondActivity extends AppCompatActivity {
@Named("vertical")
@Inject
LinearLayoutManager linearLayoutManager3;
@Named("vertical")
@Inject
LinearLayoutManager linearLayoutManager4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
DaggerSecondComponent.create().inject(this);
Log.e("m1ku", "linearLayoutManager3 = " + linearLayoutManager3);
Log.e("m1ku", "linearLayoutManager4 = " + linearLayoutManager4);
}
}
由MainActivity跳转到SecondActivity,打印结果如下
E/m1ku: linearLayoutManager3 = android.support.v7.widget.LinearLayoutManager@88d262d
E/m1ku: linearLayoutManager4 = android.support.v7.widget.LinearLayoutManager@88d262d
我们发现这里的对象与MainActivity界面中的不同,不再是单例。但是在当前界面内仍然为单例的,这是由于@Scope注解的作用范围是局部的,它只保证依赖对象在当前Component中是单例的。如果我们想实现App内的全局单例的话,我们可以将Component保存在Application中来保证该Component在app中只有一份,后面在学习@Component的dependence会实现全局单例。
@Component的依赖dependencies
使用dependencies可以实现Component的依赖关系,让Component依赖另一个已经存在的Component组件。
现在通过实现一个全局单例的UserManger来学习dependencies的用法。
定义UserModule来提供UserManager依赖
@Module
public class UserModule {
@Singleton
@Provides
public UserManager provideUserManager(){
return new UserManager();
}
}
然后定义应用的全局AppComponent,在其中定义能向应用提供单例对象的方法
@Singleton
@Component(modules = UserModule.class)
public interface AppComponent {
UserManager getUserManager();
}
在Application中初始化该Component,并提供获取其实例的方法
public class DaggerApp extends Application {
private static Context context;
private static AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.create();
context = this;
}
public static AppComponent getAppComponent() {
return appComponent;
}
使用dependencies关键字为MainComponent和SecondComponent提供依赖
@ActivityScope
@Component(modules = {MainModule.class,LayoutManagerModule.class},dependencies = AppComponent.class)
public interface MainComponent {
void inject(MainActivity activity);
}
@ActivityScope
@Component(modules = LayoutManagerModule.class, dependencies = AppComponent.class)
public interface SecondComponent {
void inject(SecondActivity secondActivity);
}
@ActivityScope是自定义的Scope注解。由于AppComponent已经被@Singleton标注,而MainComponent和SecondComponent依赖于AppComponent,如果他们再使用@Singleton注解就会报错,所以要自定义@ActivityScope注解,当然这两个Module中也要换成@ActivityScope注解。
经过编译后,分别向两个activity中注入UserManager类
public class MainActivity extends AppCompatActivity {
@Inject
UserManager userManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent
.builder()
.appComponent(DaggerApp.getAppComponent())
.mainModule(new MainModule())
.build()
.inject(this);
Log.e("m1ku","userManager = " + userManager.toString());
}
public class SecondActivity extends AppCompatActivity {
@Inject
UserManager userManager1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
DaggerSecondComponent
.builder()
.appComponent(DaggerApp.getAppComponent())
.layoutManagerModule(new LayoutManagerModule())
.build()
.inject(this);
Log.e("m1ku", "userManager1 = " + userManager1.toString());
}
}
两个界面中UserManager地址打印相同,这就证明UserManager实现了全局的单例。
userManager = com.m1ku.daggerdemo.entity.UserManager@c1044e4
userManager1 = com.m1ku.daggerdemo.entity.UserManager@c1044e4
最后
我自己的项目中用的是mvparms框架,项目框架就是用Dagger2来组建的,但都是用的一键生成的,所以还是要好好学习下Dagger2框架的。即使不用但也要会呀,等下开一篇来看下它的源码实现,如果后面碰到新的使用姿势再回来补下。
网友评论