Dagger2使用攻略-基础部分

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

内容简介:在这篇文章中,我会介绍 什么是依赖注入,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 的分享。

参考资料


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

查看所有标签

猜你喜欢:

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

Clean Code

Clean Code

Robert C. Martin / Prentice Hall / 2008-8-11 / USD 49.99

Even bad code can function. But if code isn’t clean, it can bring a development organization to its knees. Every year, countless hours and significant resources are lost because of poorly written code......一起来看看 《Clean Code》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具