Dagger2 AndroidInjector原理探究

AndroidInjector在实际的项目中已经频繁使用了,但是其底层的原理一直没有关注,只是简单的使用。这篇笔记就来探究下它的实现原理。

我们要先从Component的依赖开始讲起,然后一步一步往AndroidInjector的原理靠近。

在Dagger2里面@Component.dependencies和@Subcomponent都可以实现Component依赖,而@Subcomponent又可以分成使用和不使用@Module.subcomponents去声明依赖两种。所以算下来应该是三种实现依赖的方式。今天主要想介绍的是@Module.subcomponents的方式,但是其他两种我也先过一下。

@Component.dependencies实现依赖

@Component.dependencies是一种比较简单的方式,举个简单的例子,MainActivityComponent是子Component,它需要给MainActivity注入AppCommonData和MainActivityData。

但MainActivityModule只提供了MainActivityData,AppCommonData由dependencies指定的AppComponent提供注入:

@Component(modules = [MainActivityModule::class], dependencies = [AppComponent::class])
interface MainActivityComponent {
    fun inject(activity: MainActivity)
}

@Module
class MainActivityModule {
    @Provides
    fun providerMainActivityData(): MainActivityData {
        return MainActivityData()
    }
}

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var appCommonData: AppCommonData
    @Inject
    lateinit var mainActivityData: MainActivityData
    ...
}

由于使用了dependencies的方式,AppComponent需要将AppCommonData暴露出来给MainActivityComponent使用,如果不去暴露的话MainActivityComponent是拿不到的:

@Component(modules = [AppModule::class])
interface AppComponent {
    fun provideAppCommonData(): AppCommonData
}

@Module
class AppModule {
    @Provides
    fun provideAppCommonData(): AppCommonData {
        return AppCommonData()
    }
}

当使用dependencies方式的适合会生成DaggerMainActivityComponent,我们需要先创建父Component,然后在创建MainActivityComponent的时候将父Component传入实现依赖的注册:

class DemoApplication : Application() {
    private lateinit var appComponent: AppComponent

    lateinit var mainActivityComponent: MainActivityComponent
        private set

    override fun onCreate() {
        super.onCreate()

        appComponent = DaggerAppComponent.builder().build()

        mainActivityComponent = DaggerMainActivityComponent.builder()
            .appComponent(appComponent)
            .build()
    }
}

注入的时候需要找到MainActivityComponent去注入:

class MainActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        (applicationContext as DemoApplication)
            .mainActivityComponent
            .inject(this)
        ...
    }
}

这种方式的优点是父Component不需要知道子Component的存在,缺点是父Component需要显示提供子Component需要注入的对象,一旦需要提供的注入对象比较多的时候写起来就很啰嗦。

Demo代码

@Subcomponent实现依赖

我们可以使用@Subcomponent去实现依赖,将子Component的注解改成@Subcomponent,dependencies去掉其他不变:

@Subcomponent(modules = [MainActivityModule::class])
interface MainActivityComponent {
    fun inject(activity: MainActivity)
}

@Module
class MainActivityModule {
    @Provides
    fun providerMainActivityData(): MainActivityData {
        return MainActivityData()
    }
}

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var appCommonData: AppCommonData
    @Inject
    lateinit var mainActivityData: MainActivityData
    ...
}

AppComponent不需要直接提供AppCommonData,但是由于使用了Subcomponent之后是不会生成DaggerMainActivityComponent的,它需要AppCommonData去负责创建,所以AppCommonData需要添加DaggerMainActivityComponent的创建方法。这里的主要目的是为了明确依赖关系,因为dagger2不可能知道你想要的依赖关系是怎样的:

@Component(modules = [AppModule::class])
interface AppComponent {
    fun addMainActivityComponent(): MainActivityComponent
}

@Module
class AppModule {
    @Provides
    fun provideAppCommonData(): AppCommonData {
        return AppCommonData()
    }
}

class DemoApplication : Application() {
    private lateinit var appComponent: AppComponent

    lateinit var mainActivityComponent: MainActivityComponent
        private set

    override fun onCreate() {
        super.onCreate()

        appComponent = DaggerAppComponent.builder().build()
        mainActivityComponent = appComponent.addMainActivityComponent()
    }
}

MainActivity在使用注入的时候代码是一样的,我这里就不贴了。这种方式的好处是Subcomponent可以直接使用父类提供的所有注入对象,但是每增加一个Subcomponent,父Component都需要新增一个创建方法。

Demo代码

@Module.subcomponents实现依赖

除了在父Component添加Subcomponent的创建方法之外,我们还可以在父Component的Module里面去声明依赖关系:

@Module(subcomponents = [MainActivityComponent::class])
class AppModule {
    @Provides
    fun provideAppCommonData(): AppCommonData {
        return AppCommonData()
    }
}

