阅读 590
笔记|Dagger2进阶笔记

笔记|Dagger2进阶笔记

概述

入门笔记里讲了 Dagger 的一些基础用法,但实际项目开发中很多不会这么使用。在 Android 实际开发中,我们需要用到许多 Activity 与 Fragment, 当进行依赖注入时都需要进行许多重复性的操作,比如在 @Component 的组件中加入 inject 方法,并在对应的 Activity 或 Fragment 等中调用注入的方法。于是 Android 与 Dagger 团队一起为这些场景提供了更简便的实现方式,这在实际开发中更有实用价值。

另外这篇文章还会学习一下 Dagger 中一些注解的用法。

@Binds

之前讲过可以使用 @Module 和 @Provides 注解提供依赖项,当我们需要提供的依赖项是一个接口时,可以使用 @Binds 注解:

@Module
interface TmpModule {
    @Binds
    fun provideOperate(impl: OperateImpl): IOperate
}
复制代码

注意这种方式上面的 OperateImpl 需要使用 @Inject 注解或者 @Module + @Provides 来告知 Dagger 如何提供依赖。当然也可以直接这样:

@Module
class TmpModule {
    @Provides
    fun provideOperate(): IOperate {
        return OperateImpl()
    }
}
复制代码

@IntoSet

可以在 Module 中定义多个返回值类型相同的方法,用来提供 Set 集合的依赖项。

@Module
class TmpModule {
    @Provides
    @IntoSet
    fun provide1(): String {
        return "A"
    }

    @Provides
    @IntoSet
    fun provide2(): String {
        return "B"
    }
}
复制代码

接着可以使用:

class LoginActivity : AppCompatActivity() {
    @Inject
    lateinit var set: MutableSet<String>

    override fun onCreate(savedInstanceState: Bundle?) {
        DaggerAppComponent.create().inject(this)
        super.onCreate(savedInstanceState)
        println("Login: $set") // Login: [A, B]
    }
}
复制代码

@IntoMap

跟 @IntoSet 类似,不过它用来给 Map 提供依赖项。

@Module
class TmpModule {
    @Provides
    @IntoMap
    @IntKey(1)
    fun provide1(): String = "A"

    @Provides
    @IntoMap
    @IntKey(2)
    fun provide2(): String = "B"
}
复制代码

Dagger 提供了许多基础类型的 Key 注解,如 @IntKey, @StringKey 等,另外还可以自定义 Key 注解:

@Target(AnnotationTarget.FUNCTION)
@MapKey
annotation class GameKey(val value: KClass<out Game>)
复制代码

AndroidInjector

注入Activity

可以用来在 Android 中简化注入操作,在使用前需要添加依赖:

dependencies {
    implementation 'com.google.dagger:dagger-android:2.x'
    implementation 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries
    kapt 'com.google.dagger:dagger-android-processor:2.x'
    kapt 'com.google.dagger:dagger-compiler:2.x'
}
复制代码

1、首先把 AndroidInjectionModule 模块加入应用组件 ApplicationComponent 中,确保必要的基础类型可用(ensure that all bindings necessary for these base types are available)。

2、创建一个继承自 AndroidInjector 的 SubComponent 子组件。

@Subcomponent
interface LoginSubComponent : AndroidInjector<LoginActivity> {
    @Subcomponent.Factory
    interface Factory : AndroidInjector.Factory<LoginActivity?>
}
复制代码

3、接着将上面的子组件加入到应用组件图中。

@Module(subcomponents = [LoginSubComponent::class])
interface LoginModule {
    @Binds
    @IntoMap
    @ClassKey(LoginActivity::class)
    fun bindLoginAndroidInjectorFactory(factory: LoginSubComponent.Factory?): AndroidInjector.Factory<*>?
}

@Component(modules = [AndroidInjectionModule::class, LoginModule::class])
interface AppComponent {
    fun inject(application: MainApplication)
}
复制代码

注意:这里每次增加 Activity 或 Fragment 等时都需要创建对应的 XXXSubComponent 和 XXXModule 类,可以通过使用 @ContributesAndroidInjector 注解来避免这种重复性的操作

于是可以将第二、三步改为:

@Module
interface ActivityModule {
    // @ActivityScope 可以增加生命周期限定,这样生成的 Subcomponent 也将被其注解
    @ContributesAndroidInjector(modules = [/* 需要引用的module */])
    fun injectLoginActivity(): LoginActivity
}

@Component(modules = [AndroidInjectionModule::class, ActivityModule::class])
interface AppComponent {
    fun inject(application: MainApplication)
}
复制代码

这样每次增加需要注入的 Activity 时只需要在 ActivityModule 里面增加对应的方法即可,其实就是 Dagger 通过这个注解为我们在编译期间生成了上面的重复模版类代码

看看 Dagger 为我们生成的代码:

@Module
interface ActivityModule {
    @ActivityScope
    @ContributesAndroidInjector(modules = [TmpModule::class])
    fun injectLoginActivity(): LoginActivity
}
复制代码

生成的代码:

@Module(subcomponents = ActivityModule_InjectLoginActivity.LoginActivitySubcomponent.class)
public abstract class ActivityModule_InjectLoginActivity {
  private ActivityModule_InjectLoginActivity() {}

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

  @Subcomponent(modules = TmpModule.class)
  @ActivityScope
  public interface LoginActivitySubcomponent extends AndroidInjector<LoginActivity> {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory<LoginActivity> {}
  }
}
复制代码

