概述
入门笔记里讲了 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 { ... }
复制代码
工作原理
-
AndroidInjection.inject()
会从 Application 中获取到 DispatchingAndroidInjector 对象并调用其DispatchingAndroidInjector.inject(activity)
方法。比较简单,不贴代码了。 -
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; } } 复制代码
-
接着通过上面找到的 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…