Skip to content

Latest commit

 

History

History
846 lines (601 loc) · 25.3 KB

Android-DI从入门到放弃.md

File metadata and controls

846 lines (601 loc) · 25.3 KB

依赖注入 : Dependency Injection,DI

什么是依赖?

比如类ClassroomActivity中用到了Teacher类的实例:

class ClassroomActivity {
  val teacher = Teacher();
}

这里Teacher就被称为ClassroomActivity的依赖

依赖存在的问题

想象我们程序中有多个地方需要Teacher对象,那就要在很多地方手动构造Teacher, 万一Teacher的构造方式发生了变化, 那你就要改动多处来实现Teacher新的构造方式

导致上面这个问题的原因是 : 依赖的创建方式与使用方耦合与业务逻辑 , 依赖注入(DI)就是为了解决这个问题

什么是依赖注入

依赖注入是这样的一种行为,在类ClassroomActivity中不主动创建Teacher的对象,而是通过外部传入Teacher对象形式来设置依赖, 即非自己主动初始化依赖(对象),而是通过外部传入依赖的方式

下面就是一种外部注入依赖的方式(外部set):

class ClassroomActivity {
  Teacher mTeacher;
      
  public void setEnergy(Teacher teacher) {
      mTeacher  = teacher;
  }
}

目前Android中有许多依赖注入(DI)框架, 本文主要介绍一下DaggerKoin,了解一下它们的实现原理

Dagger

基本实现概述

Dagger的核心实现原理是:基于注解在编译期产生依赖注入相关代码,然后开发者调用生成的代码进行依赖注入, 以一个简单的使用场景为例:

使用@Inject描述你需要的依赖:

class ClassroomActivity : Activity() {
    @Inject
    lateinit var teacher: Teacher
}

使用@Provides描述依赖的创建方式:

@Module
class ClassroomModule {
    @Provides
    fun provideTeacher() = Teacher()
}

Dagger会在编译时产生下面代码:

public final class ClassroomModule_ProvideTeacherFactory implements Factory<Teacher> {
  ....
  public static Teacher provideTeacher(ClassroomModule instance) {
    return instance.provideTeacher();
  }
}

public final class ClassroomActivity_MembersInjector implements MembersInjector<ClassroomActivity> {
    public static void injectTeacher(ClassroomActivity instance, Teacher teacher) {
      instance.teacher = teacher;
    }
}

在使用时调用下面方法来实现依赖注入:

ClassroomActivity_MembersInjector.(activity, ClassroomModule_ProvideTeacherFactory.provideTeacher(new ClassModule()))

Dagger会生成依赖实例构造的工厂方法依赖注入相关模板方法

其他特性

除了基本的依赖注入外Dagger还支持 :

  1. 管理依赖的生命周期
  2. 支持跨Module(androidstudio中的)的依赖注入

Dagger�有完善、强大的依赖注入功能,不过Dagger的学习难度比较高,不是那么容易上手, 而Koin相较于Dagger在上手程度上则容易的多:

Koin

Koin是为Kotlin开发者提供的一个实用型轻量级依赖注入框架,采用纯Kotlin语言编写而成,仅使用功能解析,无代理、无代码生成、无反射,它的实现依赖于kotlin强大的语法糖(例如 Inline、Reified 等等)和函数式编程。

它的核心实现原理很简单: 利用函数类型保存依赖实例的构造方式,在运行时动态查找并完成依赖实例的创建, 它的实现原理如下 :

koinDefinition<T>是用来描述依赖实例的创建的函数类型:

typealias Definition<T> = Scope.(DefinitionParameters) -> T

koin提供了一些函数来创建Definition,比如:

val appModule = module {
    factory { RandomId() }
}

module是一个函数,在App启动时koin会调用它, 这是就会把依赖的创建方式存储起来:

inline fun <reified T> factory(
        qualifier: Qualifier? = null,
        override: Boolean = false,
        noinline definition: Definition<T>
): BeanDefinition<T> {
    return Definitions.saveFactory(qualifier, definition, rootScope, makeOptions(override))
}

