一点关于dagger2在项目中使用的总结

dagger是一款ioc注入框架,相比于butterknife只能进行控件和事件注入,它可以进行任意对象的注入,对于项目的解耦是非常方便的,在中大型项目中使用得比较多,相较于其它第三方库,这个库的入门槛还是比较高的,如果不熟悉它的使用,是很容易配置报错的,因为它里面有很多规则是需要遵守的,如果不按照它的要求来,导致的结果就是编译不通过。最近打算总结下关于它的基本使用,以及在项目中使用的建议。


基本使用

在使用之前先来说下module和component,前者是用来提供创建好的对象注入给使用者调用,而component是用来做注入操作的,起到连接作用。

说完了module和component,就来说下基本使用

添加依赖

annotationProcessor "com.google.dagger:dagger-compiler:2.4"
implementation "com.google.dagger:dagger:2.4"

module提供对象

@Module
public class MainModule {

    @Provides
    public Teacher provideTeacher(){
        return new Teacher();
    }
    
    @Provides
    public Student provideStudent(Teacher teacher){
        return new Student(teacher);
    }

}

提供的module需要使用@Module注解标注,在这个类容器中,提供了两个对象,Student是依赖Teacher的,提供的方法使用@Provides标注。

component注入操作

@Component(modules = {MainModule.class})
public interface MainComponent {
    void injectActivity(MainActivity activity);
}

负责提供注入的接口,需要使用@Component标注,同时指定绑定的Modules,接口中定义注入的方法,可以任意名称,但是参数指定为目标类,注意这个参数要写死为需要注入的目标类,每注入一个对象就需要单独写一个注入方法。

对象注入

public class MainActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;
    @Inject
    Student student;

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

        DaggerMainComponent.create().injectActivity(this);  //1

        System.out.println("teacher--->>>" + teacher.hashCode());
        System.out.println("student---->>>" + student.hashCode());
    }
}

使用@Inject标记的对象,在编译扫描时就会给标记的对象赋值,使用这些对象之前,先要在注释1处做注入操作,具体的赋值操作就在这里进行。


局部单例

dagger有个隐藏很深的“坑”,就是局部单例技术,接下来就用dagger提供的@Singleton来重现这个"坑"

module用@Singleton标注后的样子

@Singleton
@Module
public class MainModule {

    @Singleton
    @Provides
    public Teacher provideTeacher() {
        return new Teacher();
    }

    @Singleton
    @Provides
    public Student provideStudent(Teacher teacher) {
        return new Student(teacher);
    }
}

component用@Singleton标注后的样子

@Singleton
@Component(modules = {MainModule.class})
public interface MainComponent {

    void injectActivity(MainActivity activity);
    void injectActivity(SecondActivity activity);

}

然后在MainActivity和SecondActivity中做注入操作

public class MainActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;
    @Inject
    Student student;

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

        DaggerMainComponent.create().injectActivity(this);

        System.out.println("main-teacher--->>>" + teacher.hashCode());
        System.out.println("main-student---->>>" + student.hashCode());
    }
}

public class SecondActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;
    @Inject
    Student student;

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

        DaggerMainComponent.create().injectActivity(this);

        System.out.println("second-teacher--->>>" + teacher.hashCode());
        System.out.println("second-student---->>>" + student.hashCode());
    }
}

接着运行代码看下效果

main-teacher--->>>145054534
main-student---->>>240869639

second-teacher--->>>240548302
second-student---->>>121819375

我们可以看到两个Activity的hashCode是不一样的,这时候单例的效果就失效了,为什么单例会失效?只有从它注入处的源码来看下效果。

private void initialize(final Builder builder) {

  this.provideTeacherProvider =
      DoubleCheck.provider(MainModule_ProvideTeacherFactory.create(builder.mainModule));

  this.provideStudentProvider =
      DoubleCheck.provider(
          MainModule_ProvideStudentFactory.create(builder.mainModule, provideTeacherProvider));

  this.mainActivityMembersInjector =
      MainActivity_MembersInjector.create(provideTeacherProvider, provideStudentProvider);

  this.secondActivityMembersInjector =
      SecondActivity_MembersInjector.create(provideTeacherProvider, provideStudentProvider);
}

dagger注入的时候都会调用这个initialize方法,这个方法里面加了单例@Singleton之后多了DoubleCheck.provider这部分代码,看下它是怎么实现的

public static <T> Provider<T> provider(Provider<T> delegate) {
  checkNotNull(delegate);
  //1
  if (delegate instanceof DoubleCheck) {
    /* This should be a rare case, but if we have a scoped @Bind that delegates to a scoped
     * binding, we shouldn't cache the value again. */
    return delegate;
  }
  //2
  return new DoubleCheck<T>(delegate);
}

第一次进来注释1处的if肯定不成立,走注释2处的代码

private DoubleCheck(Provider<T> provider) {
  assert provider != null;
  this.provider = provider;
}

@SuppressWarnings("unchecked") 
@Override
public T get() {
  Object result = instance;
  if (result == UNINITIALIZED) {
    synchronized (this) {
      result = instance;
      if (result == UNINITIALIZED) {
        instance = result = provider.get();
        provider = null;
      }
    }
  }
  return (T) result;
}

