前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android--Hilt入门

Android--Hilt入门

作者头像
aruba
发布2021-12-16 10:11:52
1.4K0
发布2021-12-16 10:11:52
举报
文章被收录于专栏:android技术android技术
谷歌接管Dagger后,推出了自己的Hilt框架,Hilt基于Dagger做了一层封装,大大简化了Dagger的使用,定制了一系列规范,并支持Jetpack中部分组件,是一个专门为安卓开发的DI框架
一、构造函数注入

和Dagger相同,Hilt也分两种注入方式,以上篇Dagger中的代码为例子,来对比两个框架的使用区别

1.gradle中配置依赖

工程gradle中导入插件:

代码语言:javascript
复制
    dependencies {
        ...
        classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.4"
    }

moudle中进行依赖:

代码语言:javascript
复制
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

dependencies {
    ...
    def hilt_version = "2.40.4"
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}
2.使用@Inject注解定义需要注入的类
代码语言:javascript
复制
/**
 * 模拟本地数据源
 */
class LocalDataSource @Inject constructor()
代码语言:javascript
复制
/**
 * 模拟远程数据源
 */
class RemoteDataSource @Inject constructor()

定义包装类DataSource,包含上面两个类,同样对构造函数使用@Inject注解

代码语言:javascript
复制
/**
 * 数据源包装类
 * Created by aruba on 2021/12/4.
 */
data class DataSource @Inject constructor(
    var remoteDataSource: RemoteDataSource,
    var localDataSource: LocalDataSource
) 
3.使用@HiltAndroidApp注解Application,表示注入中间件

Dagger使用的是@Component注解表示一个组件,上篇文章也提到过,一个项目对应一个Component就足够了,Hilt规范了Component

代码语言:javascript
复制
@HiltAndroidApp
class App : Application()
4.在Activity中使用@Inject注解对象
代码语言:javascript
复制
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var dataSource: DataSource

...
5.对Activity使用@AndroidEntryPoint注解

在Dagger中需要调用Component的注入方法,Hilt中直接使用注解就可以实现注入

代码语言:javascript
复制
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var dataSource: DataSource

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.i("aruba_log", dataSource.toString())
    }
}

日志结果: I/aruba_log: DataSource(remoteDataSource=com.aruba.hiltapplication.di.datasource.RemoteDataSource@f7f11fd, localDataSource=com.aruba.hiltapplication.di.datasource.LocalDataSource@743eef2)

和Dagger相比,我们多导入了一个插件,此插件是利用Javassist,将编译后将@AndroidEntryPoint注解的Activity继承至自己生成的类

下面是该例子生成的Hilt_MainActivity:

代码语言:javascript
复制
public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder {
  private volatile ActivityComponentManager componentManager;

  private final Object componentManagerLock = new Object();

  private boolean injected = false;

  Hilt_MainActivity() {
    super();
    _initHiltInternal();
  }

  Hilt_MainActivity(int contentLayoutId) {
    super(contentLayoutId);
    _initHiltInternal();
  }

  // 开始注入
  private void _initHiltInternal() {
    addOnContextAvailableListener(new OnContextAvailableListener() {
      @Override
      public void onContextAvailable(Context context) {
        // 注入
        inject();
      }
    });
  }

  @Override
  public final Object generatedComponent() {
    return this.componentManager().generatedComponent();
  }

  protected ActivityComponentManager createComponentManager() {
    return new ActivityComponentManager(this);
  }

  @Override
  public final ActivityComponentManager componentManager() {
    if (componentManager == null) {
      synchronized (componentManagerLock) {
        if (componentManager == null) {
          componentManager = createComponentManager();
        }
      }
    }
    return componentManager;
  }

  protected void inject() {
    if (!injected) {
      injected = true;
      // 调用component的注入方法
      ((MainActivity_GeneratedInjector) this.generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this));
    }
  }

  @Override
  public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    return DefaultViewModelFactories.getActivityFactory(this, super.getDefaultViewModelProviderFactory());
  }
}
二、模块注入
1.依赖网络框架
代码语言:javascript
复制
dependencies {
    ...

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
}

别忘了在Manifest.xml中添加权限