这里的@Module.subcomponents相当于Module添加了Subcomponent.Builder的注入能力,我们可以将Subcomponent.Builder注入到任意对象。这里我们注入到DemoApplication,所以AppComponent如下:

@Component(modules = [AppModule::class])
interface AppComponent {
    fun inject(app: DemoApplication)
}

既然是注入Subcomponent.Builder,那么就要求我们的Subcomponent要声明Builder:

@Subcomponent(modules = [MainActivityModule::class])
interface MainActivityComponent {
    fun inject(activity: MainActivity)

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

@Module
class MainActivityModule {
    @Provides
    fun providerMainActivityData(): MainActivityData {
        return MainActivityData()
    }
}

这个时候创建MainActivityComponent就分成了两步,先创建MainActivityComponent.Builder,再创建MainActivityComponent:

class DemoApplication : Application() {
    @Inject
    lateinit var mainActivityComponentBuilder: MainActivityComponent.Builder

    lateinit var mainActivityComponent: MainActivityComponent

    override fun onCreate() {
        super.onCreate()

        DaggerAppComponent.builder()
            .build()
            .inject(this)
        mainActivityComponent = mainActivityComponentBuilder.builder()
    }
}

当然上面的Subcomponent.Builder改成Subcomponent.Factory也是可以的。

Demo代码

@Module.subcomponents实现多绑定

这种方式看上去是麻烦了一些,但是由于Subcomponent.Builder也是dagger2注入的,所以可以用这个特性去让我们的依赖注入做的更加彻底一点。

我们可以看到目前我们的MainActivity实际上是需要知道自己是用哪个Component去注入的,也就是说我们破坏了"一个对象不应该知道它是如何被注入的"这个原则:

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var appCommonData: AppCommonData

    @Inject
    lateinit var mainActivityData: MainActivityData

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        (applicationContext as DemoApplication)
            .mainActivityComponent
            .inject(this)
        ...
    }
}

当然我们使用重载的方式将Component的选择挪到Application中,但是这样依然是在Dagger2框架之外去决定依赖关系。那可不可以做的更彻底些,将MainActivity和Component的依赖关系也使用Dagger2框架去决定呢?当然是可以的,就是使用@Module.subcomponents。

首先我们定义一个BaseComponent作为基类,然后MainActivityComponent和SecondActivityComponent都继承它

interface BaseComponent<T> {
    fun inject(target: T)

    interface Builder<T> {
        fun build(): BaseComponent<T>
    }
}

@Subcomponent(modules = [MainActivityModule::class])
interface MainActivityComponent : BaseComponent<MainActivity> {
    @Subcomponent.Builder
    interface Builder : BaseComponent.Builder<MainActivity>
}

@Subcomponent(modules = [SecondActivityModule::class])
interface SecondActivityComponent : BaseComponent<SecondActivity> {
    @Subcomponent.Builder
    interface Builder : BaseComponent.Builder<SecondActivity>
}

然后定义Activity与Subcomponent.Builder之间的关联:

@Module(subcomponents = [MainActivityComponent::class, SecondActivityComponent::class])
interface ComponentBindingModule {
    @Binds
    @IntoMap
    @ClassKey(MainActivity::class)
    fun mainActivityComponentBuilder(builder: MainActivityComponent.Builder): BaseComponent.Builder<*>


    @Binds
    @IntoMap
    @ClassKey(SecondActivity::class)
    fun secondActivityComponentBuilder(builder: SecondActivityComponent.Builder): BaseComponent.Builder<*>
}

@Component(modules = [AppModule::class, ComponentBindingModule::class])
interface AppComponent {
    fun inject(app: DemoApplication)
}

ComponentBindingModule将MainActivityComponent.Builder和SecondActivityComponent.Builder用对应的Activity的class为key放入Map中,所以Activity在需要注入的时候可以用自己的class为key获取到Subcomponent.Builder然后进行注入:

class DemoApplication : Application() {
    @Inject
    lateinit var componentMap: MutableMap<Class<*>, BaseComponent.Builder<*>>

    @Inject
    lateinit var secondActivityComponentBuilder: SecondActivityComponent.Builder

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

    fun <T : Any> inject(target: T) {
        (componentMap[target.javaClass]?.build() as BaseComponent<T>).inject(target)
    }
}

于是乎Activity的注入就变得很简单了:

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var appCommonData: AppCommonData
    @Inject
    lateinit var mainActivityData: MainActivityData

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        (applicationContext as DemoApplication).inject(this)
        ...
    }
}

class SecondActivity : AppCompatActivity() {
    @Inject
    lateinit var appCommonData: AppCommonData
    @Inject
    lateinit var secondActivityData: SecondActivityData

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        (applicationContext as DemoApplication).inject(this)
        ...
    }
}

我们还可以为其编写BaseActivity去进行注入。

Demo代码

AndroidInjector的原理

