Dagger如何工作
不使用Dagger的示例
如果现在要写这么一个代码:我们的主宰大人最近想玩一个游戏:创造一个个人,给其中某些人分配灵魂,然后把他们随机放到“悲惨世界”和“快乐星球”去渡劫一场人生。如图
Master game.png
这里用到工厂模式和依赖注入,如下
- 创建人类肉体的女娲大人:
class Nuwa {
public Person getWoman(){
Face face = new Face(new Eye(), new Nose());
Head head = new Head(face, new Hair("long"));
return new Woman(new Hand(), head);
}
public Person getMan(){
Face face = new Face(new Eye(), new Nose());
Head head = new Head(face, new Hair("short"));
return new Man(new Hand(), head);
}
}
- 创建信念的工厂FaithFactory:
class FaithFactory {
public Faith believeInEvil(){
return new Evil();
}
public Faith believeInMerciful(){
return new Merciful();
}
}
- 创建追求的造梦厂DreamMaker:
class DreamMaker {
public Pursuit pursueMoney(){
return new Money();
}
public Pursuit pursuePower(){
return new Power();
}
}
- SoulFactory:
class SoulFactory {
private Pursuit mMoneyPursuit;
private Pursuit mPowerPursuit;
private Faith mEvilFaith;
private Faith mMercifulFaith;
public SoulFactory(){
DreamMaker dreamMaker = new DreamMaker();
mMoneyPursuit = dreamMaker.pursueMoney();
mPowerPursuit = dreamMaker.pursuePower();
FaithFactory faithFactory = new FaithFactory();
mEvilFaith = faithFactory.believeInEvil();
mMercifulFaith = faithFactory.believeInMerciful();
}
public Soul createMoneyEvilSoul() {
return new Soul(mMoneyPursuit, mEvilFaith);
}
...
}
- 开天辟地的盘古先生:
class Pangu {
public Universe getLesMiserable(){
return new LesMiserable();
}
public Universe getHappyPlanet(){
return new HappyPlanet();
}
}
然后,我们的主宰大人在玩游戏时是这样的:
public class Master {
public void playGame(){
Nuwa nuwa = new Nuwa();
Pangu pangu = new Pangu();
SoulFactory soulFactory = new SoulFactory();
Soul soul = soulFactory.createMoneyEvilSoul();
Person person = nuwa.getMan();
person.updateSoul(soul);
Universe universe = pangu.getLesMiserable();
universe.welcome(person);
person.nirvana();
}
}
从上可以看到,这段代码有两个令人烦闷的点:
- 需要手动new一堆类。
- 里面有4个factory模版:Nuwa、Pangu、FaithFactory、DreamMaker。
Dagger的作用就是解决这两个问题,让你不需要手动new一堆类,也不用构建一堆factory模版。
使用javax.inject.Inject + dagger.Component
- 在类的构造函数前加上@Inject注释,告诉Dagger如何创造这个类:
@Inject
public Woman(@NonNull Hand hand, @NonNull Head head) {
}
- 构造函数的参数类的构造函数前也要加上@Inject注释,告诉Dagger如何创造这个类,一直嵌套,直至构造函数没有参数:
@Inject
public Head(@NonNull Face face, @NonNull Hair hair){
}
@Inject
public Face(@NonNull Eye eye, @NonNull Nose nose){
}
@Inject
public Eye(){
}
如果某个构造函数的参数需要外部提供怎么办呢?请参考“使用dagger.Module + dagger.Provides”。
- 创建一个interface类,并为它加上@Component注释,通常这个类我们按xxxComponent的规范来命名。如下:
@Component(modules = {MasterGameModule.class, SoulModule.class})
interface MasterGameComponent {
// create Person
Man getMan();
Woman getWoman();
SoulFactory getSoulFactory();
// create Universe
HappyPlanet getHappyPlanet();
LesMiserable getLesMiserable();
}
- 以上步骤之后,build project,编译器会自动生成一个类似Nuwa的Factory类,如下
final class DaggerMasterGameComponent implements MasterGameComponent {
private DaggerMasterGameComponent() {
}
public static Builder builder() {
return new Builder();
}
public static MasterGameComponent create() {
return new Builder().build();
}
private Face getFace() {
return new Face(new Eye(), new Nose());}
private Hair getHair() {
return new Hair(MasterGameModule_ProvideLongHairFactory.provideLongHair());}
private Head getHead() {
return new Head(getFace(), getHair());}
@Override
public Man getMan() {
return new Man(new Hand(), getHead());}
@Override
public Woman getWoman() {
return new Woman(new Hand(), getHead());}
@Override
public SoulFactory getSoulFactory() {
return new SoulFactory(SoulModule_ProvidePursueMoneyFactory.providePursueMoney(), SoulModule_ProvidePursuePowerFactory.providePursuePower(), SoulModule_ProvideEvilFactory.provideEvil(), SoulModule_ProvideMercifulFactory.provideMerciful());}
@Override
public HappyPlanet getHappyPlanet() {
return new HappyPlanet();}
@Override
public LesMiserable getLesMiserable() {
return new LesMiserable();}
static final class Builder {
private Builder() {
}
public MasterGameComponent build() {
return new DaggerMasterGameComponent();
}
}
}
可以看到这个类自动创建了构造Person、Soul、Universe类所需要的所有依赖类如Head、HappyPlanet之类,这样在new一个类时可以自动找到其依赖对象的构造函数,如此嵌套直到构造类成功。
- Master直接使用DaggerMasterGameComponent代替Nuwa、Pangu等Factory,如下:
public class Master {
public void playGameWithDagger(){
MasterGameComponent masterGameComponent = DaggerMasterGameComponent.create();
Soul soul = soulFactory.createMoneyEvilSoul();
Person person = masterGameComponent.getMan();
person.updateSoul(soul);
Universe universe = masterGameComponent.getLesMiserable();
universe.welcome(person);
person.nirvana();
}
}
从上面可以看到,Dagger自动构建了Factory模版且自动生成inject类的构造函数,使得程序开发者不需要手动构建Factory模版。
使用dagger.Module + dagger.Provides
使用 @Provides 告知 Dagger 如何提供您的项目所不具备的类。
上面的demo中,Hair类的构造函数有一个String类型的参数,在默认情况下Dagger是无法自动构建这个带了参数的构造函数的。这个时候要使用@Provides注释告诉Dagger如何提供项目本身无法提供的类。@Provides注释要放在带有@Module注释的类中,一般来说,我们约定带有@Module注释的类以xxxModule命名,带有@Provides注释的方法以provideXXX()命名。如:
@Module
abstract class MasterGameModule {
@Provides
public static String provideLongHair(){
return "long";
}
}
然后在你的@Componect注释接口上加上
@Component(modules = MasterGameModule.class)
这样Dagger会为Module中每个Provides的方法创建一个以MasterGameModule_ProvideXXXFactory.java类,如下:
public final class MasterGameModule_ProvideLongHairFactory implements Factory<String> {
...
public static String provideLongHair() {
return Preconditions.checkNotNull(MasterGameModule.provideLongHair(), "Cannot return null from a non-@Nullable @Provides method");
}
...
}
它调用了xxxModule.provideXXX()方法并供DaggerXXXComponent使用:
final class DaggerMasterGameComponent implements MasterGameComponent {
...
private Hair getHair() {
return new Hair(MasterGameModule_ProvideLongHairFactory.provideLongHair());}
...
}
使用dagger.BindsInstance
如果我们不想用Module提供Hair的参数,而是想在构建时动态传入Hair的参数怎么办呢:
@Component(modules = MasterGameModule.class)
@MasterGameAnnotations.MasterGameScope
interface MasterGameComponent {
...
@Component.Builder
interface Builder{
@BindsInstance
Builder hairLength(String hairLength);
MasterGameComponent build();
}
}
给MasterGameComponent指定一个Builder接口,在使用接口时传入hairLength参数。
之后,build project,得到的DaggerMasterGameComponent中将多一个hairLength(BindsInstance注释的方法名)的变量:
final class DaggerMasterGameComponent implements MasterGameComponent {
private final String hairLength;
private DaggerMasterGameComponent(String hairLengthParam) {
this.hairLength = hairLengthParam;
initialize(hairLengthParam);
}
...
private Hair getHair() {
return new Hair(hairLength);}
private static final class Builder implements MasterGameComponent.Builder {
private String hairLength;
@Override
public Builder hairLength(String hairLength) {
this.hairLength = Preconditions.checkNotNull(hairLength);
return this;
}
@Override
public MasterGameComponent build() {
Preconditions.checkBuilderRequirement(hairLength, String.class);
return new DaggerMasterGameComponent(hairLength);
}
}
}
这时如果要指定Hair参数并获取MasterGameComponent的方法为:
MasterGameComponent masterGameComponent = DaggerMasterGameComponent.builder().hairLength("long").build();
使用javax.inject.Qualifier
上面的SoulFactory在创建Faith和Pursuit时用了他们的工厂类,如果现在用Dagger没有Faith和Pursuit类的工厂类了,怎么办呢?
- 定义@Qualifier注释来区分不同类型的Faith和Pursuit。
class MasterGameAnnotations {
@Qualifier
public @interface Money {}
@Qualifier
public @interface Power{}
@Qualifier
public @interface Evil{}
@Qualifier
public @interface Merciful{}
}
2.定义一个Module,提供不同类型的Faith和Pursuit:
@Module
abstract class SoulModule {
@Provides
@MasterGameAnnotations.Money
public static Pursuit providePursueMoney() {
return DaggerSoulComponent.create().getMoney();
}
@Provides
@MasterGameAnnotations.Power
static Pursuit providePursuePower() {
return DaggerSoulComponent.create().getPower();
}
@Provides
@MasterGameAnnotations.Evil
public static Faith provideEvil() {
return DaggerSoulComponent.create().getEvil();
}
@Provides
@MasterGameAnnotations.Merciful
public static Faith provideMerciful() {
return DaggerSoulComponent.create().getMerciful();
}
}
- 定义代@Inject的SoulFactory的构造函数:
@Inject
public SoulFactory(@MasterGameAnnotations.Money Pursuit money, @MasterGameAnnotations.Power Pursuit power,
@MasterGameAnnotations.Evil Faith evil, @MasterGameAnnotations.Merciful Faith merciful) {
mMoneyPursuit = money;
mPowerPursuit = power;
mEvilFaith = evil;
mMercifulFaith = merciful;
}
这样,Master在使用时就可以用:
Soul soul = masterGameComponent.getSoulFactory().createMoneyEvilSoul();
使用javax.inject.Singleton & javax.inject.Scope
@Scope注释使某个inject对象的生命周期限定为其Component的生命周期,即使从Component中拿到的该Inject对象为同一个。
比如,如果你想要所有的人类都放到同一个悲惨世界里面要怎么做呢?
我们从DaggerMasterGameComponent的代码可以看到,每次获取LesMiserable时都会new一个LesMiserable:
public LesMiserable getLesMiserable() {
return new LesMiserable();}
要改变这种获取方法
- 定义一个Scope注释:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MasterGameScope {}
- 在Component类前加上这个注释:
@Component(modules = {MasterGameModule.class, SoulModule.class})
@MasterGameAnnotations.MasterGameScope
interface MasterGameComponent {
- 在LesMiserable类前加上这个注释:
@MasterGameAnnotations.MasterGameScope
public class LesMiserable implements Universe{
于是,Master在游戏过程中使用同一个Component对象创建LesMiserable时,都会获得同一个LesMiserable对象(当然使用不同的Component对象还是会获取不同的LesMiserable):
public void getLesMiserable() {
MasterGameComponent masterGameComponent = DaggerMasterGameComponent.create();
Universe universe1 = masterGameComponent.getLesMiserable();
Universe universe2 = masterGameComponent.getLesMiserable();
Log.d("Master", universe1 == universe2 ? "universe1 == universe2" : "universe1 != universe2");
Universe universe3 = masterGameComponent.getHappyPlanet();
Universe universe4 = masterGameComponent.getHappyPlanet();
Log.d("Master", universe3 == universe4 ? "universe3 == universe4" : "universe3 != universe4");
}
由于HappyPlanet没有设置@Scope注释,所以他拿到的还是不同的HappPlanet,Log输出如下:
xxx D/Master: universe1 == universe2
xxx D/Master: universe3 != universe4
查看DaggerMasterGameComponent类可以看到获取LesMiserable的方法不再是每次new一个新对象了,而是获取同一个初始化就创建的对象:
private Provider<LesMiserable> lesMiserableProvider;
private void initialize() {
this.lesMiserableProvider = DoubleCheck.provider(LesMiserable_Factory.create());
}
@Override
public LesMiserable getLesMiserable() {
return lesMiserableProvider.get();}
使用dagger.Binds
使用 @Binds 告知 Dagger 接口应采用哪种实现。
比如,我们想从MasterGameComponent中直接获取Faith,可以用以下方法:
- 在Module中用Bind注释提供Faith的实现:
@Module
abstract class MasterGameModule {
@Binds
public abstract Faith bindEvilFaith(Evil faithImpl);
@Binds
public abstract Pursuit bindPowerPursuit(Power pursuitImpl);
}
- 在Component中获取Faith:
@Component(modules = {MasterGameModule.class, SoulModule.class})
@MasterGameAnnotations.MasterGameScope
interface MasterGameComponent {
Faith getFaith();
Pursuit getPursuit();
// 因为在Module中bind了Faith和Pursuit的实现,所以Component也可以自动构建Soul对象
Soul getSoul();
}
Master的获取Soul的方法可以变成:
Soul soul = masterGameComponent.getSoul();
Dagger graph
上面使用Dagger后的Master game关系图如下:
Master game Dagger.png
Dagger创建的依赖图为:
Dagger graph.png
这个图没画完全,就是Dagger自动创建了所有inject类的依赖关系。
使用dagger.SubComponent
SubComponent用来把parent component分成多个独立的部分,每个部分可以定义它单独的Scope,因此可以拥有单独的生命周期。SubComponent可以用它parent component和ancestor component提供的对象,但是parent component不能用SubComponent的Modules,SubComponent也不能用它兄弟姐妹提供的对象。
例如要把上面Master game中的Soul单独构造一个SubComponent,可以用以下方法:
- 构造SubComponent并指定其要使用的moduels:
@Subcomponent(modules = SoulModule.class)
interface SoulComponent {
// create Pursuit
Money getMoney();
Power getPower();
// create Faith
Evil getEvil();
Merciful getMerciful();
Faith getFaith();
Pursuit getPursuit();
// 因为在Module中bind了Faith和Pursuit的实现,所以Component也可以自动构建Soul对象
Soul getSoul();
SoulFactory getSoulFactory();
@Subcomponent.Builder
interface Builder{
SoulComponent build();
}
}
- 把这个Component加到它parent component所要用的modules中:
@Module(subcomponents = SoulComponent.class)
abstract class MasterGameModule {
这样使用parent component可以访问SubComponent中的Builder。
- 在parent component中添加获取SoulComponent.Builder的方法:
@Component(modules = MasterGameModule.class)
@MasterGameAnnotations.MasterGameScope
interface MasterGameComponent {
...
SoulComponent.Builder getSoulComponent();
}
- build project得到DaggerMasterGameComponent类,我们可以看到在DaggerMasterGameComponent中有一个子类SoulComponent.Builder,并有获取它的方法:
final class DaggerMasterGameComponent implements MasterGameComponent {
...
@Override
public SoulComponent.Builder getSoulComponent() {
return new SoulComponentBuilder();}
static final class Builder {
private Builder() {
}
public MasterGameComponent build() {
return new DaggerMasterGameComponent();
}
}
private final class SoulComponentBuilder implements SoulComponent.Builder {
@Override
public SoulComponent build() {
return new SoulComponentImpl();
}
}
private final class SoulComponentImpl implements SoulComponent {
private SoulComponentImpl() {
}
@Override
public Money getMoney() {
return new Money();}
@Override
public Power getPower() {
return new Power();}
@Override
public Evil getEvil() {
return new Evil();}
@Override
public Merciful getMerciful() {
return new Merciful();}
@Override
public Faith getFaith() {
return new Evil();}
@Override
public Pursuit getPursuit() {
return new Power();}
@Override
public Soul getSoul() {
return new Soul(new Power(), new Evil());}
@Override
public SoulFactory getSoulFactory() {
return new SoulFactory(SoulModule_ProvidePursueMoneyFactory.providePursueMoney(), SoulModule_ProvidePursuePowerFactory.providePursuePower(), SoulModule_ProvideEvilFactory.provideEvil(), SoulModule_ProvideMercifulFactory.provideMerciful());}
}
}
最后,Master创造Soul的方法为:
MasterGameComponent masterGameComponent = DaggerMasterGameComponent.create();
Soul soul = masterGameComponent.getSoulComponent().build().getSoul();
Dagger为它创建的依赖图为:
Subcomponent Dagger graph.png
其中parent component的Dagger graph是其sub component的Dagger grapha的子图。
总结
使用 Dagger 的优势:
Dagger通过以下方式减少了代码中大量重复又容易出错的模版代码:
- Dagger自动生成了手写依赖注入需要构造的依赖关系图,通过这个依赖关系图,Dagger可以找到提供类实例的方式。
- 只要声明了某个类的依赖关系并使用注释指定如何满足它们,Dagger就会在构建时自动完成这些类的构建。
- 对于依赖关系图中的每个类,Dagger都会生成一个工厂类型的类(XXX_Factory.java),供内部使用该类来获取该类型的实例,并通过它们构建依赖关系。
- 通过@Scope注释来决定某个依赖类在其Component生命周期内是否单例。
- 用@SubCompnent注释把流程划分为不同的子流程,当某个子流程不再使用时可以释放它在内存中的对象,这样可以提高app的性能。
- build project时,Dagger会
A. 遍历代码来构建和验证依赖关系图,来确保
a). 每个对象的依赖关系都可以被满足,不会出现runtime exceptions。
b). 依赖关系不存在死锁,因此不会有死循环的依赖关系图。
B. 生成程序运行时需要用到的用来创建实例及其依赖项的class(DaggerXXXComponent、XXX_Factory、XXXModule_ProvideXXXFactory)。
缺点:
- 学习成本大,本来很简单的手动依赖注入方式需要通过一堆注释来完成,需要理解各个注释的原理和用法。
- Dagger框架本身也在不停的更新,也不时有更优秀的框架出现,如现在google官方建议可以用Hilt来替代Dagger。当应用需要从Dagger转成Hilt又是新工作量。
Android Q的Dialer代码中大规模的使用了Dagger,之后会专门介绍Dagger在Dialer模块中的应用。
参考
javax.inject:https://docs.oracle.com/javaee/7/api/javax/inject/package-summary.html
Android官网介绍依赖注入:https://developer.android.com/training/dependency-injection
dagger document:https://dagger.dev/dev-guide/
dagger github:https://github.com/google/dagger
Android官网介绍Dagger:https://developer.android.com/training/dependency-injection/dagger-basics
Android hilt:https://developer.android.com/training/dependency-injection/hilt-android
原创文章,欢迎转载,但请注明出处。
网友评论