这里使用内联函数可以提高性能,具体化参数reified使编码更加简洁

在使用时,koin提供下面函数来构建依赖实例:

val randomId: RandomId by inject()

inject()函数最终会去koin的全局集合中寻找RandomIdDefinition<T>:

private val _instances = HashMap<IndexKey, InstanceFactory<*>>()

internal fun <T> resolveInstance(indexKey: IndexKey, parameters: ParametersDefinition?): T? {
    return _instances[indexKey]?.get(defaultInstanceContext(parameters)) as? T
}

InstanceFactory会调用函数类型来创建对应的实例:

 open fun create(context: InstanceContext): T {
    val parameters: DefinitionParameters = context.parameters
    return beanDefinition.definition.invoke(
        context.scope,
        parameters
    )
}

koin的实现并不复杂,dagger中有的一些语法在koin中基本也存在,koin相较于dagger,它把依赖的创建方式以函数对象的形式保存在了内存中简化了用法,不过也引入了一定的内存开销, 并且koin目前的实现不能实现接口和实现分离

dagger的语法比较复杂, 下面就简单学习和理解一下dagger中的各种用法:

Dagger

1. Dagger 基础

1.1. @Inject

它既可以用来指明对象的依赖,也可以用来指明依赖对象的创建方式, 不同的用法Dagger会在编译期生成不同的辅助类来完成依赖实例的注入 :

1.1.1. 声明在成员变量上

class StudentTest {
    @Inject
    lateinit var nameInfo: NameInfo
}

Dagger会在编译期生成对应的依赖对象注入类(StudentTest_MembersInjector),在运行时它用来给StudentTest对象的nameInfo注入NameInfo实例:

public final class StudentTest_MembersInjector implements MembersInjector<StudentTest> {
  private final Provider<NameInfo> nameInfoProvider;

  public StudentTest_MembersInjector(Provider<NameInfo> nameInfoProvider) {
    this.nameInfoProvider = nameInfoProvider;
  }

  public static MembersInjector<StudentTest> create(Provider<NameInfo> nameInfoProvider) {
    return new StudentTest_MembersInjector(nameInfoProvider);}

  @Override
  public void injectMembers(StudentTest instance) {
    injectNameInfo(instance, nameInfoProvider.get());
  }

  public static void injectNameInfo(StudentTest instance, NameInfo nameInfo) {
    instance.nameInfo = nameInfo;
  }
}

Provider<NameInfo>创建NameInfo的模板接口

1.1.2. 声明在构造函数上

class Student @Inject constructor(val nameInfo: NameInfo) : IPeople

Dagger会在编译期生成Student_Factory类, 这个类会依赖构造参数来构造Student对象:

public final class Student_Factory implements Factory<Student> {
  private final Provider<NameInfo> nameInfoProvider;

  ...

  public static Student_Factory create(Provider<NameInfo> nameInfoProvider) {
    return new Student_Factory(nameInfoProvider);
  }

  public static Student newInstance(NameInfo nameInfo) {
    return new Student(nameInfo);
  }
}

如果构造参数上标记了@Inject,那么Dagger会先寻找这个参数的XX_Factory,创建这个参数对象,然后再创建目前对象

1.2. @Module

它用来封装创建对象实例的方法:

@Inject的方式散落在各处不好管理

@Module
class StudentModule {
    @Provides
    fun provideNameInfo() = NameInfo("wang", "pengcheng")
}

@Provides需要声明在@Module标注的类中,它用来指明依赖实例的创建方式,对于每一个@Provides标注的方法, Dagger会在编译期生成对应的Factory:

StudentModule_ProvideNameInfoFactory

public final class StudentModule_ProvideNameInfoFactory implements Factory<NameInfo> {
  private final StudentModule module;

  ...

  public static NameInfo provideNameInfo(StudentModule instance) {
    return Preconditions.checkNotNull(instance.provideNameInfo(), "Cannot return null from a non-@Nullable @Provides method");
  }
}

