Dagger2使用攻略-基础部分

栏目: IOS · Android · 发布时间: 5年前

内容简介:在这篇文章中,我会介绍 什么是依赖注入,Dagger2是什么,解决什么问题以及基础注解的使用举个例子

在这篇文章中,我会介绍 什么是依赖注入,Dagger2是什么,解决什么问题以及基础注解的使用

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 的基本用法了。

常用注解

@Inject

这个注解有两个作用:

  • 修饰需要注入的属性,Dagger2 会自动注入
  • 修饰被注入的类的构造方法上;Dagger2 会在需要的时候通过这个注解找到构造函数自动构造对象注入
public class MainActivity extends AppCompatActivity {

    @Inject
    DBManager dbManager;

}

public class DBManager {


    @Inject
    public DBManager(){}
}

@Component

这个注解的作用 是连接提供依赖和注入依赖的。相当与一个注射器的角色,将依赖注入到需要的地方。

刚刚通过上面的 @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 当作组件一样引用过来;

@SubComponent

顾名思义 就是 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

@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);

}

@Named

在依赖迷失时给出方向。

解释一下 依赖迷失

依旧是上面那个例子,现在 都是根据返回值类型来注入的,现在都是不同的类型所以还没有出现迷失的情况;

现在我如果要加上 地址 属性;如下

// 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;

这样就可以解决了 依赖迷失。

@Qualifier

@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;

@Singleton

配合 @Component 实现 范围内单例

@Singleton 必须和 @Component 配合才能实现单例,而且只能保证在 @Component 范围内单例,如果要实现全局单例,就必须要保证 @Component 的实例在全局范围内只有一个,类似 Application 。

举个例子,我要 DBManager 在全局单例,需要以下几个步骤

@Singleton
// 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 范围。

@Scope

作用域 上面说到的 @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 {
}
  1. 创建一个类 SingletonObj 让其在 Activity范围内 单例, 让 DBManager 全局单例
@ActivityScoped
public class SingletonObj {


    @Inject
    public SingletonObj(){}
}

/**
 * 
 */
@APPScoped
public class DBManager {

    @Inject DBHelper helper;

    @Inject
    public DBManager(){}
}
  1. 定义 Component ,注意 AppScoped , ActivityScoped 的位置
@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);
}
  1. 获取 Component 并开始注入

在 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 的分享。

参考资料


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

咨询的奥秘

咨询的奥秘

[美] 杰拉尔德·温伯格(Gerald M.Weinberg) / 李彤、关山松 / 清华大学出版社 / 2004 / 29.00元

一起来看看 《咨询的奥秘》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

SHA 加密
SHA 加密

SHA 加密工具