Android Hilt  类注入工具的使用

Android Hilt 类注入工具的使用

作为 Jetpack 的一环,当然要尝试使用了~~~

注意:AS 版本要 4.0 以上,且必须开启 Java 8

使用 hilt 可以为类自动初始化,避免再编写 new XXXX 代码

Hilt 准备工作

根目录的 build.gradle 添加:

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}

app 工程的 build.gradle 添加:

...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    ...
}

dependencies {
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

添加 Java 8

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

当这一切做完后,出现了一个错误:

Gradle DSL method not found: 'kapt()'

还要再添加一个apply plugin,就像下面这样:

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

使用 Hilt

使用 @HiltAndroidApp 注解标注 Application

@HiltAndroidApp
public class MApplication extends Application {
}

不要忘了在 清单文件中配置 Application ,然后要注解 Activity

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
    public static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

必须使用这两个注解之后才能运行

只有被标注的 Activity 才能使用注入功能
而且如果某个类要使用这个注解,则它的传入参数也必须使用了注解

定义两个类,注意这两个类的构造函数要有 @Inject 注解:

public class Me implements People {

    Father father;

    private String name;
    private int age;

    @Inject
    public Me() {
        Log.i(TAG, "me init");
    }
}

public class Father implements People {

    private String name;
    private int age;

    @Inject
    public Father() {
        Log.i(TAG, "father init");
    }

}

在 Activity中声明对象 Me 要有 @Inject 注解:

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
	xxxxxxx

    @Inject
    Me me;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
	xxxxxxx
    }
}

此时运行程序,即可在 Logcat 看到日志输出:

2020-09-17 16:19:37.587 8020-8020/com.zhou.myapplication I/MainActivity: me init

实际上,它是在运行过程中生成了一个代理类,然后再注入的,所以,Me 的构造函数,以及 Activity 中的对象 Me 可以看到都不是 private 修饰的,如果 private 修饰,则无法取到对象,从而运行失败

将其修改为 private 报错:

// Activity 修改对象访问权限
    @Inject
    private Me me;

// 报错
Dagger does not support injection into private fields
    private Me me;
               ^

注解抽象类

上面只是注解普通的类,但是在实际使用过程中,可能在 Activity 中声明的是抽象类,至于对象具体是谁,则不关心,只要使用抽象类中的属性即可

在之前定义的 MePeople 中,他们都是实现的 People 接口

那么如果 Activity 中要接收的是一个 People 接口,Hilt 如果去注入呢

Hilt 支持使用 @Binds 注解绑定方法,这个方法的返回值要提供一个对象,绑定到 Activity 生命的对象上。尝试在 MainActivity 中定义一个方法:

    @Inject
    People me;

    @Binds
    People getPeople() {
        Log.i(TAG, "get People Method");
        return new Me();
    }

然后在 MainActivity 中新增注解 @Module
@InstallIn(ActivityComponent.class) ,运行时报错:

@Binds methods must be abstract
    People getPeople() {}

那么 @Module@InstallIn 是什么,它表明被注解的类是一个模块,且要被注入 Activity 。如果被注入的是 Service 或者 Fragment 有对应的 .class。具体对应关系在最后

根据提示要求这个方法是一个抽象方法。那么把这个方法定义在一个抽象类里面吧:

@Module
@InstallIn(ActivityComponent.class)
public abstract class PeopleModule {

    @Binds
    abstract People getPeople(Me me);
}

再次运行就可以在控制台看到熟悉的输出了:

2020-09-18 11:51:44.920 5622-5622/com.zhou.myapplication I/MainActivity: me init

这种方式只能提供单一的类型,也就是 People 只能是 Me 或者是 Father,下列代码获取的两个对象都是 Me ,不要被命名迷惑了:

    @Inject
    People me;

    @Inject
    People father;

如果增加 Father 会怎么样呢,修改 PeopleModule ,在里面增加一个抽象方法

    @Binds
    abstract People getPeople(Me me);
    @Binds
    abstract People getPeople(Father me);