即通过StudentModule().provideNameInfo()创建对应的NameInfo实例。

1.3. @Component

管理依赖实例, 链接@Inject@Module, 可以为对象注入依赖实例 :

@Component(modules = [StudentModule::class])
interface StudentComponent {
    fun inject(studentTest: StudentTest)
}

StudentComponent会收集modules = [StudentModule::class]中依赖的创建方式,并通过这些方式创建对象实例赋值给StudentTest需要的成员变量。

Dagger会在编译期为这个接口生成对应的实现类DaggerStudentComponent,这个类实现了StudentTest的依赖注入:

public final class DaggerStudentComponent implements StudentComponent {
  private final StudentModule studentModule;

  private DaggerStudentComponent(StudentModule studentModuleParam) {
    this.studentModule = studentModuleParam;
  }

  ...
  
  @Override
  public void inject(StudentTest studentTest) {
    injectStudentTest(studentTest);}

  @Override
  public NameInfo provideNameInfo() {
    return StudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule);}

  private StudentTest injectStudentTest(StudentTest instance) {
    StudentTest_MembersInjector.injectNameInfo(instance, StudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule));
    return instance;
  }

  public static final class Builder {
    ...
  }
}

DaggerStudentComponent私有化构造函数,通过Builder来创建, 创建时需要传入StudentModule对象

1.3.1. 暴露依赖实例

可以在@Component添加方法来暴露依赖实例:

@Component(modules = [ClassroomModule::class])
interface ClassroomComponent {
    ...
    fun getTeacher():Teacher
}

Dagger会生成工厂方法创建Teacher实例, 这样在代码中就可以直接获取:

val teacher = DaggerClassroomComponent.builder().classroomModule(ClassroomModule()).build().getTeacher()

1.4. Dagger的简单使用

通过对上面三大金刚的介绍, 我们了解了Dagger的基本使用与实现原理, 在编码时就可以这样使用:

class StudentTest {
    @Inject
    lateinit var nameInfo: NameInfo

    constructor() {
        DaggerStudentComponent.builder().studentModule(StudentModule()).build().inject(this)
        Log.d("dagger-test", "studentName : ${nameInfo.first} ${nameInfo.last}")
    }
}

logcat输出:

 D/dagger-test: wang pengcheng

1.5. @Binds

它也可以像@Provides指明一个依赖实例的提供方式, 不过它只能声明在抽象方法上, 它用来告诉Dagger接口应采用哪种实现:

@Module(includes = [StudentModule::class])
abstract class ClassroomModule {
    @Binds
    abstract fun bindPeopleWithStudent(test: Student): IPeople
}

@Module(includes = [StudentModule::class])可以使ClassroomModule拥有StudentModule创建依赖实例的能力

fun bindPeopleWithStudent(test: Student): IPeople 定义 IPeople的实现类为Student

对于Student实例的提供可以使用@Provides�,也可以使用@Inject标注在构造方法上 :

class Student @Inject constructor(val nameInfo: NameInfo) : IPeople

@Binds解决了面向接口编程的需求, 即指明了依赖接口的实现类。当然这种情况也可以用@Provides实现(方法实体是类型的强转), 但@Binds明显更加清晰 :

  @Provides
  fun providePeopleWithStudent() = Student(provideNameInfo()) as IPeople

1.6. Component依赖

如果ClassroomComponent需要使用StudentComponent的依赖实例, 则可以这样写:

@Component(modules = [ClassroomModule::class], dependencies = [StudentComponent::class])
interface ClassroomComponent {
    fun inject(test: ClassroomTest)
}

@Component(modules = [StudentModule::class])
interface StudentComponent {
    fun provideNameInfo(): NameInfo  //传递到依赖他的Component
}

