美文网首页
dagger2 使用教程第五节

dagger2 使用教程第五节

作者: 九风特 | 来源:发表于2020-09-18 17:19 被阅读0次

本节引言
前面我们分析了很多Dagger的注解,现在应该有个较全面的认识了。但dagger之所以比别的第三方模块难就是因为它比较抽象,包含的东西还挺多,接下来我们要讨论一些深一点的话题了

进一步讨论Component
既然是组件那就代表这东西可能不止一个,而且之间还可能存在联系。
dagger2的组件之间可能是这3中关系

  • 完全独立
  • 组件之间有依赖关系,但相对独立
  • 成对出现,可说是父子关系

前边我们已经比较依赖我们的实例,现在仍然利用它来进行说明吧
完全独立
完全独立这个关系虽然好理解,但也说一说吧
我们还是来丰富我们原来的demo(只是为了偷懒实际上和之前的逻辑没关系),但为了更清楚的理解Component的各种关系,我们对新增的一些类不提供@Inject构造函数。
假设我们要在主界面展示一台电脑和一台打印机的信息(界面弄个按钮,输出点信息模拟就是了)
先来创造两个类(非常简单的类,就一个名字),两个Module,两个Component

class Computer(private val name:String){
    override fun toString() = "Computer:$name"

}
class Printer(private val name:String){
    override fun toString() = "Printer:$name"
}
@Module
class ComputerModule{
    @Provides
    fun getComputer()=Computer("戴尔")
}

@Module
class PrinterModule{
    @Provides
    fun getPrinter()=Printer("惠普")
}

@Component(modules = [ComputerModule::class])
interface ComputerComponent{
    fun makeComputer():Computer
}

@Component(modules = [PrinterModule::class])
interface PrinterComponent{
    fun makePrinter():Printer
}

然后修改UI代码新增代码如下

    ...
    lateinit var myComputer:Computer
    lateinit var myPrinter:Printer
    ...

        myComputer = DaggerComputerComponent.create().makeComputer()
        myPrinter = DaggerPrinterComponent.builder().build().makePrinter()
        buttonShowInfo.setOnClickListener {
            textViewInfo.text = "$myComputer, $myPrinter"
        }

点击按钮,我们会看到一切都是按照预期


dell.png

好现在就是两个独立的Component,如果你看了前几节,到目前为止的代码,你都应该能轻松理解。

组件之间有依赖关系,但相对独立
现在我们发现打印机需要一个print功能,但我们发现这个print功能没有电脑不行,打印机无法自己打印,先来修改我们的Printer相关类:

class Printer(private val name:String, private val cpu:Computer){
    fun print() = "$cpu is working"
    override fun toString() = "Printer:$name"
}

@Module
class PrinterModule{
    @Provides
    fun getPrinter(cpu:Computer)=Printer("惠普", cpu)
}

现在挠头的事情来了,这个打印机需要一个computer参数,这我上哪给你弄去?Dagger又出新主意了,就是让一个Component可以依赖一个或多个其它Component。 先来改写代码

@Component(modules = [PrinterModule::class], dependencies = [ComputerComponent::class])
interface PrinterComponent{
    fun makePrinter():Printer
}

看到了吧PrinterComponent指定了依赖ComputerComponent, 还需要改写UI代码来明确这种依赖,自己理解下吧,我就不啰嗦了(可以看Dagger生成的源码)

        val cpuCom = DaggerComputerComponent.create()
        myComputer = cpuCom.makeComputer()
        myPrinter = DaggerPrinterComponent.builder().computerComponent(cpuCom).build().makePrinter()

这种依赖,两个component的也可以说是相互独立的,printer component也可以单独创建,不过没法构建我们希望的printer就是了

成对出现,可说是父子关系
现在我们还有另外一个类,叫做虚拟打印机,这个类和电脑的关系更密切,脱离电脑根本就不可能存在。鉴于这种紧密关系,我们称电脑和虚拟打印机是父子关系,鉴于我们现在已经很熟悉了,把相关的类一股脑贴出来吧:

class VirtualPrinter(private val name:String, private val cpu:Computer){
    override fun toString() = "VirtualPrinter:$name"
}

@Module
class VirtualPrinterModule{
    @Provides
    fun getVirtualPrinter(cpu:Computer)=VirtualPrinter("微软虚拟打印机", cpu)
}

@Component(modules = [ComputerModule::class])
interface ComputerComponent{
    fun makeComputer():Computer
    fun makeVirtualComponent():SubComponentVirtualPrinter
}

@Subcomponent(modules = [VirtualPrinterModule::class])
interface SubComponentVirtualPrinter{
    fun makeVirtualPrinter():VirtualPrinter
}

UI代码

...
lateinit var myVirtualPrinter:VirtualPrinter
...
        val cpuCom = DaggerComputerComponent.create()
        myComputer = cpuCom.makeComputer()
        myPrinter = DaggerPrinterComponent.builder().computerComponent(cpuCom).build().makePrinter()
        myVirtualPrinter = cpuCom.makeVirtualComponent().makeVirtualPrinter()
        buttonShowInfo.setOnClickListener {
            textViewInfo.text = "$myComputer, $myPrinter, $myVirtualPrinter, ${myPrinter.print()}"
        }
...

我们看到了一个新的关键字@Subcomponent这就表明这个component是一个子组件,在本例中这个子组件是通过其父组件ComputerComponent的makeVirtualComponent方法来提供的。 在这种关系中子组件是不能单独存在的,必须先有父组件才能有子组件。 如果想深挖请看下源码吧,其实也没啥。

