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想在同一个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());
}
}
这个注解也可以实现多个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不是固定的,这时候,就可以通过接下来方案解决
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。