StudentComponent可以通过StudentModule提供NameInfo, ClassroomComponent通过dependencies = [StudentComponent::class]来使用NameInfo, 除了dependencies = [StudentComponent::class]外, 还需要StudentComponent暴露对应的依赖实例方法 fun provideNameInfo(): NameInfo

上面经过Dagger编译会生成:

public final class DaggerClassroomComponent implements TestComponent {
  private final StudentComponent studentComponent;
  
  private ClassroomTest injectClassroomTest(ClassroomTest instance) {
    ClassroomTest_MembersInjector.injectStudent(instance, getStudent());
    return instance;
  }

}

public final class DaggerStudentComponent implements StudentComponent {
  @Override
  public NameInfo provideNameInfo() {
    return StudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule);
  }
}

StudentComponent变成了DaggerTestComponent的成员变量,这样就可以为Test注入NameInfo依赖, 使用时:

class ClassroomTest {
    @Inject
    lateinit var student: IPeople

    constructor() {
        DaggerClassroomComponent.builder().studentComponent(DaggerStudentComponent.builder().studentModule(StudentModule()).build()).build().inject(this)
        Log.d("dagger-test", "studentName : ${student.getName()}")
    }
}

1.7. Subcomponent

上面dependencies = [XXXComponent::class]可以简单的理解为: AComponentBComponent变成成员变量, 然后使用BComponent其依赖注入的能力

@Subcomponent则可以使BComponent变为AComponent的内部类,然后使用AComponent的依赖注入能力(@Module):

AComponent:

@Component(modules = [ClassroomModule::class])
interface ClassroomComponent {

    fun inject(test: ClassroomTest)

    fun studentComponent(): StudentComponent.Builder  //用来构造StudentComponent

}
@Module(subcomponents = [StudentComponent::class])
class ClassroomModule {
    @Provides
    fun provideTeacher() = Teacher()
}

subcomponents = [StudentComponent::class]表示StudentComponent可以看到ClassroomModule提供的依赖实例 :

BComponent:

@Subcomponent(modules = [StudentModule::class])
interface StudentComponent {

    fun inject(studentTest: StudentTest) //StudentTest对象依赖Teacher实例

    @Subcomponent.Builder
    interface Builder {
        fun build(): StudentComponent
    }
}

使用@Subcomponent声明子Component, 还需要显示声明Builder, 这样父组件才知道如何创建子组件

上面经过Dagger编译后不会生成DaggerStudentComponent, 只会生成DaggerClassroomComponent :

public final class DaggerClassroomComponent implements ClassroomComponent {

  private final class StudentComponentBuilder implements StudentComponent.Builder {
    @Override
    public StudentComponent build() {
      return new StudentComponentImpl(new StudentModule());
    }
  }

  private final class StudentComponentImpl implements StudentComponent {
    private final StudentModule studentModule;

    private StudentComponentImpl(StudentModule studentModuleParam) {
      this.studentModule = studentModuleParam;
    }

    @Override
    public void inject(StudentTest studentTest) {
      injectStudentTest(studentTest);}

    private StudentTest injectStudentTest(StudentTest instance) {
      StudentTest_MembersInjector.injectNameInfo(instance, StudentModule_ProvideNameInfoFactory.provideNameInfo(studentModule));
      StudentTest_MembersInjector.injectTeacher(instance, ClassroomModule_ProvideTeacherFactory.provideTeacher(DaggerClassroomComponent.this.classroomModule));
      return instance;
    }
  }
}

可以看到StudentTest injectStudentTest(StudentTest instance)方法中使用了ClassroomModule提供的依赖实例方法。

因为没有生成DaggerStudentComponent,所以对于DaggerStudentComponent的构建必须这样做 :

class StudentTest {
    
    @Inject lateinit var nameInfo: NameInfo
    
    @Inject lateinit var teacher: Teacher

    constructor() {
        DaggerClassroomComponent.builder().classroomModule(ClassroomModule()).build().studentComponentBuilder().build().inject(this)
        Log.d("dagger-test", "teacher name : ${teacher.name}")
    }
}

1.8. @Scope

