Dagger2

132 阅读3分钟

DI

Dependency Injection: 依赖注入

DI是一种软件设计模式,实现了控制反转(Inversion of Control),主要作用是让一个对象接收它依赖的对象.

依赖:接收方需要的对象

注入:将依赖传递给接收方的过程,注入后接收方才能调用依赖;一般来说注入可以通过构造函数、setter、或接口实现

主要优势在于解耦接收方和依赖的具体实现,更容易实现扩展和重构

传统方式

class Computer {
    fun play(gameName: String) {
        println("play $gameName")
    }
}
​
class Person {
    private final val computer = Computer()
    public fun playGame(gameName: String) {
        computer.play(gameName)
    }
}
​
fun main(){
    val person = Person()
    person.playGame("game")
}

这里的问题在于代码的可维护性和耦合性,当依赖项需要修改时,需要手动修改所有使用该依赖项的代码.

具体来说,如果Person想要使用Computer时,这个Computer可以时自己的,也可以时其他人、公司的电脑,这个电脑是外部提供的.

因此我们可以把这个Computer实例使用依赖注入的方式实现.

四种方式

  1. 构造函数注入

    class Person1(private val computer: Computer){
        public fun playGame(gameName: String) {
            computer.play(gameName)
        }
    }
    
  2. 方法注入

    class Person2() {
        public fun playGame(computer: Computer, gameName: String) {
            computer.play(gameName)
        }
    }
    
  3. 属性注入

    class Person3() {
        lateinit var computer: Computer
        fun setComputer(computer: Computer){
            this.computer = computer
        }
        public fun playGame(gameName: String) {
            computer.play(gameName)
        }
    }
    
  4. 接口注入

    interface IComputer {
        fun play(gameName: String)
    }
    ​
    class WindowsComputer : IComputer {
        override fun play(gameName: String) {
            println("windows computer play $gameName")
        }
    }
    ​
    class MacComputer : IComputer {
        override fun play(gameName: String) {
            println("mac computer play $gameName")
        }
    }
    ​
    interface Gamer {
        fun setComputer(computer: IComputer)
        fun playGame(gameName: String)
    }
    ​
    class Person4 : Gamer {
        lateinit var computer: IComputer
    ​
        override fun setComputer(computer: IComputer) {
            this.computer = computer
        }
    ​
        override fun playGame(gameName: String) {
            computer.play(gameName)
        }
    }
    

Dagger2

Dagger2相比于Dagger使用了编译时生成代码,比Dagger运行时反射性能更好

基本概念

依赖需求方:需要依赖对象的类.在上面的例子中,Person类就是依赖需求方

依赖对象:被依赖对象的类.Computer是依赖对象

依赖供应方:提供依赖对象,类似于工厂类,即创建依赖方

依赖注入器:负责将依赖对象注入到需求方;在实际代码中是一个接口,Dagger2在编译时自动生成这个接口的实现类.

实例

依赖供应方

@Module// 依赖供应商.提供Computer实例
class TaoBao {
​
    //Provides告诉被@Module标记的类哪些方法提供依赖对象,这里提供Computer对象
    @Provides
    fun getComputer(): Computer {
        return Computer("tao bao")
    }
}

这里的Taobao类表示这个Computer实例来自于taobao,@Module注解告诉Dagger这个类是依赖供应方,@Provides使用在被@Module标记类的方法上,表示实例化依赖方的具体过程.

//依赖注入器,从那个xxModule中拿依赖对象
@Component(modules = [TaoBao::class])
interface ZTOExpress {
    fun deliverTo(person: Person)
}

ZTOExpress就是一个接口形式的依赖注入器,这里@Component注解告诉了Dagger从Taobao这个依赖供应方里拿依赖方,并传递给Person;

如果有多个依赖供应方,我们也可以写成这样的形式

//依赖注入器,从那个xxModule中拿依赖对象
@Component(modules = [TaoBao::class,JD::class])
interface ZTOExpress {
    fun deliverTo(person: Person)
}

这里有个问题.依赖注入器中声明了两个供应商,Dagger不知道从哪个供应商中取实例,这里就可以用@Qulifier或者@Named注解区分

上面的过程生成了一个Computer实例,但此时依赖注入器依然不知道需要把Computer注入给哪一个Person实例,我们可以在Person类中使用@inject标记

data class Person(val name: String) {
​
    @Inject
    lateinit var computer: Computer
​
    fun playGame(gameName: String) {
        computer.play(gameName)
    }
}

