和Dagger相同,Hilt也分两种注入方式,以上篇Dagger中的代码为例子,来对比两个框架的使用区别
工程gradle中导入插件:
dependencies {
...
classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.4"
}
moudle中进行依赖:
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"
}
/**
* 模拟本地数据源
*/
class LocalDataSource @Inject constructor()
/**
* 模拟远程数据源
*/
class RemoteDataSource @Inject constructor()
定义包装类DataSource,包含上面两个类,同样对构造函数使用@Inject注解
/**
* 数据源包装类
* Created by aruba on 2021/12/4.
*/
data class DataSource @Inject constructor(
var remoteDataSource: RemoteDataSource,
var localDataSource: LocalDataSource
)
Dagger使用的是@Component注解表示一个组件,上篇文章也提到过,一个项目对应一个Component就足够了,Hilt规范了Component
@HiltAndroidApp
class App : Application()
class MainActivity : AppCompatActivity() {
@Inject
lateinit var dataSource: DataSource
...
在Dagger中需要调用Component的注入方法,Hilt中直接使用注解就可以实现注入
@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:
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());
}
}
dependencies {
...
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
}
别忘了在Manifest.xml中添加权限
interface BaiduApiService {
@GET("/index.html")
fun index(): Call<String>
}
这边指定其装载到SingletonComponent中,也就是全局APP中,旧版本的ApplicationComponent已废弃
@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)
}
}
@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) {
}
})
}
}
效果:
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() | 视图销毁时 |
Hilt定义的子组件作用域在dagger.hilt.android.scopes包下
这些作用域都是和子组件一一对应的,组件的层级关系如下图:
组件-作用域层级关系
Hilt的作用域就简单很多了,因为它预定义了组件和子组件 ,同时又定义了这些组件对应的作用域,上面的例子中,如何保证只实例化一份?使用SingletonComponent对应的作用域@Singleton即可,使用方法也是和Dagger相同的
@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:
@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
ViewModelComponent是新出的子组件,对应的作用域为ViewModelScope,作用为:一个ViewModel中多个同类型注入对象,则使用同一份实例。以前实现ViewModel中注入还需要依赖其他框架,这次来使用ViewModelScope作为例子
注意:如果把参数放入主构造,并且赋了默认值,那么会生成两个构造方法,Hilt就会报错
@ViewModelScoped
class UserInfo @Inject constructor() {
var name: String = "张三"
}
在构造中注入对象
@HiltViewModel
class MainViewModel @Inject constructor(var userInfo: UserInfo) : ViewModel()
@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: 张三
@Named注解源码中,使用了@Qualifier元注解:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
模块代码如下:
@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:
class UserInfo2(val name: String)
ViewModel中,指定注入使用@Named("wang")
@HiltViewModel
class UserViewModel @Inject
constructor(
var userInfo: UserInfo,
@Named("wang") var userInfo2: UserInfo2
) : ViewModel()
最后在Activity中打印结果
Log.i("aruba_log", viewModel.userInfo2.name)
结果: I/aruba_log: 王五
我们也可以使用@Qualifier定义一个注解
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Zhao()
使用自定义的注解,解决冲突
@InstallIn(ViewModelComponent::class)
@Module
class UserInfo2Fetcher {
@Zhao
@ViewModelScoped
@Provides
fun provideUserInfo(): UserInfo2 {
return UserInfo2("赵四")
}
@Named("wang")
@ViewModelScoped
@Provides
fun provideUserInfo2(): UserInfo2 {
return UserInfo2("王五")
}
}
ViewModel中替换成自定义的注解后运行
@HiltViewModel
class UserViewModel @Inject
constructor(
var userInfo: UserInfo,
@Zhao var userInfo2: UserInfo2
) : ViewModel()
结果: I/aruba_log: 赵四
当我们有一个接口,并且有它的实现类,那么Hilt也可以注入生成该接口。这也是Dagger的功能
interface ICallback {
fun onSuccess()
fun onFailure()
}
class CallbackImpl @Inject constructor() : ICallback {
override fun onSuccess() {
Log.i("aruba_log", "onSuccess")
}
override fun onFailure() {
Log.i("aruba_log", "onFailure")
}
}
该方法需要入参为实现类,并使用@Binds注解
@InstallIn(ActivityComponent::class)
@Module
abstract class CallbackFetcher {
@Binds
abstract fun provideCallback(callback: CallbackImpl): ICallback
}
@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对象 以上面接口实现类为例子
除了@ActivityContext,还可以通过@ApplicationContext注入全局上下文 这边在回调时,弹一个toast
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()
}
}
运行效果:
我们也可以直接定义一个Activity的成员变量,并通过构造函数注入
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()
}
}
组件下注入类可以获取对应的绑定对象
组件 | 创建时机 |
---|---|
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图等来设计记录组件和作用域的绑定关系,以便后续使用与维护