@ScopeDagger中和@Component紧紧相连 : 如果一个@Module提供的依赖实例声明了和@Component相同的@Scope,那么这个@Component会使用同一个依赖实例来做依赖注入 :

@Singleton
@Component(modules = [SingletonModule::class])
interface AppComponent : AndroidInjector<TestApplication> {
    fun inject(app: Application)

    fun getClassroom():Classroom
}

你也可以直接手动获取单例Classroom

@Module
class SingletonModule {
    @Singleton
    @Provides
    fun provideClassRoom() = Classroom()
}

@Singleton是Dagger内置的@Scope,一般用来定义一个@Component内唯一的依赖实例

class Test {
    ...
    @Inject lateinit var room1: Classroom
    @Inject lateinit var room2: Classroom
    ...    
}

上面这个Test的两个成员变量其实引用的是同一个Classroom实例, 不过在使用@Scope是需要注意 : @Subcomponent不能和@Component声明相同的@Scope

1.8.1. 单例的实现原理

其实看一下Component的注入实现就明白了:

private ClassroomActivity injectClassroomActivity(ClassroomActivity instance) {
  ClassroomActivity_MembersInjector.injectTeacher1(instance, provideTeacherProvider.get());
  ClassroomActivity_MembersInjector.injectTeacher2(instance, provideTeacherProvider.get());
  return instance;
}

即使用同一个Provider<T>来获取的对象

1.9 @Named

Dagger中提供依赖实例的方式一种有两种 :

  1. @Inject标注在构造函数上
  2. @Provider定义在@Module

那如果使用这两种方式定义了同一个依赖实例呢? 这种情况下Dagger在选择依赖实例时就会迷失,会发生编译报错, 可以使用@Named来解决依赖迷失的问题 :

@Module
class ClassroomModule {
    @Provides
    @Named("teacher1")
    fun provideTeacher1() = Teacher()


    @Provides
    @Named("teacher2")
    fun provideTeacher2() = Teacher()
}

class ClassroomActivity : Activity() {

    @Inject
    @field:Named("teacher1")
    lateinit var teacher1: Teacher

}

2. Dagger in Android

上面介绍了Dagger的基本原理与使用方法, 不过在Android中如何使用Dagger呢?

如果按照上面的基本用法使用Dagger则会遇到一些列的问题, 比如像Activity/Fragment一般是由系统创建的, 所以我们不能把它变成依赖实例, 也不能完成自动依赖注入, 因此我们需要写出类似下面这种代码 :

class ClassroomActivity : Activity() {
  
    @Inject lateinit var teacher: Teacher

    override fun onCreate(savedInstanceState: Bundle?) {
      ClassroomActivityComponent.builder().build().inject(this);
    }
}

这样写有两点不好:

  1. 太多类似的代码
  2. 每个Activity/Fragment在注入是都需要知道其对应的DaggerXXXComponent

怎么解决呢?Dagger官方给出的实现步骤如下:

2.1. Activity的自动注入

  1. 顶层Component绑定AndroidInjectionModule

  2. 定义一个@Subcomponent并继承自AndroidInjector<T>

@Subcomponent(modules = [ClassroomModule::class])
interface ClassroomActivitySubcomponent : AndroidInjector<ClassroomActivity> {

    @Subcomponent.Factory
    interface Factory : AndroidInjector.Factory<ClassroomActivity>

}
  1. 定义一个@Module并绑定先前定义的@Subcomponent
@Module(subcomponents = [ClassroomActivitySubcomponent::class])
abstract class ClassroomActivityModule {

    @Binds
    @IntoMap
    @ClassKey(ClassroomActivity::class)
    abstract fun bindClassroomActivityFactory(factory: ClassroomActivitySubcomponent.Factory): AndroidInjector.Factory<*>

}
  1. 把上面定义的@Module绑定到@Component
@Component(modules = [AndroidInjectionModule::class, ClassroomActivityModule::class])
interface AppComponent : AndroidInjector<TestApplication> {

