有关Dagger2的一些事(一)
这是为Dagger2
的详细分析第一篇,以做记录。
此篇介绍以下内容:
- @Inject
- @Component
- @Module
- @Scope
- @SubComponent
- denpendency
- @Lazy
- @Binds
- @IntoSet
- @IntoMap
此篇不会从头开始介绍dagger2,假设读者已经对dagger2有一定的了解哈
基本用法--没有Module
- 构造函数上标注@Inject
public class A {
@Inject
public A() {}
}
- 定义Component作为连接器
@Component
public interface ActivityComponent {
void inject(MainActivity MainActivity);
}
- 要进行注入的Activity
public class MainActivity extends Activity implements LifecycleOwner
{
@Inject
A a;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
DaggerActivityComponent.builder().build().inject(this);
}
}
1.png
我们详细看生成了什么:
对于ActivityComponent
,dagger
会帮你生成DaggerxxxComponent
对于MainActivity
,dagger
会帮你生成xxxMemberInjector
public final class DaggerActivityComponent implements ActivityComponent {
private MembersInjector<MainActivity> mainActivityMembersInjector;
private DaggerActivityComponent(Builder builder) {
assert builder != null;
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
public static ActivityComponent create() {
return builder().build();
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.mainActivityMembersInjector = MainActivity_MembersInjector.create(A_Factory.create());
}
@Override
public void inject(MainActivity MainActivity) {
mainActivityMembersInjector.injectMembers(MainActivity);
}
public static final class Builder {
private Builder() {}
public ActivityComponent build() {
return new DaggerActivityComponent(this);
}
}
}
两行关键代码:
this.mainActivityMembersInjector = MainActivity_MembersInjector.create(A_Factory.create());
mainActivityMembersInjector.injectMembers(MainActivity);
Factory
是真正提供数据的工厂,Injector
包含Factory
,所以Injector
可以向变量注入实例。
@Module
- 不在构造函数上标注@Inject,而是新生成一个Module
@Module
public class ActivityModule {
@Provides
public A provideA(){
return new A();
}
}
这是什么意思呢,就是需要实例时不会再从构造函数中去获取,而是通过providexxx
获取。
- 定义Component作为连接器,注意注解参数与前面的区别
@Component(modules = ActivityModule.class)
public interface ActivityComponent {
void inject(MainActivity MainActivity);
}
在MainActivity
中:
// 构造桥梁对象
DaggerActivityComponent.builder()
.activityModule(new ActivityModule())
.build()
.inject(this);
注意这里.activityModule(new xxx)
不写也没事,dagger
会自动帮我们构建,当然如果ActivityModule
的构造函数需要传递参数就需要我们手动写了。
注入过程
- 查找Module中是否存在创建该类的方法。
- 若存在创建类方法,查看该方法是否存在参数
- 若存在参数,则按从步骤1开始依次初始化每个参数
- 若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
- 若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
- 若存在参数,则从步骤1开始依次初始化每个参数
- 若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
@Scope
一个很具有疑惑性的注解。
- 创建注解
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}
- 在Module或构造函数上标注
@Module
public class UserModule {
...
@Provides
@UserScope
User providesUser() {
return new A();
}
}
or
@UserScope
public class User {
@Inject
public User() {
}
}
- 在使用该作用于的Component上标注
@UserScope
@Component(modules = {UserModule.class})
public interface UserComponent {
...
}
先说一下这个注解到底是什么意思。经常看到什么@Singleton|@PerActivity|PerFragment
。那些背后都是靠一个@Scope
完成的。那这个作用域是由谁控制的, 其实就是靠Component
控制的。
即:Component活多久,这个注解生成的实例就活多久。
我们以常见的PerActivity
为例,为什么它能做到跟Activity一个生命周期,就是因为你每新生成一个Activity
,你的DaggerxxxComponent
都会由你重新.build
生成。
假设我们在Application
中生成一个component
,并且在每个Activity
里通过getApplication().getComponent()
去获取Component
,那么不好意思,你的实例就和Application
同生死了。
所以这个是怎么做到的,很简单,背后利用了DoubleCheck
DoubleCheck:
在Provider
外面包一层DoubleCheck
,每次get
时看当前Component
下有没有已经生成的实例,有的话就直接返回。
@SuppressWarnings("unchecked") // cast only happens when result comes from the provider
@Override
public T get() {
Object result = instance;
if (result == UNINITIALIZED) {
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
result = provider.get();
/* Get the current instance and test to see if the call to provider.get() has resulted
* in a recursive call. If it returns the same instance, we'll allow it, but if the
* instances differ, throw. */
Object currentInstance = instance;
if (currentInstance != UNINITIALIZED && currentInstance != result) {
throw new IllegalStateException("Scoped provider was invoked recursively returning "
+ "different results: " + currentInstance + " & " + result);
}
instance = result;
/* Null out the reference to the provider. We are never going to need it again, so we
* can make it eligible for GC. */
provider = null;
}
}
}
return (T) result;
}
很简单就实现了作用域的控制。我的理解就是这么简单。
dependency
@Component(modules = ActivityModule.class)
public interface AComponent {
A provideA();
}
@Component(dependencies = AComponent.class)
public interface BComponent {
void inject(MainActivity MainActivity);
}
DaggerBComponent.builder().activityComponent(DaggerAComponent.builder().activityModule(new ActivityModule()).build()).build().inject(this);
先看上面代码,有两个Component
。B
是我们常见的写法,但是它@Component(dependencies = ActivityComponent.class)
依赖于A。
A
只暴露了一个接口A provideA();
所以最终实现什么效果呢,B
可以使用A
提供的provideA
去注入A的实例。
-
父Component中要显式的写出需要暴露可提供给子Component的依赖;
-
子Component在注解中使用dependencies=来连接父Component;
@SubComponent
@Component(modules = ActivityModule.class)
public interface ActivityComponent {
AnotherComponent anotherComponent();
}
@Subcomponent
public interface AnotherComponent {
void inject(MainActivity MainActivity);
}
DaggerActivityComponent.builder().build().anotherComponent(DaggerAnotherComponent.builder().build()).inject(this);
我们会看到,它跟denpendency
不同,后者会生成两个DaggerxxxComponent
,但它只会生成一个。
public final class DaggerActivityComponent implements ActivityComponent {
...
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.provideAProvider = ActivityModule_ProvideAFactory.create(builder.activityModule);
}
@Override
public AnotherComponent anotherComponent() {
return new AnotherComponentImpl();
}
public static final class Builder {
private ActivityModule activityModule;
private Builder() {}
public ActivityComponent build() {
if (activityModule == null) {
this.activityModule = new ActivityModule();
}
return new DaggerActivityComponent(this);
}
public Builder activityModule(ActivityModule activityModule) {
this.activityModule = Preconditions.checkNotNull(activityModule);
return this;
}
}
private final class AnotherComponentImpl implements AnotherComponent {
private MembersInjector<MainActivity> mainActivityMembersInjector;
private AnotherComponentImpl() {
initialize();
}
@SuppressWarnings("unchecked")
private void initialize() {
this.mainActivityMembersInjector =
MainActivity_MembersInjector.create(DaggerActivityComponent.this.provideAProvider);
}
@Override
public void inject(MainActivity MainActivity) {
mainActivityMembersInjector.injectMembers(MainActivity);
}
}
}
先说一个事实,一个Component
要不只能是@Component,要不只能是@SubComponent
然后看上面代码,最终是由标注了@SubComponent的AnotherComponent
去进行了真正的注入。这里@SubComponent可以继承它的父类提供的所有实例。我试了一下,如果只有AnotherComponent
存在是无法完成注入的,也就是说他是不独立的,它必须依附于父类。
所以dependency
与subComponent
的区别:
Component Dependencies - Use this when:
you want to keep two components independent.
you want to explicitly show what dependencies from one component is used by the other
Subcomponents - Use this when:
you want to keep two component cohesive
you may not care to explicitly show what dependencies from one component is used by the other
前者更独立,组件只是提供provide
给另一个借用一下。
同时注意:
两个拥有依赖关系的 Component 是不能有相同 @Scope 注解的!使用@SubComponent 则可以使用相同的@Scope注解。 (很重要!!!!)
@Lazy & @Provider
在Activity
中,我们可以这么声明变量。
@Inject
Lazy<A> a;
Provider<A> b;
只有真正去aLazy.get()
才会进行注入。
实现也很简单,继续DoubleCheck
包裹Provider
,然后真正get
的时候才会从Factory
中获取。不过他跟前面的scope
不大一样,是在对instance
实例赋值的时候进行包裹的。
其中Lazy(懒加载)的作用好比component初始化了一个present对象,然后放到一个池子里,需要的时候就get它,所以你每次get的时候拿到的对象都是同一个;并且当你第一次去get时,它才会去初始化这个实例.
@Override
public void injectMembers(MainActivity instance) {
if (instance == null) {
throw new NullPointerException("Cannot inject members into a null reference");
}
instance.a = DoubleCheck.lazy(bAndAProvider);
instance.b = bAndAProvider;
}
而对于b直接赋予provider
的值,后面要get
时就从provider.get
获取。
procider(强制加载)的作用:
1:同上当你第一次去get时,它才会去初始化这个实例
2:后面当你去get这个实例时,依旧是从
Provider
中获取,所以跟基本用法是一样的。
所以Provider与Lazy的区别在于,Lazy延迟加载之后每次的调用都是同一个对象,而Provider只是延迟加载,至于调用的对象是不是同一个就要看@scope
有没有进行约束了。
@Binds
现在我们假设有个接口叫IinterfaceA
UserA
和UserB
分别实现了这个接口。
然后我们在Activity
里这么写:
@Inject
IinterfaceA a;
我们希望实现IinterfaceA a = new UserA()
我们不能直接在UserA
的构造函数上标注@Inject
因为我们要注入的是接口不是实现。所以按已有的知识体系那就用Module
咯:
@Provides
public IinterfaceA providesA(){
return new UserA();
}
而如果用@Binds
这个新的特性,我们可以这么写。注意abstract
@Module
public abstract class AModule {
@Binds
public abstract IinterfaceA bindA(UserA userA);
}
这个意思是如果你想要注入到IinterfaceA
,请找UserA
。
其实这两货看起来差不多,那为什么要出现@Binds
。后来看有个解释挺不错:
@Provides methods are instance methods and they need an instance of our module in order to be invoked. If our Module is abstract and contains @Binds methods, dagger will not instantiate our module and instead directly use the Provider of our injected parameter (LoginPresenter in the above case).
对于Provides
而言,Module
是会被实例化的,因为要去创建UserA
。但是对于@Binds
而言,不需要实例化Module
就可以做到。
所以@Binds解决了我们面向接口编程的需求。
我的理解,@Binds
是完全可以被替代的,就看你自己的需求了。
@IntoSet & @ElementsIntoSet
如果我们在Activity
里:
@Inject
Set<String> a
这个可以通过@IntoSet & @ElementsIntoSet
实现实例一一注入到集合中,看个例子
@Provides @IntoSet
String providexxx() {
return "ABC";
}
@Provides @IntoSet
String providekkk() {
return "Acc";
}
@Provides @ElementsIntoSet
Set<String> provideSomeStrings() {
return new HashSet<String>(Arrays.asList("DEF", "GHI"));
}
这样a就包含<"ABC","Acc","DEF","GHI">
其实到这里基本的套路都差不多,直接上编译出来的源码:
//(int individualProviderSize, int collectionProviderSize)
this.setOfStringProvider =
SetFactory.<String>builder(2, 1)
.addProvider(providexxxProvider)
.addProvider(providekkkProvider)
.addCollectionProvider(provideSomeStringsProvider)
.build();
@Override
public Set<T> get() {
int size = individualProviders.size();
// Profiling revealed that this method was a CPU-consuming hotspot in some applications, so
// these loops were changed to use c-style for. Versus enhanced for-each loops, C-style for is
// faster for ArrayLists, at least through Java 8.
List<Collection<T>> providedCollections =
new ArrayList<Collection<T>>(collectionProviders.size());
for (int i = 0, c = collectionProviders.size(); i < c; i++) {
Collection<T> providedCollection = collectionProviders.get(i).get();
size += providedCollection.size();
providedCollections.add(providedCollection);
}
Set<T> providedValues = newHashSetWithExpectedSize(size);
for (int i = 0, c = individualProviders.size(); i < c; i++) {
providedValues.add(checkNotNull(individualProviders.get(i).get()));
}
for (int i = 0, c = providedCollections.size(); i < c; i++) {
for (T element : providedCollections.get(i)) {
providedValues.add(checkNotNull(element));
}
}
return unmodifiableSet(providedValues);
}
我们看到,individualProviderSize
对应@IntoSet
的个数,collectionProviderSize
对应@ElementsIntoSet
的个数。
当get
时会通过individualProvider
和collectionProvider
提供的数据注入到instance
中形成集合。
@IntoMap
跟IntoSet
一个性质,只不过多了Key
的约束。上例子:
@Provides
@IntoMap // 指定该@Provides方法向Map提供元素
@StringKey("A") // 指定该元素在Map中所对应的的Key
String providexxx() {
return "ABC";
}
@Provides
@IntoMap // 指定该@Provides方法向Map提供元素
@ClassKey(MainActivity.class)
String providekkk() {
return "Acc";
}
StringKey
代表以字符串作为key,ClassKey
代表以类作为key。
目前dagger2
预定义的有:
- ClassKey
- IntKey
- LongKey
- StringKey
编译后的代码不贴了,跟set
原理差不多
自定义key类型
enum MyEnum {
ABC, DEF;
}
@MapKey
@interface MyEnumKey {
MyEnum value();
}
@MapKey
@interface MyNumberClassKey {
Class<? extends Number> value();
}
@Module
class MyModule {
@Provides @IntoMap
@MyEnumKey(MyEnum.ABC)
static String provideABCValue() {
return "value for ABC";
}
@Provides @IntoMap
@MyNumberClassKey(BigDecimal.class)
static String provideBigDecimalValue() {
return "value for BigDecimal";
}
}
这里定义了自定义两种key:ABC,DEF
Map
的高级用法请见下节。