探究 Dagger2

Dagger2Dagger的分支,早期有square开发,现在由谷歌公司接手维护。主要实现了依赖注入(DI)的思想,对象不在具体使用的地方进行实例化,而是在其他地方进行统一管理。最大限度的进行解耦。

Dagger2 使用注解的形式来标示注入和提供实例等操作,但基于运行效率,混淆问题等原因的考虑,Dagger2去除了Dagger1中使用的反射,选择了编译时注解,而非运行时注解。

首先来看看Dagger提供的几个注解:

  • @Inject:在需要注入的地方(目标类)添加这个注解,比如:MainActivity要使用LocalManager实例,那我们就在要使用的地方定义一个成员变量。

    @Inject
    LocationManager locationManager;//不能同private修饰

    另外@Iject 还用来标注所依赖类的构造函数,在自动实例化对象的时候调用哪个构造函数。

  • @Component:目标类所需要的实例与生产实例的地方需要一个桥梁来把他们理解起来,@Component 就是充当这个桥梁的作用。Compont需要引用到目标类所需要的实例,就会查找目标类中用Inject注解标注的属性,查找到相应的属性后会接着查找该属性对应的用Inject标注的构造函数(如果下面要提到的Module没有提供的话),剩下的工作就是初始化该属性的实例并把实例进行赋值。(以上查找的操作都是在编译期执行)。

测试1

有了上面两个注解,现在就可以做个最简单的测试demo了:

1.新建工程,根据https://github.com/google/dagger#android-gradle的说明进行配置工程。

 // Add plugin https://bitbucket.org/hvisser/android-apt
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
  }
}

// Apply plugin
apply plugin: 'com.neenbedankt.android-apt'

// Add Dagger dependencies
dependencies {
  compile 'com.google.dagger:dagger:2.6'
  apt 'com.google.dagger:dagger-compiler:2.6'
}

APT用来在编译时处理注解,并自动生成辅助代码。

2.新建一个实体类TestBean,TestBean将被用来自动生成并被注入到目标类中。

public class TestBean {
    private String msg;
    //标示用这个构造函数 进行自动实例化(创建对象)
    @Inject
    public TestBean() {
        msg = "hello world";
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

}

3.创建Component,来连接目标类和依赖类。

//用这个标注标识是一个连接器
@Component
public interface MainActivityComponent {
    //这个函数将在生成的代码中进行实现,来对被注入的值进行赋值(注入)
    void inject(MainActivity activity);
}

4.点击菜单栏Build->Make project来使apt处理注解,并生成辅助代码。

5.在MainActivity中进行注入并使用。

public class MainActivity extends AppCompatActivity {

    @Inject
    TestBean testBean;//自动注入

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerMainActivityComponent.builder().build().inject(this);

        TextView textView = (TextView) findViewById(R.id.text);
        textView.setText(testBean.getMsg());
    }
}

该部分代码 --> Link

OK,最简单的使用方法就完成了。

现在来看看Dagger具体是怎么实现的呢。
首先来看一下Dagger帮我们生成哪些代码:

可以看到,一共生成了三个class:TestBean_Factory, DaggerMainActivityComponent, MainActivity_MembersInjector.

TestBean_Factory对应着实体类TestBean,是使用枚举实现了一个单例TestBean_Factory(TestBean在这里不是单例).

public enum TestBean_Factory implements Factory<TestBean> {
  INSTANCE;

  @Override
  public TestBean get() {
    return new TestBean();
  }

  public static Factory<TestBean> create() {
    return INSTANCE;
  }
}

DaggerMainActivityComponent实现了前面写的MainActivityComponent接口,使用Builder(建造者模式)创建DaggerMainActivityComponent实例,内部有一个MainActivity_MembersInjector对象,实现的方法inject()则直接调用了mainActivityMembersInjector的injectMembers方法。

public final class DaggerMainActivityComponent implements MainActivityComponent {
  private MembersInjector<MainActivity> mainActivityMembersInjector;

  private DaggerMainActivityComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static MainActivityComponent create() {
    return builder().build();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.mainActivityMembersInjector =
        MainActivity_MembersInjector.create(TestBean_Factory.create());
  }