这时候运行会直接报错:

Cannot have more than one binding method with the same name in a single module
    abstract People getPeople(Me me);

为同一对象提供多个绑定

上面一篇提到,同一个 People 只能用一个注解 @Binds 绑定一个 getPeople(Me me) 方法,当在同一个 Activity 里面,要有不同的对象实例怎么办?

这时候要用到注解 @Qualifier@Retention(RetentionPolicy.RUNTIME)

新建一个类,在里面增加两个 interface

@Module
@InstallIn(ActivityComponent.class)
public class PeopleFactory {

    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ReturnMe {
    }

    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ReturnFather {
    }

}

首先定义了两个限定符,接下来要使用 @Provider 定义提供这两个类型的方法

    @Provides
    @ReturnMe
    public static People providerMe() {
        return new Me();
    }

    @Provides
    @ReturnFather
    public static People providerFather() {
        return new Father();
    }

定义这两个方法,使用 @Providers 表明这是一个类型提供者。然后使用限定符标志这是哪个对象的提供者

如何使用?

再回到 MainActivity

    @PeopleFactory.ReturnMe
    @Inject
    People me;

    @PeopleFactory.ReturnFather
    @Inject
    People father;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

使用 @PeopleFactory.ReturnMeReturnFather 就可以了。声明他们都是同一个接口类型 People 。但可以同时获得 MeFather 两个对象。日志如下:

2020-09-21 15:21:25.266 8902-8902/com.zhou.myapplication I/MainActivity: me init
2020-09-21 15:21:25.266 8902-8902/com.zhou.myapplication I/MainActivity: father init

设置组件作用域

通过设置组件作用域可实现类似单例模式的功能,在 Application 中所有 @Inject 声明的对象都是同一个对象实例

    @PeopleFactory.ReturnMe
    @Inject
    People me1;
    @PeopleFactory.ReturnMe
    @Inject
    People me2;

通过这种方式,声明了两个 Me 对象,这个对象会创建两个,分别赋予 me1me2

使用 @ActivityScoped 注解,可以确保在一个 Activity 作用域中只创建一次

    @ActivityScoped
    @Provides
    @ReturnMe
    public static People providerMe() {
        return new Me();
    }

现在看看日志输出:

        if (me1 == me2) {
            Log.i(TAG, "me1 == me2: true");
        }

// Logcat 输出:

2020-09-21 16:48:46.438 3116-3116/com.zhou.myapplication I/MainActivity: me1 == me2: true

现在他们在 Activity 中是单例的。引用相同

如果想在整个 Application 中只创建一次,需要使用 @Singleton 且 class 使用
@InstallIn(ApplicationComponent .class) 注解,表明这个类注入到 Application 中

InstallIn 注入的 .class 对象表

Hilt 组件注入器面向的对象
ApplicationComponentApplication
ActivityRetainedComponentViewModel
ActivityComponentActivity
FragmentComponentFragment
ViewComponentView
ViewWithFragmentComponent带有 @WithFragmentBindings 注释的 View
ServiceComponentService

设置作用域的注解表

Android 类生成的组件作用域
ApplicationApplicationComponent@Singleton
View ModelActivityRetainedComponent@ActivityRetainedScope
ActivityActivityComponent@ActivityScoped
FragmentFragmentComponent@FragmentScoped
ViewViewComponent@ViewScoped
带有 @WithFragmentBindings 注释的 ViewViewWithFragmentComponent@ViewScoped
ServiceServiceComponent@ServiceScoped

后记

官方使用文档

使用 Hilt 通过注入,可以接口数据对象。使其不需要编写样式化代码,数据在使用时,不关注 new ,而是使用即可。具体的数据提供通过 Module 提供。Hilt 在大型项目中使用可以解耦,带来便利性。

通过编写Demo程序,学习了 Hilt 的使用并了解其注解方式。就当扩充自己的知识库~~~
学无止境

over

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://zwc365.com/2020/10/07/hilt类注入工具的使用

Buy me a cup of coffee ☕.