在这篇文章中,我会介绍 什么是依赖注入,Dagger2是什么,解决什么问题以及基础注解的使用
举个例子
有一个 A 类 它里面定了一个 B 类型的 属性 b; 这里 A 就依赖了 B;
public class A{
public A(){
b = new B();
b.print();
}
private B b;
}
这就意味着 A 离开 B 不能单独运行,也就是说 A 在哪里工作,B就会跟到哪里,A 无法离开 B 被复用。
这种情况下 A 就是 依赖者,B就是依赖。依赖者依赖于它的依赖。
两个相互使用的类称为耦合;耦合有强有弱。耦合总是有方向性的。可能 A 依赖 B,但 B 不一定依赖 A。
在依赖者内部构建或者由依赖者寻找依赖这种就称为 硬编码依赖
关于 什么是依赖,更详细的硬编码依赖的缺点这部分,更详细的可以参考这篇文章,我就是从篇文章学习来的。
https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-introduction-be6580cb3edb
依赖注入:一个对象提供另一个对象的依赖的技术;
依赖是个能被使用的对象(一个服务);注入是将依赖传递给要使用它的对象(客户端 / 依赖者)。
服务作为客户端的一部分。将服务传递给客户端而不是客户端构建或者寻找服务,这是模式(依赖注入)的基本要求。
换句话说:
依赖作为依赖者的一部分。将依赖传递给依赖者而不是由依赖者构建或者寻找依赖,这是依赖注入的基本要求。
也就是说 依赖从来原来的由依赖者构建,改为现在由外部注入,也可以称为 控制反转。
这样的好处是很明显的,提高可测试性,解偶,降低维护成本等等。
更详细的解释 可以看一下这篇文章,解释的超级棒,如果你看过权力的游戏,就更棒了。
https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-di-part-i-f5cc4e5ad878
Dagger2 就是 Android 平台的一个依赖注入框架,它现在由 Google 维护开发。
Dagger2 是编译时框架,会在编译时根据你的注解配置生成需要的代码。
下面是我对 Dagger2 中的常用注解的理解。理解了这些注解的意思和作用,基本就学会了 Dagger2 的基本用法了。
这个注解有两个作用:
public class MainActivity extends AppCompatActivity {
@Inject
DBManager dbManager;
}
public class DBManager {
@Inject
public DBManager(){}
}
这个注解的作用 是连接提供依赖和注入依赖的。相当与一个注射器的角色,将依赖注入到需要的地方。
刚刚通过上面的 @Inject
注解 了 提供依赖的构造方法 和 需要注入的属性,而这样还是不够的,需要使用 @Comnponent
连接起来。
创建一个接口,并定义一个方法,定义要往哪里注入;在编译时期 Dagger2 就会自动生成这个接口的实现类 并以 Dagger 开头。
还可以定义 向外提供实例的方法;Dagger2 都会在编译时期生成相应的代码。
下面是 示例
@Component()
public interface MainComponent {
void inject(MainActivity mainActivity);
DBManager getDBManager();
}
// 在需要被注入的类中注入 例如:
public class MainActivity extends AppCompatActivity {
@Inject
DBManager dbManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注入
DaggerMainComponent.create().inject(this);
}
}
@Component
有两个属性 modules
and dependencies
;
modules
的作用是引用 Module
的,下面 @Module
会继续说dependencies
的作用是 引用其他 Component
使用的,相当于 把其他的 Component
当作组件一样引用过来;顾名思义 就是 Comnponent 的儿子,它也表示一个注射器的角色,不过它可以继承 Component的全部 属性。
Dagger2 不会生成 Dagger开头的 DaggerSubComponent 这种类,所以,SubComponent 需要在 Component 注册和维护。这样的也好统一管理维护,Dagger2 会在生成 Component的时候自动实现生成在内定义的方法。
举个例子 我的 ApplicationComponent 是个全局单例的,有 NetModule, APPModule,等等很多全局性依赖,如果我的 Activity 的注射器 使用 @SubComnponent
,那么就可以使用Application的全部依赖。
@ActivityScoped
@Subcomponent(modules = MainModule.class)
public interface MainComponent {
void inject(MainActivity mainActivity);
}
@APPScoped
@Component(modules = {APPModule.class, APIModule.class} )
public interface APPComponent {
MainComponent plus(MainModule module);
SecondComponent plus(SecondModule module);
}
//注入
DaggerAPPComponent.builder()
.aPPModule(new APPModule(getApplication()))
.build()
.plus(new SecondModule())
.inject(this);
当然还有另外一种方法不用 @SubComponent
,使用 Component
并使用 denpendencies
引用上 ApplicationComponent
这样就相当于将 ApplicationComponent
组合进来。
@Module
这个注解用来标注提供依赖的工厂。对的,工厂,我是这么理解的。
@Provides
这个注解用在提供定义提供依赖的方法上,表示向外提供依赖。方法的返回类型就是提供的依赖类型。
前面提到的 @Inject
可以在注解在构造函数以用来提供依赖;而在 @Inject
不能满足需要的时候这个就派上用场了。
例如 我注入一个 字符串,数字或一个 第三方依赖的对象 例如 Retrofit ,@Inject
已经满足不了啦。
这个时候可以创建一个类 专门用来提供这些依赖,并使用 @Module
注解,然后在 Component
的属性 modules
引用上就可以使用了。
// 需要注入的 Activity
public class ThirdActivity extends AppCompatActivity {
@Inject String name;
@Inject int age;
@Inject
OkHttpClient client;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
DaggerThirdComponent.create().inject(this);
Log.e(ThirdActivity.class.getSimpleName(), "onCreate: name-"+name+";age-"+age+";client-"+client);
}
}
// 提供依赖的 工厂
@Module
public class ThirdModule {
@Provides
public String provideName(){
return "skymxc";
}
@Provides
public int provideAge(){
return 24;
}
@Provides
public OkHttpClient provideOkHttpClient(){
OkHttpClient client = new OkHttpClient.Builder().build();
return client;
}
}
// 连接 依赖和注入方 ,在这里引用 依赖提供方。
@Component(modules = ThirdModule.class)
public interface ThirdComponent {
void inject(ThirdActivity activity);
}
在依赖迷失时给出方向。
解释一下 依赖迷失:
依旧是上面那个例子,现在 都是根据返回值类型来注入的,现在都是不同的类型所以还没有出现迷失的情况; 现在我如果要加上 地址 属性;如下
// activity内
@Inject String name;
@Inject int age;
@Inject
OkHttpClient client;
@Inject String address;
// module 中
@Provides
public String provideName(){
return "skymxc";
}
@Provides
public int provideAge(){
return 24;
}
public String provideAddress(){
return "北京";
}
这个时候 在 module 中 有两个返回 String 类型的 方法,Dagger2 这个时候就不知道注入哪一个了,所以就会出现 依赖迷失 的情况;
错误: [Dagger/DuplicateBindings] java.lang.String is bound multiple times:
@Provides String com.skymxc.example.dagger2.di.module.ThirdModule.provideAddress()
@Provides String com.skymxc.example.dagger2.di.module.ThirdModule.provideName()
简单的解决方法就是在 属性和提供依赖上 加上 @Named
注解
@Named("name")
@Provides
public String provideName(){
return "skymxc";
}
@Provides
@Named("address")
public String provideAddress(){
return "北京";
}
// 在 属性上也加上
@Named("name")
@Inject String name;
@Named("address")
@Inject String address;
这样就可以解决了 依赖迷失。
@Named
的元注解,解决依赖迷失的大 Boss;看一下 @Named
的源码,@Named
就是被 @Qualifier
注解的。
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
如果怕通过 @Named
写字符串的方式容易出错就可以通过 @Qualifier
自定义注解来实现。
下面举个例子,再加一个 身高属性。定义两个注解来区分 @Age
and @Height
.
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Height {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Age {
}
//在 module 和 属性上使用
@Age
@Provides
public int provideAge(){
return 24;
}
@Provides
@Height
public int provideHeight(){
return 175;
}
@Age
@Inject int age;
@Height
@Inject int height;
配合 @Component
实现 范围内单例
@Singleton
必须和 @Component
配合才能实现单例,而且只能保证在 @Component
范围内单例,如果要实现全局单例,就必须要保证 @Component
的实例在全局范围内只有一个,类似 Application 。
举个例子,我要 DBManager
在全局单例,需要以下几个步骤
@Singleton
或者 在 @Provides 修饰的方法上加。// 1.DBManager 标注 @Singleton
@Singleton
public class DBManager {
@Inject
public DBManager(){}
}
// 2.
@Singleton
@Component(modules = {APPModule.class, APIModule.class})
public interface APPComponent {
MainComponent plus(MainModule module);
SecondComponent plus(SecondModule module);
//可有可无 为了测试
DBManager getDBManager();
}
//3. 在 Application 中获取 实例,并保证唯一实例
public class MApplication extends Application {
private APPComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAPPComponent.builder()
.aPPModule(new APPModule(this))
.build();
}
public APPComponent getAppComponent() {
return appComponent;
}
}
// 测试,在 MainActivity 注入两个。
public class MainActivity extends AppCompatActivity {
@Inject
DBManager dbManager;
@Inject DBManager dbManager1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//使用 Application 获取 AppComponent
((MApplication)getApplication()).getAppComponent()
.plus(new MainModule())
.inject(this);
Log.e(MainActivity.class.getSimpleName(), "onCreate: appdb-->"+((MApplication)getApplication()).getAppComponent().getDBManager().hashCode());
//是否是全局范围内单例
if (dbManager==dbManager1) {
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager-sintleton->"+dbManager.hashCode());
}else{
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}
}
}
// 在 SecondActivity 注入两个看看是否和 Main 中的是一个实例
public class SecondActivity extends AppCompatActivity {
@Inject
DBManager dbManager;
@Inject
DBManager dbManager1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
((MApplication)getApplication()).getAppComponent()
.plus(new SecondModule())
.inject(this);
if (dbManager==dbManager1) {
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager-singleton->"+dbManager.hashCode());
}else{
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}
}
}
测试结果必须是全局唯一单例,看一下 log
E/MainActivity: onCreate: appdb-->192114699
onCreate: dbmanager-sintleton->192114699
E/SecondActivity: onCreate: dbmanager-singleton->192114699
@Singleton
的作用域 始终是跟随所在的 Component
的实例的,如果超出它的范围就无法保证单例。
就拿上个例子举例,如果每次 在 Activity 注入的时候 不从 Application 获取实例而是每次都是使用 DaggerAppComponent 创建一个新的 实例 ,那么就无法保证两个 Activity 内的 DBManager 都是一个实例了,因为每个 Activity 都是获取新的 AppComponent 的实例,它的作用范围只能在单个实例内。
下面我实现一个 只在 Activity 范围实现单例的 例子,就是把上面的代码改改,在Activity注入的时候 创建新的 Component 实例。
public class SecondActivity extends AppCompatActivity {
@Inject
DBManager dbManager;
@Inject
DBManager dbManager1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
// ((MApplication)getApplication()).getAppComponent()
// .plus(new SecondModule())
// .inject(this);
// 获取新实例
DaggerAPPComponent.builder().aPPModule(new APPModule(getApplication())).build().plus(new MainModule()).inject(this);
if (dbManager==dbManager1) {
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager-singleton->"+dbManager.hashCode());
}else{
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}
}
}
public class MainActivity extends AppCompatActivity {
@Inject
DBManager dbManager;
@Inject DBManager dbManager1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取新实例
DaggerAPPComponent.builder().aPPModule(new APPModule(getApplication())).build().plus(new MainModule()).inject(this);
if (dbManager==dbManager1) {
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager-sintleton->"+dbManager.hashCode());
}else{
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}
}
}
// log
09-23 00:02:52.937 E/DBHelper: DBHelper:
09-23 00:02:52.937 E/MainActivity: onCreate: dbmanager-sintleton->115289709
09-23 00:02:57.097 E/DBHelper: DBHelper:
09-23 00:02:57.097 E/SecondActivity: onCreate: dbmanager-singleton->64826129
总结 : Dagger2 实现单例要 @Singleton
和 @Component
|| @SubComponent
配合使用,只能实现范围内(实例内)单例,所以范围要控制好。只要范围控制好,随意 Activity 或者 Application 范围。
作用域 上面说到的 @Singleton
就是它的默认实现,也是唯一一个默认实现。
看一下 @Singleton
的源码
/**
* Identifies a type that the injector only instantiates once. Not inherited.
*
* @see javax.inject.Scope @Scope
*/
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
@Singleton
能够实现范围内单例 主要是 @Scope
在起作用。默认实现叫 Singleton
也是为了更好的理解。
我们可以根据自己的情况,自定义我们自己的依赖作用域,就像我们上面说的 跟随 Application 生命周期的,跟随 Activity 生命周期的,或者 User 生命周期的等等。
举个例子 我们定义俩个 AppScoped, ActivityScoped. 分别让我们的依赖实现 全局单例和Activity内单例
/**
* APP全局单例
* 此注解使用的 Component 要全局范围内唯一 ,不然无法实现全局单例
*/
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface APPScoped {
}
/**
* activity 内单例
* 使用 此注解的Component 生命周期要跟随 Activity 的生命周期。
*/
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScoped {
}
@ActivityScoped
public class SingletonObj {
@Inject
public SingletonObj(){}
}
/**
*
*/
@APPScoped
public class DBManager {
@Inject DBHelper helper;
@Inject
public DBManager(){}
}
@APPScoped
@Component(modules = { APIModule.class,APPModule.class})
public interface APPComponent {
MainComponent plus(MainModule module);
SecondComponent plus(SecondModule module);
DBManager getDBManager();
}
@ActivityScoped
@Subcomponent(modules = MainModule.class)
public interface MainComponent {
void inject(MainActivity mainActivity);
}
@ActivityScoped
@Subcomponent(modules = SecondModule.class)
public interface SecondComponent {
void inject(SecondActivity activity);
}
在 Application 获取 AppComponent 的实例 ,并保持唯一。
public class MApplication extends Application {
private APPComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAPPComponent.builder()
.aPPModule(new APPModule(this))
.build();
}
public APPComponent getAppComponent() {
return appComponent;
}
}
在 MainActivity 获取到 MainComponent 的实例 并注入
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
@Inject
DBManager dbManager;
@Inject DBManager dbManager1;
@Inject
SingletonObj mainSingleton;
@Inject
SingletonObj mainSingleton1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.bt_to_second).setOnClickListener(this);
findViewById(R.id.bt_to_third).setOnClickListener(this);
((MApplication)getApplication()).getAppComponent()
.plus(new MainModule())
.inject(this);
Log.e(MainActivity.class.getSimpleName(), "onCreate: appdb-->"+((MApplication)getApplication()).getAppComponent().getDBManager().hashCode());
//查看 是否和 second的一致,是否是全局范围内单例
if (dbManager==dbManager1) {
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager-sintleton->"+dbManager.hashCode());
}else{
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(MainActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}
//主要看 这个 和 second的是否一致,是否是activity范围内单例。
if (mainSingleton==mainSingleton1){
Log.e(MainActivity.class.getSimpleName(), "onCreate: main-singleton->"+mainSingleton.hashCode());
}else{
Log.e(MainActivity.class.getSimpleName(), "onCreate: main:"+mainSingleton.hashCode());
Log.e(MainActivity.class.getSimpleName(), "onCreate: main1:"+mainSingleton1.hashCode());
}
}
}
在 SecondActivity 获取到 SecondComponent 的实例 并注入 ,这里就可以看出来 是否是 范围内单例。
public class SecondActivity extends AppCompatActivity {
@Inject
DBManager dbManager;
@Inject
DBManager dbManager1;
@Inject
SingletonObj mainSingleton;
@Inject
SingletonObj mainSingleton1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
((MApplication)getApplication()).getAppComponent()
.plus(new SecondModule())
.inject(this);
if (dbManager==dbManager1) {
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager-singleton->"+dbManager.hashCode());
}else{
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager:"+dbManager.hashCode());
Log.e(SecondActivity.class.getSimpleName(), "onCreate: dbmanager1:"+dbManager1.hashCode());
}
if (mainSingleton==mainSingleton1){
Log.e(SecondActivity.class.getSimpleName(), "onCreate: main-singleton>"+mainSingleton.hashCode());
}else{
Log.e(SecondActivity.class.getSimpleName(), "onCreate: main:"+mainSingleton.hashCode());
Log.e(SecondActivity.class.getSimpleName(), "onCreate: main1:"+mainSingleton1.hashCode());
}
}
}
log 可以看出 范围内单例
E/MainActivity: onCreate: appdb-->229426894
onCreate: dbmanager-sintleton->229426894
onCreate: main-singleton->142055919
E/SecondActivity: onCreate: dbmanager-singleton->229426894
onCreate: main-singleton>241744847
总结 :我们可以通过 @Scope
随意自定义我们自己的作用域,当然不是说我们定义了 ActivityScoped 他就能保证 Activity内单例了,要配合 Component 范围并用对位置。
这些Demo 的代码 我放在了 Github
基础部分就先介绍这些吧,接下来我会继续 Dagger2-Android 的分享。