  @Override
  public void inject(MainActivity activity) {
    mainActivityMembersInjector.injectMembers(activity);
  }

  public static final class Builder {
    private Builder() {}

    public MainActivityComponent build() {
      return new DaggerMainActivityComponent(this);
    }
  }
}

MainActivity_MembersInjector 是真正的注入器,实例在DaggerMainActivityComponent中创建,injectMembers函数的一个参数是MainActivity,直接可以通过intance.xx = xxx.get(); 来对MainActivity中的对象进行赋值。

public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
  private final Provider<TestBean> testBeanProvider;

  public MainActivity_MembersInjector(Provider<TestBean> testBeanProvider) {
    assert testBeanProvider != null;
    this.testBeanProvider = testBeanProvider;
  }

  public static MembersInjector<MainActivity> create(Provider<TestBean> testBeanProvider) {
    return new MainActivity_MembersInjector(testBeanProvider);
  }

  @Override
  public void injectMembers(MainActivity instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.testBean = testBeanProvider.get();
  }

  public static void injectTestBean(MainActivity instance, Provider<TestBean> testBeanProvider) {
    instance.testBean = testBeanProvider.get();
  }
}

如果想保证TestBean是个单例呢?
Dagger提供了@Singleton 注解,将@Singleton注解加到TestBean和MainActivityComponent上,就可以保证在同一个Component下,多个注入值是相同的对象。

而在实现上,DaggerMainActivityComponent中用DoubleCheck对TestBean_Factory进行了包装,DoubleCheck中的get方法对第一次创建的对象进行了缓存,再次去取的时候直接从缓存中返回,从而实现“单例”。

@Module 项目中使用到了第三方的类库,第三方类库又不能修改,所以根本不可能把Inject注解加入这些类中,这时我们的Inject就失效了。 那我们可以封装第三方的类库,封装的代码怎么管理呢,总不能让这些封装的代码散落在项目中的任何地方,总得有个好的管理机制,那Module就可以担当此任。

Module其实是一个简单工厂模式,Module的职责就是创建并提供类实例。

Module中的创建对象的函数用@Provides进行标注,Component在搜索到目标类中用Inject注解标注的属性后,Component就会去Module中去查找用Provides标注的对应的创建类实例方法,这样就可以解决第三方类库用dagger2实现依赖注入了。

简单依赖注入

首先做一个全局的Module,AppApplicationModule:

@Module
public class AppApplicationModule {
    private final Context context;

    public AppApplicationModule(Context context) {
        this.context=context;
    }

    //标示这个方法可以提供LocationManager
    @Provides
    @Singleton
    LocationManager provideLocationManager() {
        return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
    }

    @Provides
    @Singleton
    Gson provideGson(){//函数名可以不以provide开头 但是@Provides是必须的
        return new Gson();
    }
}

AppApplicationModule提供了LocationManager和Gson两个单例对象(LocayionManager貌似本来就是单例,只是省去getSystemService的操作)。

然后看一下Component:

@Singleton
@Component(modules = {AppApplicationModule.class})
public interface ApplicationComponent {
    void inject(MyApplication application);
    void inject(MainActivity activity);
}

提供了两个方法,第一个用于对Application里的对象进行注入。第二个用于对MainActivity。注意,不能写成注入点所在类的父类!!!

现在看一下怎么注入:

public class MyApplication extends Application{

    private ApplicationComponent component;

    private static MyApplication instance;

    public static MyApplication getInstance() {
        return instance;
    }

    @Inject
    LocationManager locationManager;

    @Inject
    Gson gson;

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
        component = DaggerApplicationComponent.builder()
                .appApplicationModule(new AppApplicationModule(this))
                .build();
        component.inject(this);
        Log.i("", "locationManager:"+Integer.toHexString(locationManager.hashCode()));
        Log.i("", "gson:"+Integer.toHexString(gson.hashCode()));
    }

    public ApplicationComponent getComponent() {
        return component;
    }
}

现在就可以直接运行一下试试了。

还要在MainActivity里面把这俩单例注入:

public class MainActivity extends AppCompatActivity {