本着趁热打铁的精神,我们来继续引入一个概念(实际上我这个使用教程就是官方文档介绍关键字的顺序。。。)

惰性的注入
你可能会说:你这个demo不行,如果用户不点击“User”按钮,不是白白创建User实例了吗,浪费资源啊。我反驳说:大哥,我这是demo不是产品。不,你说的对,我改还不行吗!!!那我先抄一段官方文档的文字行不?


有时需要延迟实例化对象。对于任何绑定T,都可以创建一个Lazy<T>,它将实例化延迟到第一次调用Lazy<T>的get()方法时。如果T是一个单例,那么Lazy<T>将是ObjectGraph中所有注入的相同实例。否则,每个注入站点将获得自己的惰性<T>实例。无论如何,对Lazy<T>的任何给定实例的后续调用都将返回相同的T的底层实例。

class GrindingCoffeeMaker {
  @Inject Lazy<Grinder> lazyGrinder;

  public void brew() {
    while (needsGrinding()) {
      //Grinder在第一次调用.get()时创建了一次并缓存了它。
      lazyGrinder.get().grind();
    }
  }
}


那我们现在就把我们的User对象改成Lazy的

lateinit var user:Lazy<User>

去掉原来初始化User的代码,在按钮点击中:

        buttonUser.setOnClickListener {
            user.get().apply {
                name = "Hero"
                car.name = "大众"
                gotoCompany()
                textViewInfo.text = toString()
            }
        }

运行一切okay,没啥问题。这个Lazy还不错,毕竟用的时候在创建类是个不错的事情(computer实例,可以用kotlin自己的lazy体制,就不介绍了)。
提供者注入
有时您需要返回多个实例,而不是只注入一个值。当您有几个选项(工厂、构建器等)时,一个选项是注入Provider<T>而不仅仅是T。Provider<T>每次调用.get()时都会为T调用绑定逻辑。如果该绑定逻辑是@Inject构造函数,那么将创建一个新的实例,但是@Provides方法没有这样的保证。

class BigCoffeeMaker {
  @Inject Provider<Filter> filterProvider;

  public void brew(int numberOfPots) {
  ...
    for (int p = 0; p < numberOfPots; p++) {
      maker.addFilter(filterProvider.get()); //每次换一个新filter。
      maker.addCoffee(...);
      maker.percolate();
      ...
    }
  }
}

这玩意我实在想不出啥时候有用,就不分析了, 在来分析一个不太常用的吧
可选的绑定
这个好像有点用吧,我们还是用我们的实例来进行点发挥和说明吧,比如我们的Car类,可以有引擎,也可以没有引擎,当然没引擎的话自然无法执行gotoCompany方法咯,那怎么办呢?这就需要使用@BindsOptionalOf注解了,来看一下官方对此注解的说明吧


如果你想要绑定可以工作,即使某些依赖没有绑定到组件中,你可以添加一个@BindsOptionalOf方法到一个模块:

@BindsOptionalOf abstract CoffeeCozy optionalCozy();

这意味着@Inject构造函数和成员以及@Provide方法可以依赖于一个Optional<CoffeeCozy>对象。如果组件中有对CoffeeCozy的绑定,则会出现这个"Optional", 如果没有绑定的CoffeeCozy,这个"Optional"将缺席。


请认真理解吧,让我组织更自然的语言来解释它,我也不会,改造我们的实例,贴代码看看

class Car @Inject constructor(val engine:Optional<Engine>){//改造car类添加Optional,说明此字段可能为空
    lateinit var name:String
}

@Module
interface EngineModule{//改造Module来说明这个Engine是个可选注入
    @BindsOptionalOf
    fun  optionalEngine():Engine
}

class User @Inject constructor(val age: Int, val car: Car, val pad: SafeNotePad)
{
    lateinit var name:String
    @RequiresApi(Build.VERSION_CODES.N)
    fun gotoCompany()//改造此方法,以适应car可能没引擎的情况,没引擎则什么也不做
    {
            if(car.engine.isPresent)
            {
                car.engine.get().on()
                Log.i("zrm", "$name goto company")
                car.engine.get().off()
            }
    }
    override fun toString(): String {
        return "User Name:$name, Age:$age, Car:${car.name}, $pad"
    }
}

那么现在运行程序,会发现,gotoCompany什么也没做,因为引擎为空了,如果不做@BindsOptionalOf的注释,engine如果不提供Provider的话,是编译不过的,自己可以试试。
既然是可选,那就代表也可以有引擎,那我们来写个提供引擎的Module并加入到Component中

@Module
class EngineModuleChina{
    @Provides
    fun getEngine(): Engine {
        return ChinaEngine()
    }
}
@Component(modules = [UserModule::class, EngineModule::class, EngineModuleChina::class])
interface MainComponent{
    fun inject(activity: MainActivity)
}

此时我们运行程序,发现gotoCompany又可以正常工作了。一切到达了预期
思考:这玩意(@BindsOptionalOf)啥时候有用呢,可能是你设计一个类,有很多字段都是需要注入的,但有的字段由于工作进度等等还没设计好提供方法,就可以用这玩意。 等你有了Provider就不用改很多代码了,就添加个Provider就行了。还有别的用途吗??不想了。。。

还想继续,但到目前为止感觉已经基本够用了,等有时间在来个终章,把剩下的一些不太常用的东西继续说说,无论如何本节到此为止吧。

相关文章

网友评论

      本文标题:dagger2 使用教程第五节

      本文链接:https://www.haomeiwen.com/subject/mnmdyktx.html