4、让 Application 实现 HasAndroidInjector 接口,并注入一个 DispatchingAndroidInjector 实例:

class MainApplication : Application(), HasAndroidInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>

    override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector

    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.create().inject(this)
    }
}
复制代码

5、最后在 Activity 中调用 AndroidInjection.inject(this) 即可:

class LoginInfo @Inject constructor() {
    override fun toString(): String {
        return "LoginInfo"
    }
}

class LoginActivity : AppCompatActivity() {
    @Inject
    lateinit var loginInfo: LoginInfo

    override fun onCreate(savedInstanceState: Bundle?) {
        // 可以放到 BaseActivity 中调用
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
        println("loginInfo: $loginInfo")
    }
}
复制代码

注入Fragment

跟注入 Activity 类似,上面的 Key 换成了 Fragment, 不一样的地方在于 AndroidInjection.inject(this) 需要放到 onAttach 中执行。

另外,可以选择将我们的 Fragment Component 作为另一个 Fragment/Activity Component 的子组件,也可以跟上面 Activity 一样作为应用组件的子组件,这取决于我们的业务需要。

下面直接使用官方文档的例子说明当 Fragment 绑定到 YourActivitySubcomponent 的用法:

public class YourActivity extends Activity implements HasAndroidInjector {
  @Inject DispatchingAndroidInjector<Object> androidInjector;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
    // ...
  }

  @Override
  public AndroidInjector<Object> androidInjector() {
    return androidInjector;
  }
}

public class YourFragment extends Fragment {
  @Inject SomeDependency someDep;

  @Override
  public void onAttach(Activity activity) {
    AndroidInjection.inject(this);
    super.onAttach(activity);
    // ...
  }
}

@Subcomponent(modules = ...)
public interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> {
  @Subcomponent.Factory
  public interface Factory extends AndroidInjector.Factory<YourFragment> {}
}

@Module(subcomponents = YourFragmentSubcomponent.class)
abstract class YourFragmentModule {
  @Binds
  @IntoMap
  @ClassKey(YourFragment.class)
  abstract AndroidInjector.Factory<?>
      bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Factory factory);
}

@Subcomponent(modules = { YourFragmentModule.class, ... }
public interface YourActivityOrYourApplicationComponent { ... }
复制代码

工作原理

  1. AndroidInjection.inject() 会从 Application 中获取到 DispatchingAndroidInjector 对象并调用其 DispatchingAndroidInjector.inject(activity) 方法。比较简单,不贴代码了。

  2. DispatchingAndroidInjector.inject(activity) 方法会根据 activity 来寻找对应的 AndroidInjector.Factory(即 LoginSubComponent.Factory)。还记得刚才的 bindLoginAndroidInjectorFactory 方法不?它通过 @IntoMap 注解将 LoginActivity 作为 key, LoginSubComponent.Factory 作为 value, Dagger 会将这条记录存入 map 中,DispatchingAndroidInjector.inject(activity) 便是在这个 map 中找到 LoginSubComponent.Factory 的。贴出关键代码:

    public final class DispatchingAndroidInjector<T> implements AndroidInjector<T> {
    
        @Override
        public void inject(T instance) {
            boolean wasInjected = maybeInject(instance);
            // ...
        }
    
        public boolean maybeInject(T instance) {
            // 从 Map 中找到 Factory 类
            Provider<AndroidInjector.Factory<?>> factoryProvider = injectorFactories.get(instance.getClass().getName());
            AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get();
            // 创建 AndroidInjector 实例,之前的 LoginSubComponent 便是其子接口
            AndroidInjector<T> injector = factory.create(instance);
            // 执行 LoginSubComponent 实现类的 inject 方法
            injector.inject(instance);
            return true;
        }
    }
    复制代码
  3. 接着通过上面找到的 Factory 来创建 AndroidInjector(即 LoginSubComponent) 实例并调用其 inject 方法,该方法内部会对 LoginActivity 执行注入。

    private final class LoginSubComponentImpl implements LoginSubComponent {
        @Override
        public void inject(LoginActivity arg0) {
            injectLoginActivity(arg0);
        }
    
        private LoginActivity injectLoginActivity(LoginActivity instance) {
            // injectLoginInfo 方法会为 instance.loginInfo 赋值
            LoginActivity_MembersInjector.injectLoginInfo(instance, new LoginInfo());
            return instance;
        }
    }
    复制代码

基础类型

Dagger 提供了一些基础类型诸如 DaggerApplication, DaggerActivity, DaggerFragment, DaggerService, DaggerBroadcastReceiver, DaggerContentProvider 等,可以直接继承它们来实现上面的那些行为,具体使用直接看它们的文档或者源码即可,比较简单。

总结

这篇文章介绍了几个常用的 Dagger 注解,以及 AndroidInjector 的用法,这可以减少写一些重复性代码,在实际开发中比较有帮助。Dagger 的用法看起来比较复杂,主要是太多注解了,其实它的原理还是比较简单的,通过注解处理器生成一系列的注入代码,主要还是多用用吧,习惯就好~

文中内容如有错误欢迎指出,共同进步!觉得不错的彦祖杰伦亦菲热巴等留个再走哈~

博文链接

Dagger 的更多用法参考官方文档: dagger.dev/dev-guide/a…

文章分类
Android