美文网首页android技术
Android使用dagger2,语言为kotlin

Android使用dagger2,语言为kotlin

作者: 克罗克达尔 | 来源:发表于2019-11-04 13:38 被阅读0次

使用依赖注入的好处在这里不细说,我们重点了解如何在Android项目中使用dagger2

1. 引入dagger2

 implementation 'com.google.dagger:dagger-android:2.25.2'
 implementation 'com.google.dagger:dagger-android-support:2.25.2'
    // if you use the support libraries
 implementation 'com.google.dagger:dagger:2.25.2'
 kapt 'com.google.dagger:dagger-compiler:2.25.2'
 kapt 'com.google.dagger:dagger-android-processor:2.25.2'

注意如果项目级别的gradle文件中没有

apply plugin: 'kotlin-kapt'

要记得加上,不然会报错

2.声明提供方

我们可以在一个类的构造方法上加上@Inject来表明当有一个类使用到我们这个类的实例的时候,可以调用我们这个用注解标记的构造方法来生成一个

class Model constructor(
    val uuid: String
) {
    @Inject
    constructor() : this(UUID.randomUUID().toString())
}

3.声明使用方

在需要提供注入的类的属性上使用@Inject来表明我们这个属性需要dagger2来注入

class MainActivity : AppCompatActivity() {


    @Inject
    lateinit var model1: Model


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

4.声明注入器

现在我们有了依赖的提供方和依赖的使用方,那么如何把这两者结合起来,让依赖注入到使用者需要的属性上呢?我们这里就需要用到@Component注解来生成一个注解器。

我们用@Component来注解一个接口,dagger2会在编译时自动生成一个Dagger+类名的实现类实现依赖注入。具体使用方法有以下两种:

- Members-injection methods

该方法有一个参数,这个参数表示需要注入的对象,告知dagger2去寻找需要的依赖并注入到对象的属性上,例如

@Component
interface MainComponent {

    fun inject(mainActivity: MainActivity)
}

- Provision methods

声明一个不需要参数但有返回值的方法,返回一个需要被注入的依赖,返回对象由使用者自行处理,例如:

@Component
interface SecondComponent {

    fun getModel(): Model
}

5.使用注入器注入依赖

当我们声明了依赖的提供方、依赖的使用方和注入器,我们就可以使用依赖注入了。编译项目,会生成Dagger+注入器为类名的注入器的实现类。

class MainActivity : AppCompatActivity() {


    @Inject
    lateinit var model1: Model


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        DaggerMainComponent.builder().build().inject(this)
        model1.uuid.log()
    }
}

在注入器注入依赖后,我们就可以使用被注入的对象了。

6.第三方依赖注入

我们可以使用@Inject来标识一个构造函数,当我们需要这个类的实例时,我们会调用@Inject标识的构造方法来创建一个对象。但是如果我们需要注入的是一个第三方的对象,我们没有办法去修改第三方的源码,我们应该怎么办?
我们可以使用@Module@Provides来搭配使用:

  • 新建一个module接口,在接口上使用@Module
  • 声明一个方法, 返回值为我们需要的类型
@Module
class HttpServiceModule {
    @Provides
    fun provideHttpService(): HttpService {
        return HttpService()
    }
}
  • 在注入器的@Component里面添加我们刚才新建的module
@Component(modules = [HttpServiceModule::class])
interface HttpComponent {

    fun inject(httpActivity: HttpActivity)
}

然后就可以正常使用了

7.指定依赖注入

有时我们会遇到这么一种情况,我们会提供同一个类型的不同的依赖,比如我们项目中集成了高德地图和百度地图,我把两个地图的key使用依赖注入的方式来注入到初始化的代码中,在一个module里面声明了两个类型都是字符串的key,如何让dagger2知道我们需要的是哪一个呢?

class KeyActivity : AppCompatActivity() {


    @Inject
    lateinit var key: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_key)
    }
}


@Module
class KeyModule {

    @Provides
    fun provideAmapKey(): String = "amap"


    @Provides
    fun provideBaiduKey(): String = "baidu"
}

@Component(modules = [KeyModule::class])
interface KeyComponent {

    fun inject(keyActivity: KeyActivity)
}

我们可以使用@Named注解,在注解提供的方法上和需要注入的属性上使用相同的字符串,通过字符串匹配,让dagger2知道我们要的是哪个。例如:

class KeyActivity : AppCompatActivity() {