    fun inject(app: Application)

}
  1. Application初始化DispatchingAndroidInjector
class TestApplication : Application(), HasAndroidInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>

    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.create().inject(this)
    }

    override fun androidInjector() = dispatchingAndroidInjector
}
  1. 在Activity中注入依赖
class ClassroomActivity : Activity() {

    @Inject
    lateinit var teacher: Teacher

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        AndroidInjection.inject(this)
        setContentView(TextView(this).apply {
            text = "${teacher.nameInfo.getName()} "
        })
    }
}

如果你不想每次都写AndroidInjection.inject(this), 你可以直接让你的Activity继承自DaggerAcitivity

AndroidInjection.inject(this)会自动寻找AppComponent中的依赖实例并注入到ClassroomActivity中。

对于上面2、3两步,其实可以使用@ContributesAndroidInjector合为一步:

@Module
abstract class ClassroomActivityModule {

    @ContributesAndroidInjector(modules = [ClassroomModule::class])
    abstract fun contributeInjectorClassroomActivity(): ClassroomActivity

}

Dagger在编译时会根据@ContributesAndroidInjector自动生成上面2、3步的代码。

那上面实现原理是什么呢?

2.2. Activity自动注入实现原理

来看一下DaggerAppComponent中生成的依赖注入代码:

public final class DaggerAppComponent implements AppComponent {
  ...

  private Map<Class<?>, Provider<AndroidInjector.Factory<?>>> getMapOfClassOfAndProviderOfAndroidInjectorFactoryOf() {
    return Collections.<Class<?>, Provider<AndroidInjector.Factory<?>>>singletonMap(ClassroomActivity.class, (Provider) classroomActivitySubcomponentFactoryProvider);
  }

  @SuppressWarnings("unchecked")
  private void initialize() {
    this.classroomActivitySubcomponentFactoryProvider = new Provider<ClassroomActivityModule_ContributeInjectorClassroomActivity.ClassroomActivitySubcomponent.Factory>() {
      @Override
      public ClassroomActivityModule_ContributeInjectorClassroomActivity.ClassroomActivitySubcomponent.Factory get(
          ) {
        return new ClassroomActivitySubcomponentFactory();}
    };
  }
  
  ...

  private final class ClassroomActivitySubcomponentImpl implements ClassroomActivityModule_ContributeInjectorClassroomActivity.ClassroomActivitySubcomponent {
    
    ...

    @Override
    public void inject(ClassroomActivity arg0) {injectClassroomActivity(arg0);}

    private ClassroomActivity injectClassroomActivity(ClassroomActivity instance) {
      ClassroomActivity_MembersInjector.injectTeacher(instance, getTeacher());
      return instance;
    }
  }
}

上面这段逻辑核心点有两个:

  1. ClassroomActivity是利用ClassroomActivitySubcomponentImpl完成依赖注入的
  2. ClassroomActivitySubcomponentImpl保存在了DaggerAppComponent.getMapOfClassOfAndProviderOfAndroidInjectorFactoryOf的map中

然后继续看一下AndroidInjection.inject(this)发生了什么:

public final class AndroidInjection {

    public static void inject(Activity activity) {
      Application application = activity.getApplication();
      ...
      injector = ((HasAndroidInjector) application).androidInjector();
      ...
      injector.inject(activity);
    }
}

即它最终调用了activity.getApplication().androidInjector().injector.inject(activity),其实就是调用到了DispatchingAndroidInjector<Any>.inject(),而它最终会调用到:

public boolean maybeInject(T instance) {
  Provider<AndroidInjector.Factory<?>> factoryProvider =
        injectorFactories.get(instance.getClass().getName());
  AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get();
  ...
  factory.create(instance).inject(instance);
}

其实上面injectorFactories就是DaggerAppComponent中的那个Factory Map, 最终调用到ClassroomActivitySubcomponentImpl.inject()

4. 参考文档

google dagger

dagger android

kotlin-内联函数

kotlin-lambda