    @Inject
    LocationManager locationManager;

    @Inject
    Gson gson;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyApplication.getInstance().getComponent().inject(this);
        TextView textView = (TextView) findViewById(R.id.text);

        Log.i("", "locationManager:"+Integer.toHexString(locationManager.hashCode()));
        Log.i("", "gson:"+Integer.toHexString(gson.hashCode()));

        Log.i("", "locationManager:"+locationManager.toString());
        textView.setText(locationManager.toString());
        textView.append("\n");
        textView.append(gson.toString());
    }
}

可以看一下在MyApplication和MainActivity里面的gson对象是否是同一个。

代码

多层依赖

一般情况下,我们需要为每个Activity或者Fragment都创建一个Component,还有诸如网络请求我们也需要创建一个Component等等,而我们又不可能在Activity里面进行多次inject,这时候我们可能就需要使用到多层依赖了。

Dagger2允许使用component作为component的依赖,实现多层级的依赖注入。

我们创建一个MainActivityComponent:

@Component(modules = MainActivityModule.class, dependencies = ApplicationComponent.class)
@ActivityScoped
public interface MainActivityComponent {
    //这个函数将在生成的代码中进行实现,来对被注入的值进行赋值(注入)
    void inject(MainActivity mainActivity);
}

MainActivityModule.java

@Module
public class MainActivityModule {
    private Context context;
    public MainActivityModule(Context context){
        this.context = context;
    }

    @Provides
    Context provideContext(){
        return context;
    }
}

在这里继承了ApplicationComponent.class,Dagger2支持继承多个Component,如dependencies={A.class,B.class}

对于这个ActivityScoped注解,因为ApplicationComponent有Singleton注解,所以这里也是必须加上的,,,原因看说明。。。

In Dagger, an unscoped component cannot depend on a scoped component. We create a custom scope to be used by all fragment components. Additionally, a component with a specific scope cannot have a sub component with the same scope.

但是现在ApplicationComponent.class只是提供了inject方法,而没有提供gson的的依赖。我们需要将AppApplicationModule提供的LocationManager和Gson 传递到 MainActivityComponent。

修改后:

@Singleton
@Component(modules = {AppApplicationModule.class})
public interface ApplicationComponent {
    void inject(MyApplication application);
    void inject(MainActivity activity);
    LocationManager getLocationManager();
    Gson getGson();
}

在MainAcitivity里面进行注入:

public class MainActivity extends AppCompatActivity {

    @Inject
    LocationManager locationManager;

    @Inject
    TestBean testBean;

    @Inject
    Gson gson;

    @Inject
    Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerMainActivityComponent.builder()
                .mainActivityModule(new MainActivityModule(this))
                .applicationComponent(MyApplication.getInstance().getComponent())
                .build()
                .inject(this);
        TextView textView = (TextView) findViewById(R.id.text);

        Log.i("", "locationManager:"+Integer.toHexString(locationManager.hashCode()));
        Log.i("", "gson:"+Integer.toHexString(gson.hashCode()));

        Log.i("", "locationManager:"+locationManager.toString());
        textView.setText(locationManager.toString());
        textView.append("\n");
        textView.append(testBean.getMsg());
        textView.append("\n");
        textView.append(gson.toString());
    }
}

PS:这里有点小问题,最终的inject(this)竟然走到了ApplicationComponent的inject()..囧,,,没法,先把ApplicationComponent中的void inject(MainActivity activity);删去吧。。。

这里成功注入了用@inject提供注入的,使用Module注入的,以及继承的Component对应得Module提供的实例。

代码

Qualifier 用来解决需要不同的实例化方式的对象在同一个Scope进行注入的带来的问题。

Dagger2 运用在在MVP架构中时,能够使得结构更加清晰,各个模块各司其事。

其他DI库:

  1. butterknife:View及监听事件注入
  2. tiger:Google 推出的号称fastest Java DI framework.

其他资料:
Dagger 2 精彩概述

Dagger 2 中的作用域

详解Dagger2

todo-mvp-dagger架构Demo

史上最通俗易懂的Android中使用Dagger入门教程

Android:dagger2让你爱不释手

Avengers