Android Dagger2 从零单排(四) Dependencies与SubComponent

  转发请注明出处:https://www.jianshu.com/p/b989e2cb88f6
  Dagger2作为Android界最具杀伤力的匕首,本系列文章将用最通俗的语言带领你揭开它的真面目。
  边缘OB:从零单排带你从低分局打到高分局,从First Blood(第一滴血)到Holy Shit(超越神的杀戮),每盘Rampage(暴走)不在话下!
  Android Dagger2 从零单排(一) 基础注解
  Android Dagger2 从零单排(二) @Qualifier
  Android Dagger2 从零单排(三) @Scope
  Android Dagger2 从零单排(四) Dependencies与SubComponent

  上一篇我们详细介绍了作用域@Scope的使用,本篇我们来研究Dependencies与SubComponent的用法。
  在前几篇的例子里,@Component都是跟@Module一一对应的提供依赖,如果一个@Component需要用另一个@Component提供依赖,这种情况该如何处理?比如,在Activity注入的依赖,依赖于Applicaton维护的全局单例对象,可能会第一时间想到,先用全局@Component在Activity注入全局单例对象,在注入Activity依赖时把对象传进去......首先在Activity维护这个全局单例对象是毫无意义,仅作为注入时的依赖使用,再者如果需要更多的@Component提供依赖的时候,此时代码逻辑就会变得非常混乱,至少,我如果看到一个Activity有很多个@Component在注入依赖,我绝对会头大。
  此时引出本文的主题,Dependencies与SubComponent,两者都能解决刚才的问题,但是两者处理的方式又大有不同。
  例子一,我们先来了解Dependencies,中文翻译是依赖关系,我们可以理解为:@Component为其他@Component提供了依赖对象。

public class Father {
}

@Module
public class FatherModule {
    @Provides
    public Father provideFather() {
        return new Father();
    }
}

@Component(modules = FatherModule.class)
public interface FatherComponent {
    Father offerFather();
}

public class Child {
    public Child(Father father) {
    }
}

@Module
public class ChildModule {
    @Provides
    public Child provideChild(Father father) {
        return new Child(father);
    }
}

@Component(modules = ChildModule.class, dependencies = FatherComponent.class)
public interface ChildComponent {
    void inject(ChildActivity activity);
}

public class ChildActivity extends Activity {
    @Inject
    Child mChild;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dagger);
        FatherComponent fatherComponent = DaggerFatherComponent.create();
        DaggerChildComponent.builder().fatherComponent(fatherComponent).build().inject(this);//Component类需要编译才会生成
        ((TextView) findViewById(R.id.text)).setText("注入成功 = " + mChild.toString());
    }
}

  例子中Child依赖于Father,最终为了注入Child,我们看下过程:
  (1)在ChildComponent类的注解,用dependencies表示ChildComponent依赖于FatherComponent,可以使用FatherComponent显式暴露出来的依赖。
  (2)在FatherComponent类,需要写抽象方法暴露依赖,例子中要提供Father实例,所以有个返回值为Father的方法,方法名字可以自己命名,看得懂就好,不被打就好...
  (3)注入的时候,必须先实例FatherComponent,再当做参数实例ChildComponent进行注入,如果@Module是有参构造方法的,按照正常调用在build()方法调用前实例化即可。
  注意:
  (1)FatherComponent依然是可以独立作为一个容器注入依赖。
  (2)无@Scope的@Component可以依赖有@Scope的@Component,有@Scope的@Component只能依赖有@Scope的@Component,并且两者的@Scope不能相同。
  (3)@Singleton的@Component只能被依赖而不能依赖任何@Component。
  总结,反正我是不太喜欢这种方式,需要在@Component暴露才能被依赖,我需要一种不暴露都能直接全部用的方式!简单!粗暴!
  例子二,满足愿望来了,下面介绍SubComponent,子Component,可以理解为继承或者拓展的意思,先看例子:

public class Father {
}

@Module(subcomponents = ChildComponent.class)
public class FatherModule {
    @Provides
    public Father provideFather() {
        return new Father();
    }
}

@Component(modules = FatherModule.class)
public interface FatherComponent {
    ChildComponent.Builder buildChildComponent();
}

public class Child {
    public Child(Father father) {
    }
}

@Module
public class ChildModule {
    @Provides
    public Child provideChild(Father father) {
        return new Child(father);
    }
}

@Subcomponent(modules = ChildModule.class)
public interface ChildComponent {
    void inject(ChildActivity activity);
    @Subcomponent.Builder
    interface Builder {
        ChildComponent build();
    }
}

public class ChildActivity extends Activity {
    @Inject
    Child mChild;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dagger);
        FatherComponent fatherComponent = DaggerFatherComponent.create();
        fatherComponent.buildChildComponent().build().inject(this);
        ((TextView) findViewById(R.id.text)).setText("注入成功 = " + mChild.toString());
    }
}