主要看get部分代码,里面有个双重检查,第一次肯定是走到if里面从provider中获取Object对象,然后赋值给instance,下次进来时候result先从instance获取缓存的值,if就不成立了,此时直接返回刚刚的缓存值。由于我们在两个Activity中的DaggerMainComponent是不一样的,这时候就实现不了单例了。


解决方案

关于上面的一个解决方案就是将DaggerMainComponent的创建提升到Application中,而注入只需要在对应的代码中使用就行了。

public class MyApp extends Application {

    private static MainComponent component;

    @Override
    public void onCreate() {
        super.onCreate();

        component = DaggerMainComponent.create();
    }

    public static MainComponent getComponent() {
        return component;
    }
}

注入

public class MainActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;
    @Inject
    Student student;

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

        MyApp.getComponent().injectActivity(this);
    }
}

运行效果

main-teacher--->>>145054534
main-student---->>>240869639

second-teacher--->>>145054534
second-student---->>>240869639

此时注入两个Activty的Teacher和Student都是同一个单例对象

对于单例一般不建议使用Singleton,而是自定义Scope,使用Singleton需要遵守框架里面的很多规则,而使用自定义Scope只需要遵守两条规则:1.多个组件之间的scope不能相同,2.没有scope的不能依赖有scope的组件

后面就使用自定义Scope来实现单例。


多个Component依赖

可能存在这样一个问题就是有两个或者多个Component想在同一个Activity中注入,此时该怎么实现了?还是直接上例子

此时MainComponent想要依赖一个PresenterComponent,那么就可以使用@Component的dependencies来依赖PresenterComponent

@Component(modules = {MainModule.class}, dependencies = {PresenterComponent.class})

PresenterComponent部分代码改成这样

@Component(modules = PresenterModule.class)
public interface PresenterComponent {

    Presenter providePresenter();
}

然后MyApp中component创建代码改成这样的

component = DaggerMainComponent.builder()
        .mainModule(new MainModule())
        .presenterComponent(DaggerPresenterComponent.create())
        .build();

presenterComponent也就是依赖的Component是必须要提供的,不然报错

throw new IllegalStateException(
            PresenterComponent.class.getCanonicalName() + " must be set");

页面注入

public class MainActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;
    @Inject
    Student student;
    @Inject
    Presenter presenter;

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

        MyApp.getComponent().injectActivity(this);

        System.out.println("main-teacher--->>>" + teacher.hashCode());
        System.out.println("main-student---->>>" + student.hashCode());
        System.out.println("presenter---->>>" + presenter.hashCode());
    }
}

Subcomponent使用

这个注解也可以实现多个Component的组合使用,直接上代码

//MainComponent
@AppScope
@Component(modules = {MainModule.class})
public interface MainComponent {
	//提供一个方法,表示PresenterComponent是MainComponent的子Module
    PresenterComponent buildPresenterComponent();
}

//PresenterComponent
@UserScope
//子module
@Subcomponent(modules = PresenterModule.class)
public interface PresenterComponent {
    void injectActivity(MainActivity activity);
    void injectActivity(SecondActivity activity);
}

//MyApp的Component声明,它的类型是PresenterComponent
component = DaggerMainComponent
        .builder()
        .mainModule(new MainModule())
        .build()
        .buildPresenterComponent();

不同的上面已经标出来了,后面的页面注入是一样的,就不贴了。

其实一般不推荐使用Subcomponent注解,可以看这里

@Override
public PresenterComponent buildPresenterComponent() {
  return new PresenterComponentImpl();
}
private PresenterComponentImpl() {
  this.presenterModule = new PresenterModule();
  initialize();
}

PresenterModule的创建是通过无参构造方法创建的,这就表示不能通过外部传入参数,也就带来了一定的限制性,所以一般推荐第一种方式,来实现多个Component注入。


动态url获取

有时候,网络请求的url不是固定的,这时候,就可以通过接下来方案解决

public class MyApp extends Application {

    private static MainComponent component;

    @Override
    public void onCreate() {
        super.onCreate();

        //测试不同url
        String url1 = "www.baidu.com";
        String url2 = "www.123.com";
        List list = new ArrayList();
        list.add(url1);
        list.add(url2);

        component = DaggerMainComponent.builder()
                .mainModule(new MainModule(list))
                .presenterComponent(DaggerPresenterComponent.create())
                .build();
    }

    public static MainComponent getComponent() {
        return component;
    }
}

网络请求需要的url,通过构造方法传入MainModule,看MainModule的定义

@AppScope
@Module
public class MainModule {

    private List<String> urls;

    public MainModule(List<String> urls) {
        this.urls = urls;
    }

    @AppScope
    @Provides
    @Named("url1")
    public String provideUrl1() {
        return urls.get(0);
    }

    @AppScope
    @Provides
    @Named("url2")
    public String provideUrl2() {
        return urls.get(1);
    }

    @AppScope
    @Provides
    public Teacher provideTeacher() {
        return new Teacher();
    }

    @AppScope
    @Provides
    public Student provideStudent(Teacher teacher) {
        return new Student(teacher);
    }

}

由于要提供的url都是String类型,此时可以通过@Named来标记,看待注入类中怎么注入的

@Inject
@Named("url1")
String url1;
@Inject
@Named("url2")
String url2;

也是通过@Named来获取需要的url,这样就可以通过@Name来注入不同的url。

你可能感兴趣的:(android)