AndroidInjector的就是基于上面的技术实现的。我们可以看到DaggerApplication里面注入了一个DispatchingAndroidInjector<Object>,Activity在onCreate的时候就可以拿到这个东西去进行注入:

public abstract class DaggerApplication extends Application implements HasAndroidInjector {
  @Inject volatile DispatchingAndroidInjector<Object> androidInjector;
  ...
  @Override
  public AndroidInjector<Object> androidInjector() {
    ...
    return androidInjector;
  }
  ...
}

那我们来看看DispatchingAndroidInjector其实内部类似的也有两个Map用于获取不同的类对应的AndroidInjector.Factory:

public final class DispatchingAndroidInjector<T> implements AndroidInjector<T> {
    ...
    private final Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactories;
    ...
    @Inject
    DispatchingAndroidInjector(
        Map<Class<?>, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithClassKeys,
        Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithStringKeys) {
      this.injectorFactories = merge(injectorFactoriesWithClassKeys, injectorFactoriesWithStringKeys);
    }
    ...
    @Override
    public void inject(T instance) {
        boolean wasInjected = maybeInject(instance);
        ...
    }
    ...
    public boolean maybeInject(T instance) {
        Provider<AndroidInjector.Factory<?>> factoryProvider = injectorFactories.get(instance.getClass().getName());
        ...
        AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get();
        try {
              AndroidInjector<T> injector = checkNotNull(factory.create(instance), "%s.create(I) should not return null.", factory.getClass());
              injector.inject(instance);
              return true;
        } 
        ...
  }
}

于是乎在inject的时候就能通过类名找到这个AndroidInjector.Factory进而创建AndroidInjector去进行注入。

Map<Class<?>, Provider<AndroidInjector.Factory<?>>> 和Map<String, Provider<AndroidInjector.Factory<?>>>是通过AndroidInjectionModule注入的:

@Module
public abstract class AndroidInjectionModule {
  @Multibinds
  abstract Map<Class<?>, AndroidInjector.Factory<?>> classKeyedInjectorFactories();

  @Multibinds
  abstract Map<String, AndroidInjector.Factory<?>> stringKeyedInjectorFactories();

  private AndroidInjectionModule() {}
}

所以AppComponent里面需要添加这个module依赖:

@Component(modules = [AndroidInjectionModule::class, AppModule::class, ComponentBindingModule::class])
interface AppComponent : AndroidInjector<DemoApplication>

而Map里面找到的都是AndroidInjector.Factory,所以我们的Subcomponent也应该继承AndroidInjector:

@Subcomponent(modules = [MainActivityModule::class])
interface MainActivityComponent : AndroidInjector<MainActivity> {
    @Subcomponent.Factory
    interface Factory : AndroidInjector.Factory<MainActivity>
}

@Subcomponent(modules = [SecondActivityModule::class])
interface SecondActivityComponent : AndroidInjector<SecondActivity> {
    @Subcomponent.Factory
    interface Factory : AndroidInjector.Factory<SecondActivity>
}

ComponentBindingModule里面依然是将AndroidInjector.Factory用对应的Activity的class为key放入Map中:

@Module(subcomponents = [MainActivityComponent::class, SecondActivityComponent::class])
interface ComponentBindingModule {
    @Binds
    @IntoMap
    @ClassKey(MainActivity::class)
    fun mainActivityComponentFactory(factory: MainActivityComponent.Factory): AndroidInjector.Factory<*>


    @Binds
    @IntoMap
    @ClassKey(SecondActivity::class)
    fun secondActivityComponentFactory(factory: SecondActivityComponent.Factory): AndroidInjector.Factory<*>
}

Activity在注入的时候只需要使用AndroidInjection.inject(this)即可:

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var appCommonData: AppCommonData
    @Inject
    lateinit var mainActivityData: MainActivityData

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
        ...
    }
}

Demo代码

ContributesAndroidInjector

上面的MainActivityComponent、SecondActivityComponent的代码还有IntoMap的代码其实比较呆板,我们可以通过ContributesAndroidInjector去优化。所以ComponentBindingModule可以改成下面这个样子,然后:

@Module
abstract class ComponentBindingModule {
    @ContributesAndroidInjector(modules = [MainActivityModule::class])
    abstract fun contributesMainActivity(): MainActivity
    ...
}

它会帮我们生成Subcomponent、IntoMap代码:

//下面的代码可以省略,被ContributesAndroidInjector代替了
@Module
class MainActivityModule {
    @Provides
    fun providerMainActivityData(): MainActivityData {
        return MainActivityData()
    }
}

@Binds
@IntoMap
@ClassKey(MainActivity::class)
fun mainActivityComponentFactory(factory: MainActivityComponent.Factory): AndroidInjector.Factory<*>

Demo代码

参考文章

Dagger & Android

Multibindings

在Dagger 2中Activities和Subcomponents的多绑定

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

推荐阅读更多精彩内容