0%

Android-Dagger2之进阶用法

概述

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

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

@Binds

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

1
2
3
4
5
@Module
interface TmpModule {
@Binds
fun provideOperate(impl: OperateImpl): IOperate
}

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

1
2
3
4
5
6
7
@Module
class TmpModule {
@Provides
fun provideOperate(): IOperate {
return OperateImpl()
}
}

@IntoSet

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Module
class TmpModule {
@Provides
@IntoSet
fun provide1(): String {
return "A"
}

@Provides
@IntoSet
fun provide2(): String {
return "B"
}
}

接着可以使用:

1
2
3
4
5
6
7
8
9
10
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 提供依赖项。

1
2
3
4
5
6
7
8
9
10
11
12
@Module
class TmpModule {
@Provides
@IntoMap
@IntKey(1)
fun provide1(): String = "A"

@Provides
@IntoMap
@IntKey(2)
fun provide2(): String = "B"
}

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

1
2
3
@Target(AnnotationTarget.FUNCTION)
@MapKey
annotation class GameKey(val value: KClass<out Game>)

AndroidInjector

注入Activity

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

1
2
3
4
5
6
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 子组件。

1
2
3
4
5
@Subcomponent
interface LoginSubComponent : AndroidInjector<LoginActivity> {
@Subcomponent.Factory
interface Factory : AndroidInjector.Factory<LoginActivity?>
}

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

1
2
3
4
5
6
7
8
9
10
11
12
@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 注解来避免这种重复性的操作

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

1
2
3
4
5
6
7
8
9
10
11
@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 为我们生成的代码:

1
2
3
4
5
6
@Module
interface ActivityModule {
@ActivityScope
@ContributesAndroidInjector(modules = [TmpModule::class])
fun injectLoginActivity(): LoginActivity
}

生成的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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 实例:

1
2
3
4
5
6
7
8
9
10
11
12
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) 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 的。贴出关键代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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 执行注入。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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 的更多用法参考官方文档: https://dagger.dev/dev-guide/android