2.定义Retrofit API
代码语言:javascript
复制
interface BaiduApiService {
    @GET("/index.html")
    fun index(): Call<String>
}
3.定义模块
  • 和Dagger相同,使用@Moudle注解就可以表示一个模块,使用@Provides注解提供给Component生成注入对象的方法
  • 使用@InstallIn注解,指定该模块需要装载到哪些Component中,并且我们不必再定义组件了,Hilt预定义了我们移动开发中所需的组件和子组件

这边指定其装载到SingletonComponent中,也就是全局APP中,旧版本的ApplicationComponent已废弃

代码语言:javascript
复制
@InstallIn(SingletonComponent::class)
@Module
class NetworkModule {

    @Provides
    fun getBaiduApiService(): BaiduApiService {
        return Retrofit.Builder()
            .baseUrl("https://www.baidu.com")
            .addConverterFactory(ScalarsConverterFactory.create())
            .build().create(BaiduApiService::class.java)
    }
}
4.在Activity中使用
代码语言:javascript
复制
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var dataSource: DataSource
    @Inject
    lateinit var baiduApiService: BaiduApiService
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.i("aruba_log", dataSource.toString())

        getIndex()
    }

    /**
     * 获取百度首页
     */
    private fun getIndex() {
        baiduApiService.index().enqueue(object : Callback<String> {
            override fun onResponse(call: Call<String>, response: Response<String>) {
                findViewById<TextView>(R.id.tv_hello).text = response.body()
            }

            override fun onFailure(call: Call<String>, t: Throwable) {
            }
        })
    }
}

效果:

三、预定义组件与作用域
1.预定义的Component

Hilt定义的组件为SingletonComponent,子组件在dagger.hilt.android.components包下

这些组件对应的生命周期为:

组件

创建时机

销毁时机

SingletonComponent

Application#onCreate()

Application#onDestroy()

ActivityRetainedComponent

Activity#onCreate()

Activity#onDestroy()

ServiceComponent

Service#onCreate()

Service#onDestroy()

ActivityComponent

Activity#onCreate()

Activity#onDestroy()

ViewModelComponent

ViewModel#super()

ViewModel#clear()

FragmentComponent

Fragment#onAttach()

Fragment#onDestroy()

ViewComponent

View#super()

视图销毁时

ViewWithFragmentComponent

View#super()

视图销毁时

2.预定义的Scope

Hilt定义的子组件作用域在dagger.hilt.android.scopes包下

这些作用域都是和子组件一一对应的,组件的层级关系如下图:

组件-作用域层级关系

3.模块中使用作用域

Hilt的作用域就简单很多了,因为它预定义了组件和子组件 ,同时又定义了这些组件对应的作用域,上面的例子中,如何保证只实例化一份?使用SingletonComponent对应的作用域@Singleton即可,使用方法也是和Dagger相同的

代码语言:javascript
复制
@InstallIn(SingletonComponent::class) //表示全局组件
@Module
class NetworkModule {

    @Singleton
    @Provides
    fun getBaiduApiService(): BaiduApiService {
        return Retrofit.Builder()
            .baseUrl("https://www.baidu.com")
            .addConverterFactory(ScalarsConverterFactory.create())
            .build().create(BaiduApiService::class.java)
    }
}

在Activity中,注入两个对象,并打印下两者的hashcode:

代码语言:javascript
复制
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var baiduApiService1: BaiduApiService
    @Inject
    lateinit var baiduApiService2: BaiduApiService
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.i("aruba_log","baiduApiService1 hashcode:${baiduApiService2.hashCode()}")
        Log.i("aruba_log","baiduApiService2 hashcode:${baiduApiService2.hashCode()}")
    }
}

日志结果: I/aruba_log: baiduApiService1 hashcode:174572891 I/aruba_log: baiduApiService2 hashcode:174572891

4.构造方法使用作用域

ViewModelComponent是新出的子组件,对应的作用域为ViewModelScope,作用为:一个ViewModel中多个同类型注入对象,则使用同一份实例。以前实现ViewModel中注入还需要依赖其他框架,这次来使用ViewModelScope作为例子

4.1 定义注入类,并使用@ViewModelScope注解

注意:如果把参数放入主构造,并且赋了默认值,那么会生成两个构造方法,Hilt就会报错

代码语言:javascript
复制
@ViewModelScoped
class UserInfo @Inject constructor() {
    var name: String = "张三"
}
4.2 定义ViewModel,并为其添加@HiltViewModel注解

在构造中注入对象

代码语言:javascript
复制
@HiltViewModel
class MainViewModel @Inject constructor(var userInfo: UserInfo) : ViewModel()
4.3 Activity中使用
代码语言:javascript
复制
@AndroidEntryPoint
class ViewModelActivity : AppCompatActivity() {
    val viewModel: MainViewModel by lazy {
        ViewModelProvider(this).get(MainViewModel::class.java)
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)

        Log.i("aruba_log", viewModel.userInfo.name)
    }
}

日志结果: I/aruba_log: 张三

5.@Qualifier注解解决注入冲突
  • 对于构造函数注入,只能有一个构造函数被@Inject注解,否则编译时报错
  • 对于模块注入,如果多个@Provides注解的方法返回相同类型,使用@Qualifier注解可以解决冲突,@Qualifier注解相当于为其取了个别名,在使用对象注入时也相应的使用@Qualifier注解,即可得到对应的注入对象
5.1 @Named解决注入冲突

@Named注解源码中,使用了@Qualifier元注解:

代码语言:javascript
复制
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

模块代码如下:

代码语言:javascript
复制
@InstallIn(ViewModelComponent::class)
@Module
class UserInfo2Fetcher {

    @Named("zhao")
    @ViewModelScoped
    @Provides
    fun provideUserInfo(): UserInfo2 {
        return UserInfo2("赵四")
    }

    @Named("wang")
    @ViewModelScoped
    @Provides
    fun provideUserInfo2(): UserInfo2 {
        return UserInfo2("王五")
    }
}

UserInfo2:

代码语言:javascript
复制
class UserInfo2(val name: String)

ViewModel中,指定注入使用@Named("wang")

代码语言:javascript
复制
@HiltViewModel
class UserViewModel @Inject
constructor(
    var userInfo: UserInfo,
    @Named("wang") var userInfo2: UserInfo2
) : ViewModel()

最后在Activity中打印结果

代码语言:javascript
复制
Log.i("aruba_log", viewModel.userInfo2.name)

结果: I/aruba_log: 王五

5.2 自定义注解解决冲突

我们也可以使用@Qualifier定义一个注解

代码语言:javascript
复制
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Zhao()

使用自定义的注解,解决冲突

代码语言:javascript
复制
@InstallIn(ViewModelComponent::class)
@Module
class UserInfo2Fetcher {

    @Zhao
    @ViewModelScoped
    @Provides
    fun provideUserInfo(): UserInfo2 {
        return UserInfo2("赵四")
    }

    @Named("wang")
    @ViewModelScoped
    @Provides
    fun provideUserInfo2(): UserInfo2 {
        return UserInfo2("王五")
    }
}

ViewModel中替换成自定义的注解后运行

代码语言:javascript
复制
@HiltViewModel
class UserViewModel @Inject
constructor(
    var userInfo: UserInfo,
    @Zhao var userInfo2: UserInfo2
) : ViewModel()

结果: I/aruba_log: 赵四

四、接口注入

当我们有一个接口,并且有它的实现类,那么Hilt也可以注入生成该接口。这也是Dagger的功能

1.定义接口
代码语言:javascript
复制
interface ICallback {
    fun onSuccess()
    fun onFailure()
}
2.实现类,并使用@Inject注解
代码语言:javascript
复制
class CallbackImpl @Inject constructor() : ICallback {
    override fun onSuccess() {
        Log.i("aruba_log", "onSuccess")
    }

    override fun onFailure() {
        Log.i("aruba_log", "onFailure")
    }
}
3.定义模块抽象类,提供抽象方法返回ICallback接口

该方法需要入参为实现类,并使用@Binds注解

代码语言:javascript
复制
@InstallIn(ActivityComponent::class)
@Module
abstract class CallbackFetcher {

    @Binds
    abstract fun provideCallback(callback: CallbackImpl): ICallback
}
4.Activity中注入,并调用方法:
代码语言:javascript
复制
@AndroidEntryPoint
class ViewModelActivity : AppCompatActivity() {
    @Inject
    lateinit var callback: ICallback

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)

        callback.onSuccess()
    }
}

日志结果: I/aruba_log: onSuccess

五、默认绑定

Hilt定义的组件都绑定了安卓上下文相关对象,如:在ActivityComponent中注入的类,直接可以通过注入获取Activity对象 以上面接口实现类为例子

1.构造方法中使用@ActivityContext注解注入Context

除了@ActivityContext,还可以通过@ApplicationContext注入全局上下文 这边在回调时,弹一个toast

代码语言:javascript
复制
class CallbackImpl @Inject constructor(@ActivityContext var context: Context) : ICallback {
    override fun onSuccess() {
        Log.i("aruba_log", "onSuccess")
        Toast.makeText(context, "onSuccess", Toast.LENGTH_SHORT).show()
    }

    override fun onFailure() {
        Log.i("aruba_log", "onFailure")
        Toast.makeText(context, "onFailure", Toast.LENGTH_SHORT).show()
    }
}

运行效果:

2.注入Activity

我们也可以直接定义一个Activity的成员变量,并通过构造函数注入

代码语言:javascript
复制
class CallbackImpl @Inject constructor(var activity: Activity) : ICallback {
    override fun onSuccess() {
        Log.i("aruba_log", "onSuccess")
        Toast.makeText(activity, "onSuccess", Toast.LENGTH_SHORT).show()
    }

    override fun onFailure() {
        Log.i("aruba_log", "onFailure")
        Toast.makeText(activity, "onFailure", Toast.LENGTH_SHORT).show()
    }
}
3.组件-绑定对应关系

组件下注入类可以获取对应的绑定对象

组件

创建时机

SingletonComponent

Application

ActivityRetainedComponent

Application

ServiceComponent

Application、Service

ActivityComponent

Application、Activity

ViewModelComponent

Application

FragmentComponent

Application、Activity、Fragment

ViewComponent

Application、Activity、View

ViewWithFragmentComponent

Application、Activity、Fragment、View

总结

DI框架虽然带来了极大的便利,但无论是Dagger还是Hilt,在使用过程中,有必要使用流程图、UML图等来设计记录组件和作用域的绑定关系,以便后续使用与维护

Demo地址:https://gitee.com/aruba/hilt-application.git
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/12/7 上,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 谷歌接管Dagger后,推出了自己的Hilt框架,Hilt基于Dagger做了一层封装,大大简化了Dagger的使用,定制了一系列规范,并支持Jetpack中部分组件,是一个专门为安卓开发的DI框架
  • 一、构造函数注入
    • 1.gradle中配置依赖
      • 2.使用@Inject注解定义需要注入的类
        • 3.使用@HiltAndroidApp注解Application,表示注入中间件
          • 4.在Activity中使用@Inject注解对象
            • 5.对Activity使用@AndroidEntryPoint注解
            • 二、模块注入
              • 1.依赖网络框架
                • 2.定义Retrofit API
                  • 3.定义模块
                    • 4.在Activity中使用
                    • 三、预定义组件与作用域
                      • 1.预定义的Component
                        • 2.预定义的Scope
                          • 3.模块中使用作用域
                            • 4.构造方法使用作用域
                              • 4.1 定义注入类,并使用@ViewModelScope注解
                              • 4.2 定义ViewModel,并为其添加@HiltViewModel注解
                              • 4.3 Activity中使用
                            • 5.@Qualifier注解解决注入冲突
                              • 5.1 @Named解决注入冲突
                              • 5.2 自定义注解解决冲突
                          • 四、接口注入
                            • 1.定义接口
                              • 2.实现类,并使用@Inject注解
                                • 3.定义模块抽象类,提供抽象方法返回ICallback接口
                                  • 4.Activity中注入,并调用方法:
                                  • 五、默认绑定
                                    • 1.构造方法中使用@ActivityContext注解注入Context
                                      • 2.注入Activity
                                        • 3.组件-绑定对应关系
                                        • 总结
                                          • Demo地址:https://gitee.com/aruba/hilt-application.git
                                          相关产品与服务
                                          消息队列 TDMQ
                                          消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
                                          领券
                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档