@Inject用于构造方法

data class Computer(val name:String) {
​
    @Inject
    constructor() : this(name = "default") {
        Log.i("Basti611","Computer constructor")
    }
​
    fun play(gameName: String) {
        Log.i("Basti611","computer name: $name")
        Log.i("Basti611","playing $gameName")
    }
}

当inject标记构造函数时,如果依赖注入器找不到依赖供应商提供的依赖方生成方法,就去通过被@Inject标记的构造方法生成依赖对象.

@Inject标记构造方法生成一个实例比@Module写依赖工厂类方便很多,但如果Computer是三方类,无法修改那么只能通过@Module提供对象

依赖对象还有依赖

添加一个CPU类,并添加到Computer的依赖,同时在Taobao工厂类中提供CPU的实例化方法

data class CPU(
    val name: String
)
​
data class Computer @Inject constructor(val name: String, val cpu: CPU) {
    fun play(gameName: String) {
        Log.i("Basti611", "computer name: $name")
        Log.i("Basti611", "playing $gameName")
    }
}
​
@Module// 依赖供应商.提供Computer实例
class TaoBao {
​
    //Provides告诉被@Module标记的类哪些方法提供依赖对象,这里提供Computer对象
//    @Provides
    fun getComputer(): Computer {
        return Computer("tao bao")
    }
​
    @Provides
    fun getCPU(): CPU {
        return CPU("CPU")
    }
}

流程:getComputer()的@Provides方法添加了一个CPU类型的参数,Dagger在获取Computer实例时,发现这个方法依赖于CPU,就会去找CPU的提供方法,拿到CPU后再交给Computer的实例化方法,最终获取到一个Computer并完成依赖注入

  1. 查找 @Module 类中是否存在创建该类的方法
  2. 如果存在,查看该方法是否存在参数 a. 存在参数,则按从步骤1 开始依次初始化每个参数 b. 不存在参数,则直接初始化该类实例,注入到依赖需求方
  3. 如果不存在,则查找该类中被 @Inject 标识的构造方法 a. 如果构造函数有参数,则按照从步骤1 开始依次初始化每个参数 b. 如果构造函数没有参数,则直接初始化该类实例,一次依赖注入到此结束

以上总结就是递归构造,优先找@Provides,如果没有再找@Inject

@Named @Qulifier

如果依赖方供应商Taobao中有两个提供Computer的方法,编译时会报错

@Module// 依赖供应商.提供Computer实例
class TaoBao {
​
    //Provides告诉被@Module标记的类哪些方法提供依赖对象,这里提供Computer对象
//    @Provides
    fun getComputer(): Computer {
        return Computer("tao bao")
    }
​
    @Provides
    fun getAmdCPU(): CPU {
        return CPU("AMD")
    }
​
    @Provides
    fun getIntelCPU(): CPU {
        return CPU("INTEL")
    }
}

Dagger会显示CPU被绑定了多次,因此Dagger无法知道通过调用哪个方法.

这里可以通过@Named注解,在返回值类型相同的情况下通过@Named注解的value值区分.

@Module// 依赖供应商.提供Computer实例
class TaoBao {
​
    //Provides告诉被@Module标记的类哪些方法提供依赖对象,这里提供Computer对象
//    @Provides
    fun getComputer(cpu: CPU): Computer {
        return Computer("tao bao", cpu)
    }
​
    @Provides
    @Named("AMD")
    fun getAmdCPU(): CPU {
        return CPU("AMD")
    }
​
    @Provides
    @Named("INTEL")
    fun getIntelCPU(): CPU {
        return CPU("INTEL")
    }
}
​
data class Computer @Inject constructor(val name: String, @Named("AMD") val cpu: CPU) {
    fun play(gameName: String) {
        Log.i("Basti611", "computer name: $name")
        Log.i("Basti611", "playing $gameName")
    }
}

依赖注入器依赖其他依赖注入器

如果一个供应方无法完成所有实例的供应,需要其他供应方提供实例,可以使用依赖注入器的依赖注入器,首先提供一个新的CPU供应方:

@Module
class CPUProvider {
​
    @Provides
    @Named("AMD")
    fun getAmdCPU(): CPU {
        return CPU("AMD")
    }
​
    @Provides
    @Named("INTEL")
    fun getIntelCPU(): CPU {
        return CPU("INTEL")
    }
}

现在需要一个依赖注入器