    @Named("amap")
    @Inject
    lateinit var key: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_key)



        DaggerKeyComponent.create().inject(this)

        key.log()
    }
}


@Module
class KeyModule {

    @Named("amap")
    @Provides
    fun provideAmapKey(): String = "amap"


    @Named("baidu")
    @Provides
    fun provideBaiduKey(): String = "baidu"
}

@Component(modules = [KeyModule::class])
interface KeyComponent {

    fun inject(keyActivity: KeyActivity)
}

当然有些人觉得用字符串来标识指定注解不太靠谱,因为字符串可能会写错。我们可以使用@Qualifier注解来标识我们自己创建的注解,当提供方的自定义注解和使用方的自定义注解相同时,dagger2就会为我们做出正确的注入。

class Dog : Animal {
    override fun toString(): String {
        return "dog"
    }
}

class Cat : Animal {
    override fun toString(): String {
        return "cat"
    }
}


@Module
class AnimalModule {


    @DogAnimal
    @Provides
    fun provideDog(): Animal = Dog()

    @CatAnimal
    @Provides
    fun provideCat(): Animal = Cat()
}


@Component(modules = [AnimalModule::class])
interface AnimalComponent {

    fun inject(animalActivity: AnimalActivity)
}

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class DogAnimal

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CatAnimal

class AnimalActivity : AppCompatActivity() {


    @CatAnimal
    @Inject
    lateinit var animal: Animal

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_animal)


        DaggerAnimalComponent.create().inject(this)

        animal.toString().log()

    }
}

8.单例

考虑一下代码,一个使用方需要使用两个依赖,这两个依赖类型一样,他们会是同一个对象吗?或者说在注入的时候会调用几次带有@Inject的构造方法?

class PeopleActivity : AppCompatActivity() {

    @Inject
    lateinit var people1: People

    @Inject
    lateinit var people2: People


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_people)


        DaggerPeopleComponent.create().inject(this)

        "$people1 $people2 ${people1 === people2}".log()
    }
}


class People @Inject constructor()


@Component
interface PeopleComponent {

    fun inject(peopleActivity: PeopleActivity)
}

考虑下上面的输入是什么?上面的输入是

com.ke.dagger2_demo.People@5aa9f47 com.ke.dagger2_demo.People@e17a374 false

但是有些时候我们不希望每次使用依赖的时候都去创建一个对象,比如使用我们的http client,我们希望全局有且仅有唯一一个http client,那么这时我们应该怎么做?
我们可以使用@Singleton注解放在我们希望是单例的类名上,告诉dagger2在创建该类实例的时候只能创建一次。

@Singleton
class People @Inject constructor()

注意一点,在对应的注入器上同样要加上@Singleton注解

@Singleton
@Component
interface PeopleComponent {

    fun inject(peopleActivity: PeopleActivity)
}

晚上上述操作后在看看我们的输入

com.ke.dagger2_demo.People@5aa9f47 com.ke.dagger2_demo.People@5aa9f47 true

加了@Singleton注解后,两个依赖是同一个对象,不会在使用依赖的时候额外创建了。

额外提一点,@Singleton@Scope的一个实现,类似@Named@Qualifier一样,如果你不喜欢Singleton这个名字,可以自己创建一个自定义注解并用@Scope来标识。

9.@Binds

在实际项目中,我们可能会遇到这么一种情况,我们需要注入的依赖类型是抽象的,我们可以在指定的module中利用@Provide注解一个方法,接收一个实现类对象,返回的类型是抽象的,例如:

class WeaponActivity : AppCompatActivity() {

    @Inject
    lateinit var weapon: Weapon


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_weapon)

        DaggerWeaponComponent.create().inject(this)

    }
}


interface Weapon {
    fun attack()
}

class Gun @Inject constructor() : Weapon {
    override fun attack() {
        "gun attack".log()
    }
}

class Sword @Inject constructor() : Weapon {
    override fun attack() {
        "sword attack".log()
    }
}


@Module
class WeaponModule {

    //接收一个实现类的对象,返回抽象类型
    @Provides
    fun provideWeapon(gun: Gun): Weapon {
        return gun
    }
}

@Component(modules = [WeaponModule::class])
interface WeaponComponent {
    fun inject(weaponActivity: WeaponActivity)
}

我们在这里可以用@Binds注解来简化这一步骤,把module中的方法改为抽象方法,module改为抽象类,然后把方法前缀provide改成bind@Provide注解修改成@Binds,就可以起到相同的功能。