例子中还是Child依赖于Father,最终为了注入Child,我们看下过程:
  (1)在ChildComponent类的注解换成@Subcomponent表示是一个子@Component,@Subcomponent注解不会在编译后生成Dagger前缀的容器类,而是体现在父@Component的私有内部类,并且如何在父@Component中构建@Subcomponent都需要我们自己写...(这里有点无解,官网也只是说要你写,为啥写也没给原因,在我的构想,既然是继承关系,完全可以自动生成,反正父@Component的依赖是完全暴露的...)
  (2)依葫芦画瓢,还记得通常注入的方法builder().build(),生成Builder对象,传入@Module对象,调用build()方法生成@Component实例,此时我们按着这个步骤:
    2.1.在ChildComponent类写一个内部接口Builder,必须要注解成@Subcomponent.Builder表示是顶级@Subcomponent的内部类。
    2.2.Builder里面有build方法,返回值是ChildComponent,此时ChildComponent改造完毕。
  (3)刚才说过,@Subcomponent是父@Component的私有内部类,所以我们要让父@Component有能够生成@Subcomponent类的方法,所以在父@Component中写一个方法返回值是刚才的内部接口ChildComponent.Builder,到这步继承关系正式成立。
  (4)把父@Component的依赖全部暴露给@Subcomponent,在FatherModule类的注解,用subcomponents来表示开放全部依赖给ChildComponent。除非继承之后,@Subcomponent注入依赖时没有使用@Component的依赖,仅用于各自注入使用,此时可以不加(例如在ChildActivity同时注入Father与Child,并且Father与Child都是无参构造,@Subcomponent的依赖没有使用父@Component的情况,第4步可忽略)。
  (5)注入的时候,先实例FatherComponent,获取ChildComponent.Builder,获取ChildComponent实例最后注入。
  整个编译过程,FatherComponent在检测到ChildComponent.Builder返回值方法,同时检测ChildComponent类与其内部类的注解,如果是@Subcomponent注解,会在FatherComponent的实现类里维护ChildComponent类与其内部类的实现类,如果ChildComponent使用了FatherComponent的依赖,则检测FatherModule有没有注解开放“权限”。
  注意:
  (1)@Subcomponent不能再使用dependencies依赖其他@Component。
  (2)@Subcomponent同样也可以被继承。
  (3)@Subcomponent可以使用父@Component所有依赖,父@Component只有@Subcomponent.Builder实例,而不能使用@Subcomponent的依赖。
  (4)@Scope的使用同样继承关系中也是不能相同,但没有子类不能使用@Singleton的限制。
  (5)如果@Subcomponent指向的@Module是有参构造方法,写法如下,并且需要在build()方法调用前实例@Module:

    @Subcomponent.Builder
    interface Builder {
        ChildComponent build();
        Builder requestModule(ChildModule module);
    }

  总结,个人感觉@Subcomponent的方式在实际应用比较常用,如前文说的,Activity的注入依赖于Application的单例的情况。
  要放大招了,@Subcomponent还有一种写法,抽象工厂方法定义

@Subcomponent(modules = ChildModule.class)
public interface ChildComponent {
    void inject(ChildActivity activity);
}

@Component(modules = FatherModule.class)
public interface FatherComponent {
    ChildComponent buildChildComponent();
    //如果ChildModule是有参构造方法
    //ChildComponent buildChildComponent(ChildModule childModule);
}

  这种方式最狠,就只需要作以上修改即可,调用的时候还是先实例父@Component获取@Subcomponent注入,作为例子三在Demo展示。
  此时ChildComponent没有Builder接口,也没有build()方法,看了编译出来的文件,@Subcomponent作为父@Component的内部类,Builder的构建方式被构造方法传入代替,最后还是与build()方法相同构建出ChildComponent对象。
  两种写法比较,例子二是Dagger2推荐写法,标准的建造者模式,官方建议写法,例子三则是相对简单,个人比较喜欢直接粗暴的方式。
  官网提及比较重要的知识点
  (1)刚才所指的@Scope的使用在继承关系中不能相同,指的是父类与子类之间,如果父类有多个子类,子类与子类之间是可以相同,看Dagger2官网例子:

@Singleton 
@Component
interface RootComponent {
  SessionComponent.Builder sessionComponent();
}

@SessionScope 
@Subcomponent
interface SessionComponent {
  FooRequestComponent.Builder fooRequestComponent();
  BarRequestComponent.Builder barRequestComponent();
}

@RequestScope 
@Subcomponent
interface FooRequestComponent {...}

@RequestScope 
@Subcomponent
interface BarRequestComponent {...}

  (2)使用Subcomponent的重要原因是封装应用的不同部分。父@Component负责维护共享的数据、对象,不同处则由各自的@Subcomponent维护,这跟Android封装Base公共类的思想类似。
  (3)父子Component中的@Module,或Subcomponent的工厂方法定义的@Module,均不能定义重复的@Module,还是列出官方的例子:

@Component(modules = {RepeatedModule.class, ...})
interface ComponentOne {
  ComponentTwo componentTwo(RepeatedModule repeatedModule); // COMPILE ERROR!
  ComponentThree.Builder componentThreeBuilder();
}

@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentTwo { ... }

@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentThree {
  @Subcomponent.Builder
  interface Builder {
    Builder repeatedModule(RepeatedModule repeatedModule);
    ComponentThree build();
  }
}

DaggerComponentOne.create().componentThreeBuilder()
    .repeatedModule(new RepeatedModule()) // UnsupportedOperationException!
    .build();

  最后总结:比原本的计划发布时间推迟了几天,为了能更清晰的说明例子二的演变过程,我这人也不太会说话,如果有说不明白的地方,你**来打我啊!

  Demo源码截我 对应daggerFour包名
  Dagger2 GitHub地址
  Dagger2 官网地址
  所有的测试实例均基于2.15版本。
  下一篇,我们来研究一些其余用得比较少但还是需要了解的Dagger2知识。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249