@Component(modules = [CPUProvider::class])
interface Ups {
    @Named("AMD") fun getAMDCPU():CPU
    @Named("INTEL") fun getIntelCPU():CPU
}

这里要注意的是因为ups是一个子组件依赖注入器,所以不需要定义类似于injectTo的方法,只需要定义一个返回值是依赖方的方法.

最后修改依赖注入的方法

        val person = Person("张三")
        val upExpress = DaggerUps.builder().build()
        DaggerZTOExpress.builder().jD(JD()).ups(upExpress).build().deliverTo(person)
        person.playGame("games")

@SubComponent

如果一个依赖注入器过于复杂,可以拆分成如果若干个子依赖注入器,可以使用@SubComponent

@Subcomponent
interface ZTOShanghaiExpress {
    fun deliverTo(person: Person)
}

ZTOExpress中应该有一个返回ZTOShanghaiExpress实例的方法

//依赖注入器,从那个xxModule中拿依赖对象
@Component(modules = [TaoBao::class,JD::class], dependencies = [Ups::class])
interface ZTOExpress {
    fun deliverTo(person: Person)
    
    fun getShanghaiZTOExpress(): ZTOShanghaiExpress
}
​
private fun init() {
        val person = Person("张三")
        val upExpress = DaggerUps.builder().build()
        val ztoExpress = DaggerZTOExpress.builder().jD(JD()).ups(upExpress).build()
        val shanghaiZTOExpress = ztoExpress.getShanghaiZTOExpress()
        shanghaiZTOExpress.deliverTo(person)
        person.playGame("games")
    }

这里的ZTOShanghaiExpress虽然没有被@Module标记,但是因为他属于ZTOExpress,因此继承了父组件的注解.

Provider Lazy

Lazy和Porovider都用来包装需要被Dagger注入的类型,Lazy指延迟加载,也即是当需要用到时才会获取一个实例;Provide会强制重新加载,每次用到依赖对象时,都会注入一次

总结

什么情况下需要使用Dagger2

全局单例管理

在大型应用中,我们经常需要管理一些全局单例对象,例如网络请求库、数据库实例、或者共享的缓存对象。Dagger2 可以通过 @Singleton 注解轻松管理这些单例。

@Module
public class NetworkModule {
    @Provides
    @Singleton
    OkHttpClient provideOkHttpClient() {
        return new OkHttpClient.Builder().build();
    }
​
    @Provides
    @Singleton
    Retrofit provideRetrofit(OkHttpClient client) {
        return new Retrofit.Builder()
            .baseUrl("https://api.example.com")
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    }
}
​
@Component(modules = {NetworkModule.class})
@Singleton
public interface AppComponent {
    Retrofit getRetrofit();
}
//使用
AppComponent appComponent = DaggerAppComponent.create();
Retrofit retrofit = appComponent.getRetrofit();

优点: 避免手动管理单例逻辑

依赖复杂的构造对象

当一个对象需要许多依赖时,手动构造非常繁琐,并且容易出错。Dagger2 能自动注入这些依赖,简化开发。

class AnalyticsService {
    private final Context context;
    private final String apiKey;
​
    @Inject
    AnalyticsService(Context context, String apiKey) {
        this.context = context;
        this.apiKey = apiKey;
    }
}
​
@Module
public class AnalyticsModule {
    @Provides
    String provideApiKey() {
        return "API_KEY";
    }
​
    @Provides
    AnalyticsService provideAnalyticsService(Context context, String apiKey) {
        return new AnalyticsService(context, apiKey);
    }
}
​
@Component(modules = {AnalyticsModule.class})
public interface AppComponent {
    AnalyticsService getAnalyticsService();
}

Activity/Fragment依赖注入

Dagger2 可以方便地管理 ActivityFragment 中的依赖,例如 ViewModel、Presenter、或者其他上下文相关的实例。

@Module
public class ActivityModule {
    private final Activity activity;
​
    public ActivityModule(Activity activity) {
        this.activity = activity;
    }
​
    @Provides
    Activity provideActivity() {
        return activity;
    }
}
​
@Component(modules = {ActivityModule.class})
public interface ActivityComponent {
    void inject(MainActivity activity);
}
​
​
public class MainActivity extends AppCompatActivity {
    @Inject
    AnalyticsService analyticsService;
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
​
        ActivityComponent component = DaggerActivityComponent.builder()
            .activityModule(new ActivityModule(this))
            .build();
        component.inject(this);
​
        analyticsService.track("MainActivity started");
    }
}
​