@Module
abstract class WeaponModule {
    

    @Binds
    abstract fun bindWeapon(gun: Gun): Weapon

}

10.注入到Set中

如果我们需要注入的不是单个对象,而是一个集合,例如:

@Inject
lateinit var carSet: Set<Car>

我们应该怎么做呢?
我们可以利用@IntoSet方法,把module提供的依赖打包成Set集合注入到我们需要的地方,例如:

class IntoSetActivity : AppCompatActivity() {


    @Inject
    lateinit var carSet: Set<Car>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_into_set)

        DaggerCarComponent.create().inject(this)

        carSet.forEach {
            it.toString().log()
        }

    }
}


data class Car(val name: String)

@Module
class CarModule {

    @Provides
    @IntoSet
    fun provideA(): Car {
        return Car("A")
    }

    @Provides
    @IntoSet
    fun provideB(): Car {
        return Car("B")
    }
}

@Component(modules = [CarModule::class])
interface CarComponent {

    fun inject(intoSetActivity: IntoSetActivity)
}

11.注入到Map中

如果我们需要注入的对象类型是一个map,我们也能注入吗?可以的,和注入到Set类似,我们只需要把@IntoSet修改成@IntoMap,我们就可以,并且需要指定key。代码如下:

class IntoMapActivity : AppCompatActivity() {


    @Inject
    lateinit var foodMap: Map<String, Food>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_into_map)

        DaggerFoodComponent.create().inject(this)


        foodMap.forEach {
            "key = ${it.key} , value = ${it.value}".log()
        }

    }
}


data class Food(val name: String)

@Module
class FoodModule {

    @IntoMap
    @Provides
    @StringKey("apple")
    fun provideApple(): Food {
        return Food("apple")
    }


    @IntoMap
    @Provides
    @StringKey("milk")
    fun provideMilk(): Food {
        return Food("milk")
    }
}

@Component(modules = [FoodModule::class])
interface FoodComponent {
    fun inject(intoMapActivity: IntoMapActivity)
}

12.运行时注入

如果一个提供方的构造函数包含一个参数,并且这个参数的具体的值只有在程序运行的时候才知道,比如;

class RuntimeInjectActivity : AppCompatActivity() {


    @Inject
    lateinit var bike: Bike

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_runtime_inject)

        DaggerBikeComponent.builder().bikeModule(BikeModule(100))
            .build().inject(this)

        "bike price = ${bike.price}".log()

    }
}


data class Bike(val price: Int)

我们需要给activity注入一个bike对象,但是bike的价格只有在运行的时候才知道,我们不能直接在Bike类的构造方法上加上@Inject注解,因为我们没法提供价格,我们也不能使用无参数的Provide方法,因为创建Bike对象必须提供一个价格,那么我们该怎么做?我们可以在创建Module的时候提供一个参数,在Provide方法里面把这个价格给传入到Bike的构造方法里面,如下:

@Module
class BikeModule(private val price: Int) {


    @Provides
    fun provideBike(): Bike {
        return Bike(price)
    }
}

完成例子如下:

class RuntimeInjectActivity : AppCompatActivity() {


    @Inject
    lateinit var bike: Bike

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_runtime_inject)

        DaggerBikeComponent.builder().bikeModule(BikeModule(100))
            .build().inject(this)

        "bike price = ${bike.price}".log()

    }
}


data class Bike(val price: Int)


@Module
class BikeModule(private val price: Int) {


    @Provides
    fun provideBike(): Bike {
        return Bike(price)
    }
}

@Component(modules = [BikeModule::class])
interface BikeComponent {

    fun inject(runtimeInjectActivity: RuntimeInjectActivity)
}

13.BindsInstance

在正常情况下,如果我们的module里面的某个Provide方法在创建需要注入的对象的时候需要另外一个对象,例如下面创建Computer对象的时候需要用到Application对象,我们可以通过构造方法来传入一个Application对象,在创建Component对象的时候调用

.computerModule(ComputerModule(application))

来传入Module。

class BuilderActivity : AppCompatActivity() {


    @Inject
    lateinit var computer: Computer

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_builder)


        DaggerComputerComponent
            .builder()
            .computerModule(ComputerModule(application))
            .build().inject(this)
    }
}


data class Computer(val name: String)


@Module
class ComputerModule(private val application: Application) {

