依赖注入(DI, Dependency Injection),目的是为了解决类之间的耦合,方便测试。
减少耦合的一个原则是,一个类A
需要的类B
的对象的实例化不应该在A
中实现,而是通过一定的方式从外部传入B
的实例,这样就解决了类A
和B
之间的耦合。
(PS:写这篇文章的目的不是说自己已经掌握了Dagger2,其实还有很多没有深刻理解的地方。把自己研究这几天的成果写出来,给想了解Dagger2的同学做个参考,同时也作为自己的一个备忘。有错误的地方,请大家斧正。)
举个例子:
public class ClassA {
public ClassA() {
//do some init work
}
}
public class ClassB {
private ClassA mInstanceA;
public ClassB() {
//ClassA的实例化在ClassB的构造方法中执行
instanceA = new ClassA();
}
}
如上,ClassB
依赖于ClassA
,并且在ClassB
的构造方法中完成了ClassA
的实例化。这样看上去似乎没有什么问题,但是当CLassA
的构造方法改变,则必须要修改ClassB
的代码。这时ClassA
和ClassB
之间有耦合,牵一发动全身。
依赖注入可以很好的解决这个问题。有很多种注入依赖的方法:
1.通过接口注入依赖。
重构一下ClassB
的写法:
public interface IClassB {
void setClassA(ClassA a);
}
public class ClassB implements IClassB {
private ClassA mInstanceA;
@Override
void setClassA(ClassA a) {
mInstanceA = a;
}
}
2.通过setter方法注入依赖。
public class ClassB {
private ClassA mInstanceA;
public void setInstanceA(ClassA a) {
mInstanceA = a;
}
}
3.通过构造方法注入依赖。
public class ClassB {
private ClassA mInstanceA;
public ClassB(ClassA a) {
mInstanceA = a;
}
}
4.通过依赖注入框架注入依赖。
开始讲这篇文章的主角--Dagger2。
Dagger2是目前Android上比较流行的一个依赖注入框架,Google出品。之前在项目中@刘青也用到了这个框架,非常适合与MVP架构配合使用。
大家知道在MVP架构中,View
持有Presenter
的引用,Presenter
持有Model
的引用,其实说白了就是依赖。使用Dagger2可以很方便的注入这些依赖。
贴出部分代码:
public class LoginFragment extends BaseFragment implements LoginView {
@Inject
LoginPresenter mLoginPresenter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
injectDependencies();
}
private void injectDependencies() {
DaggerLoginComponent.builder()
.appComponent(ComponentHolder.getAppComponent())
.loginModule(ModuleProvider.getInstance().provideLoginModule(this, getActivity()))
.build()
.inject(this);
}
}
可以看出,LoginFragment
依赖于LoginPresenter
。在mLoginPresenter
上添加@Inject
注解,然后在onCreate
方法中做了某种操作后,LoginPresenter
就被注入到LoginFragment
中了,具体是哪种操作,我们往下看。
首先看下Dagger2中常见的几个注解:
1.@Inject
在字段或构造方法前加上这个注解,表明这个字段需要被注入依赖或者标注依赖对应的构造方法。
2.@Module
被@Module
标注的类作为依赖的提供者。
3.@Provide
在方法前加上这个注解,表明这个方法提供依赖,通常用在被@Module
注解的类的方法上。举个例子:
@Provides
InjectEntity provideInjectEntity() {
return new InjectEntity();
}
表明这个方法提供InjectEntity
的依赖。
@Component
Component
是一个注入器。@Inject
需要注入,@Module
提供注入,Component
就是这两者之间的桥梁。被@Component
注解的一定是接口。
@Scope
@Scope
可以自定义注解,通过自定义注解可以限定注解的作用域。
@Qualifier
当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示。
代码
拿Google官方的demo来讲:
@Module
public class AndroidModule {
private final DemoApplication application;
public AndroidModule(DemoApplication application) {
this.application = application;
}
@Provides
@Singleton
@ForApplication
Context provideApplicationContext() {
return application;
}
@Provides
@Singleton
LocationManager provideLocationManager() {
return (LocationManager) application.getSystemService(LOCATION_SERVICE);
}
}
这是一个Module
,用来提供依赖的。看provideApplicationContext
方法,@Provides
注解表明它提供Context
的依赖。当某个Context类型的字段被@Inject
注解时,Dagger2会到Module
中寻找返回值为Context
,并且被@Provides
标注的方法,通过这个方法将依赖注入到需要的类中。
看到这里你可能会有疑问,如果一个Module
中有多个方法被@Provides
标注,并且返回值是Context
类型时,该如何选择?这时就需要用到@Qualifier
标注了,先看下@ForApplication的
具体写法:
@Qualifier @Retention(RUNTIME)
public @interface ForApplication {
}
在提供依赖的方法以及需要依赖注入的字段前都用@ForApplication
标注,这两个就构成了一一对应关系,如果Module
中还有一个返回值为Context
的方法,但是没有用@ForApplication
标注,就可以将其排除。
@ForApplication
只是一个标记,你可以改成别的名字,只要能区别开其他有相同返回值的方法即可。
如下是被依赖字段的@Qualifier
写法:
@Named("ForApplication")
@Provides
Person provideContext(Context context) {
return new Person(context);
}
另一种写法
其实还有一种简单的,不用定义新注解的写法:
@Named("Context")
@Provides
Person provideApplicationContext() {
return new application;
}
@Named("Context")
@Inject
Context context;
在提供依赖的方法以及需要依赖注入的字段前都加上@Named
标注,只要@Name
里面的字符串相同,则构成一一对应的关系。这种写法会比较简便,但是字符串的标记容易导致不匹配,字符串写错了是不会报错的。
再看@Singleton
注解。这是Dagger2自带的一个注解,用于保持在同一个Component
中的对象是单例的。其实你去看@Singleton
的源码,就简单的几行:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
事实上,如果你自己定义一个注解,把Singleton
名字换掉,效果和@Singleton
是一样的。
可以下这样一个结论:
只要Component上添加的@Scope注解和Module中提供依赖的方法上添加的@Scope注解一样,就会保持这个方法返回的对象在该Component实例中是单例的。
这也就是很多使用Dagger2的例子中使用的@PerApp
、@PerActivity
的来历。
public class DemoApplication extends Application {
@Singleton
@Component(modules = AndroidModule.class)
public interface ApplicationComponent {
void inject(DemoApplication application);
void inject(HomeActivity homeActivity);
void inject(DemoActivity demoActivity);
}
@Inject LocationManager locationManager;
private ApplicationComponent component;
@Override public void onCreate() {
super.onCreate();
component = DaggerDemoApplication_ApplicationComponent.builder()
.androidModule(new AndroidModule(this))
.build();
component().inject(this);
}
public ApplicationComponent component() {
return component;
}
}
在DemoApplication
中有一个接口ApplicationComponent
,用@Component
注解表示这是一个Component
,后面的(modules = AndroidModule.class)
指明了与其相关联的Module
。
需要注意的是,Component
必须是一个接口,而且需要引用到目标类的实例,上面代码中的inject()
方法就是为Component
提供目标类的,之所以这样是因为Component
需要目标类的实例来寻找目标类中被@Inject
标记的字段,以便为这些字段到Module
找相应的提供依赖的方法。举个例子:
上述Component中有这个方法
void inject(DemoApplication application);
Component
拿到DemoApplication
的实例后会在其中找到下面这个字段:
@Inject LocationManager locationManager;
然后,Component
会到其对应的Module
中去寻找用@Provides
标记的,并且返回值是LocationManager
的方法,通过找到的这个方法得到LocationManager
的实例。到这里也就完成了依赖的注入。
继续看HomeActivity
的写法:
public class HomeActivity extends DemoActivity {
@Inject LocationManager locationManager;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((DemoApplication) getApplication()).component().inject(this);
Log.d("HomeActivity", locationManager.toString());
}
}
继承自DemoActivity
不用管,对理解Dagger没什么意义。
可以看到有一个被@Inject
注解的字段locationManager
,表示这个字段需要依赖注入。在onCreate
中调用了Component
的inject
方法,传入目标类的引用。流程就像我们上面分析的那样,到Component到Module中寻找@Provides方法,不再赘述。
注意到之前AndroidModule
中提供LocationManager
依赖的方法是加入了Singletion
依赖的,说明所提供的LocationManager
在Component
中是单例的。我们可以再加一个LocationManager
验证一下是否真的是单例(提示一下,这里有个坑,不要被我带进去了)。
把HomeActivity
代码改成如下所示:
public class HomeActivity extends DemoActivity {
@Inject LocationManager locationManager;
//新加一个相同类型的字段,用来验证是否真的是单例的
@Inject LocationManager locationManager2;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((DemoApplication) getApplication()).component().inject(this);
Log.d("HomeActivity", locationManager.toString());
Log.d("HomeActivity", locationManager2.toString());
}
}
运行之后看下打印:
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
嗯,的确是一样的。那如果将Singletion去掉是不是应该就不一样了呢?去掉试下:
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
还是一样的?
额,只能说Google这个例子选的不好,为什么一定要用LocationManager
这个类来举例子呢?翻上去看看provideLocationManager()
这个方法咋写的:
getSystemService(LOCATION_SERVICE);
Android中的系统级别服务采用集合形式的单例模式,所以不管你调多少次,返回的都是同一个实例。其实可以在provideLocationManager()
方法中加一个打印,你会发现加上@Singleton
注解,provideLocationManager()
只调用一次,去掉会调用两次,这样就符合我们预期了。
组件之间的依赖
看一个例子:
@Module
public class SubModule {
@Provides
public InjectEntity provideEntity(Context context) {
return new InjectEntity();
}
}
很显然,SubModule
提供一个InjectEntity
的依赖,但是提供依赖的同时provideEntity
方法还依赖一个Context
对象,如果在SubModule
里有@Provides
方法返回Context
对象,就像下面这样:
@Module
public class SubModule {
private Context mContext;
public SubModule(Context context) {
mContext = context;
}
@Provides
public Context provideContext() {
return mContext;
}
@Provides
public InjectEntity provideEntity(Context context) {
return new InjectEntity();
}
}
这样provideEntity
方法会从provideContext
方法中获得Context
对象的依赖。但是问题是如果SubModule
中就是没有提供Context
依赖的方法怎么办呢?这时会到其所依赖的Component
中寻找:
@Component(modules = MainModule.class)
public Interface MainComponent {
void inject(DemoApplication application);
}
@Module
public class MainModule {
private Context mContext;
public MainModule(Context context) {
mContext = context;
}
@Provide
public Context provideContext() {
return mContext;
}
}
@Component(dependencies = MainComponent.class, Module = SubModule.class)
public interface SubComponent {
void inject(HomeActivity activity);
}
上面代码逻辑很清晰,SubComponent
依赖于MainComponent
,在SubModule
中找不到的依赖会通过MainComponent
到MainModule
中去寻找。
这里有一个需要注意的点:
一个没有作用域(unscoped )的组件不可以依赖有作用域的组件.
组件之间的依赖在Android中一般体现为Activity
或Fragment
作用域的Component
依赖于App作用域的Component
,后者为前两者提供Application
实例或一些需要在App全局使用的单例对象。
以上
参考:
google dagger2 官方参考文档:
https://google.github.io/dagger/
google官方demo地址:
https://github.com/google/dagger/tree/master/examples/android-simple
其他文章: