美文网首页
Dagger2使用姿势学习

Dagger2使用姿势学习

作者: m1Ku | 来源:发表于2019-06-10 10:28 被阅读0次

前言

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的注入过程

  1. 使用@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", "我在握手了");
    }
}

  1. 使用@Component标注Component接口
@Component
public interface MainComponent {
    void inject(MainActivity activity);
}

make项目,让代码重新编译,编译过程Dagger2会生成依赖注入实际起作用的代码,这些代码后面再看。

  1. 在注入目标类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

  • 标注在构造方法上

    1. 表示Dagger2可以使用这个构造方法构造对象,如Finger类。
    2. 可以用来注入当前构造方法所需要的依赖,如Hand类构造参数依赖Finger类。
    3. 如果存在多个构造方法,@Inject注解仅可以标注其中一个。
  • 标注在属性上

    表示这个属性是需要被注入的,该属性不能用private来修饰。

  • 标注在方法上

    除了属性注入,Dagger2也可以使用方法注入,方法注入会在目标类构造方法执行后执行。

    上面MainActivity的hand也可以使用方法注入,如下

    @Inject
    public void setHand(Hand hand){
        this.hand = hand;
    }
    

@Component

用来标注在一个接口上,接口中定义inject方法,方法的参数就是目标类对象。编译后,会生成其的实现类,主要注入逻辑在该实现类中完成,后面会看其代码。

@Module和@Provides的使用

在上面的注入过程中,我们都是在依赖的构造方法上使用@Inject标注,那么如果我们想注入系统提供的类或者第三方的类该怎么办呢?这时就不能标注其构造方法了,此时就要借助@Module和@Provides注解来完成依赖的注入了。

下面使用Dagger2将系统的Date日期类注入到MainActivity中

  1. 使用@Module标注Module类
@Module
public class MainModule {

}
  1. 在Module类中定义一个用@Provides标注的方法,在方法中实例化Date类,其返回值为Date类型
@Module
public class MainModule {
    @Provides
    public Date provideDate() {
        return new Date();
    }
}
  1. 为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框架的。即使不用但也要会呀,等下开一篇来看下它的源码实现,如果后面碰到新的使用姿势再回来补下。

相关文章

网友评论

      本文标题:Dagger2使用姿势学习

      本文链接:https://www.haomeiwen.com/subject/cipaxctx.html