    @Provides
    @Singleton
    fun provideComputer(): Computer {
        return Computer(application.packageName)
    }
}

@Singleton
@Component(modules = [ComputerModule::class])
interface ComputerComponent {


    fun inject(builderActivity: BuilderActivity)

}

那么我们有别的方式在Module的Provide方法中提供需要的对象来创建我们要注入的对象吗?
有的,我们先改造我们Module里面的Provide方法

@Module
class ComputerModule {

    @Provides
    @Singleton
    fun provideComputer(application: Application): Computer {
        return Computer(application.packageName)
    }
}

我们可以看到,Application对象是作为方法参数传入到provideComputer方法里面,意味着我们需要提供一个Application对象。现在我们再来改造我们的Component注入器:

@Singleton
@Component(modules = [ComputerModule::class])
interface ComputerComponent {


    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        fun build(): ComputerComponent


    }

    fun inject(builderActivity: BuilderActivity)

}

我们通过@Component.Builder注解来标识我们要自己创建Component的构造器(Builder),在通过** @BindsInstance**来告诉构造器Module需要的Application是通过这个方法传入的,那么我们的注入方法就变成了

DaggerComputerComponent.builder()
            .application(application)
            .build()
            .inject(this)

14. SubComponent

我们有时会遇到这么一种情况,一个module的provide方法的参数是另外一个module的provide的方法的返回值,比如:

class Member

class MemberA @Inject constructor(val member: Member)

@Module
class MemberModule {


    @Provides
    fun provideMember(): Member {
        return Member()
    }
}


@Module
class MemberAModule {

    @Provides
    fun provideMemberA(member: Member): MemberA {
        return MemberA(member)
    }
}

我们要创建MemberA对象,需要一个Member对象,但是Member对象是另外一个Module提供的,我们这时应该怎么办?我们有以下两种方法:

  • Component dependencies
    我们可以在MemberComponent中显式暴露Member对象,并在MemberAComponent中声明依赖MemberComponent,代码如下:
@Component(modules = [MemberModule::class])
interface MemberComponent {


    //暴露member对象,方便其他component调用
    fun member(): Member

}

@Component(modules = [MemberAModule::class], dependencies = [MemberComponent::class])
interface MemberAComponent {

    fun inject(subComponentActivity: SubComponentActivity)
}

在注入的时候,我们先创建被依赖的Component,在创建DaggerMemberAComponent的时候依次传入MemberAModule对象和DaggerMemberComponent

val daggerMemberComponent = DaggerMemberComponent.create()


        DaggerMemberAComponent
            .builder()
            .memberAModule(MemberAModule())
            .memberComponent(daggerMemberComponent)
            .build()
            .inject(this)
  • Subcomponent
    我们也可以利用Subcomponent,在MemberComponent中声明一个返回值为MemberBComponent的方法,传入的参数为MemberBModule,在MemberBComponent上把@Component换成@Subcomponent注解,其余的和@Componen一样,代码如下
@Component(modules = [MemberModule::class])
interface MemberComponent {

    fun createSub(): MemberBComponent

}



@Subcomponent(modules = [MemberBModule::class])
interface MemberBComponent {

    fun inject(subComponentActivity: SubComponentActivity)
}

注意在注入的时候,利用daggerMemberComponent来创建我们的daggerMemberComponent来完成注入

        val daggerMemberComponent = DaggerMemberComponent.create()

        daggerMemberComponent.createSub().inject(this)

15.Subcomponent.Builder

和前面提到的@Component.Builder一样,Component关联的Module的一个Provide方法需要一个参数,我们可以自己去创建一个Builder,并通过@BindsInstance来注解一个方法来传入参数

@Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        fun build(): ComputerComponent


    }

同样,在SubComponent中,我们同样可以利用类似的方法来传入参数给Module来创建我们需要的依赖,实现这一目标需要以下几个步骤

  • SubComponent内部创建一个Builder接口,然后创建两个方法,一个方法传入我们Module需要的参数,返回值为Builder类型,并且在方法上加上@BindsInstance注解;另一个方法返回SubComponent,如下
@Subcomponent(modules = [SonModule::class])
interface SonComponent {

    fun inject(subComponentBuilderActivity: SubComponentBuilderActivity)


    @Subcomponent.Builder
    interface Builder {

        fun build(): SonComponent

        @BindsInstance
        fun name(name: String): Builder
    }
}
  • Component创建一个返回类型为我们刚才新建的Builder接口的方法
@Component(modules = [FatherModule::class])
interface FatherComponent {

    fun createSonBuilder(): SonComponent.Builder
}
  • 在注入的时候,先创建Component,然后调用Component的创建SubComponentBuilder的方法来创建Builder,再利用Builder创建SubComponent
DaggerFatherComponent.builder().build().createSonBuilder().name("hankuke").build()
            .inject(this)

以上就是dagger2的内容,链接在这里
下面我们在讲dagger.android,主要是和Android相关的东西

16.在安卓中使用注入

我们可以在Android里面按照如下的方式完成注入:

DaggerMainComponent
            .builder()
            .build()
            .inject(this)

但是这么写一是很臃肿,几乎每个需要注入的地方都要这么写,而是几乎每个需要注入的地方代码都一样,显得有些不太好。当然在Android里面,dagger2提供了更简单的方式,我们可以按照以下几个步骤来完成Android中的注入:

  • AppComponent中引入AndroidInjectionModule
@Singleton
@Component(modules = [AndroidInjectionModule::class,MainActivityModule::class])
interface AppComponent {

    fun inject(app: App)
}
  • 声明ActivitySubcomponent并实现AndroidInjector接口,然后在SubComponent内部声明一个Factory并实现AndroidInjector.Factory
@Subcomponent(modules = [UserModule::class])
interface MainActivitySubcomponent : AndroidInjector<MainActivity> {

    @Subcomponent.Factory
    interface Factory : AndroidInjector.Factory<MainActivity>
}
  • 编写我们的UserModule类和User类
data class User(val name: String, val age: Int)

@Module
class UserModule {

    @Provides
    fun provideUser(): User = User("lufei", 20)
}
  • 声明ActivityModule并和ActivitySubcomponent建立关联,创建一个方法声明绑定关系
@Module(subcomponents = [MainActivityModule_ContributeMainActivity.MainActivitySubcomponent::class])
abstract class MainActivityModule {


@Binds
@IntoMap
@ClassKey(MainActivity::class)
abstract fun bindMainActivityInjectorFactory(factory: MainActivitySubcomponent.Factory): AndroidInjector.Factory<*>
    
    
}
  • 让我们的Application实现HasAndroidInjector接口,并且注入一个DispatchingAndroidInjector<Any>作为HasAndroidInjector实现方法androidInjector()的返回值

class App : Application(), HasAndroidInjector {


    @Inject
    lateinit var androidInjector: DispatchingAndroidInjector<Any>

    override fun androidInjector(): AndroidInjector<Any> {
        return androidInjector
    }
}
  • ApplicationonCreate()方法里面为Application注入
 override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.create().inject(this)
    }
  • ActivityonCreate()super.onCreate()方法之前为当前Activity注入
class MainActivity : AppCompatActivity() {


    @Inject
    lateinit var user: User

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        user.toString().log()
    }
}

-大功告成

17.更加简化的方式

上面的代码已经可以完成我们的需求,但我们还有一种更加简单的方式来完成对Activity的注入。
我们先删除MainActivitySubcomponent类,并在MainActivityModule中声明下面的方法:

@ContributesAndroidInjector(modules = [UserModule::class])
    abstract fun contributeMainActivity():MainActivity

来代替

 @Binds
 @IntoMap
 @ClassKey(MainActivity::class)
 abstract fun bindMainActivityInjectorFactory(factory: MainActivitySubcomponent.Factory): AndroidInjector.Factory<*>

重新编译项目,我们可以看到dagger自动帮我们生成了一个类MainActivityModule_ContributeMainActivity,类代码如下:

@Module(subcomponents = MainActivityModule_ContributeMainActivity.MainActivitySubcomponent.class)
public abstract class MainActivityModule_ContributeMainActivity {
  private MainActivityModule_ContributeMainActivity() {}

  @Binds
  @IntoMap
  @ClassKey(MainActivity.class)
  abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory(
      MainActivitySubcomponent.Factory builder);

  @Subcomponent(modules = UserModule.class)
  public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory<MainActivity> {}
  }
}

我们可以看到,我们在未简化之前的操作,如声明SubComponent类,声明** bindAndroidInjectorFactory方法,声明@Subcomponent.Factory**工厂类,都由dagger帮助我们完成了。

相关文章

网友评论

    本文标题:Android使用dagger2,语言为kotlin

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