背景
为什么要使用依赖注入
?
现在大多数应用已经通过 依赖注入
的方式来将依赖的类对象传递进来,从而提高代码扩展性,开发过程中可以灵活的替换依赖的类。
依赖注入
是什么?
在外部创建依赖的类对象,将其通过接口参数传递给类(需求方)使用。
Google提供的依赖注入框架 Hilt
在我们平时的项目中,类对象的创建和依赖注入基本涉及几十、上百个类,非常复杂。这些动作自己完成容易陷入吃力不讨好(费力易错)的境地。所以 Google 提供了依赖注入框架「Hilt」。通过 「hilt」支持的方式配置需要创建的类对象、类与类之间的依赖关系,「Hilt」就可以自动创建对象、管理对象的生命周期、依赖注入。
怎么快速上手
回顾当初学习依赖注入的过程, 我带着以下的问题慢慢的熟悉了 Hilt
- 怎么告诉「Hilt」当前变量需要被提供实例
- 怎么告诉「Hilt」该去哪里拿实例
- 怎么告诉「Hilt」产生的实例该不该以单例的形式提供
从使用的角度上讲,「Hilt」通过解析注解
识别并解决上面的问题。所以,使用「Hilt」其实很大程度上是学习其提供的各种注解。
那么「Hilt」提供了哪些注解, 我们又该如何根据自己的需求来选择。
使用步骤
@HiltAndroidApp
: 初始化:自定义 Application 为其添加
```
@HiltAndroidApp
class MainApplication : Application()
```
@HiltAndroidAp、@HiltViewModel、AndroidEntryPoint
: Android Class
需要被注入实例
- Application ---> @HiltAndroidApp
- ViewModel ---> @HiltViewModel
- Activity ---> @AndroidEntryPoint
- Fragment ---> @AndroidEntryPoint
以 Activity 为例,建立依赖.
@AndroidEntryPoint class MainActivity : AppCompatActivity() { @Inject lateinit var analytics: AnalyticsAdapter }
注意
- 如果非
Android Class
,声明 @EntryPoint. - @Inject 标识的变量不能使用
private
. - 如果使用了
@AndroidEntryPoint
, 其依赖的类也需要添加此注解, 比如Fragment
, 那么其HostActivity
也需要。
@Inject
: 当前变量需要被注入
「Hilt」会找其类声明,其构造函数如果被 @inject
,那么就调用构造函数返回.比如:
class AnalyticsAdapter @Inject constructor(
private val service: AnalyticsService
) { }
有点开发经验的同学就会说,我们平时用的三方库,比如 Retrofit
就没办法给构造函数添加@Inject
, 那该怎么办呢? ---> @Module
@Module
、@Provides
: 当前类通过@Provides
标注的方法提供实例
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {
@Provides
fun provideAnalyticsService(
// Potential dependencies of this type
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
@Qualifier
: 如果 module 中有可以提供多个接口的实例,可以通过不同的 @Qualifier 取。
比如下面的例子就表示,创建 okhttpclient 需要 @InterceptorBASIC对应的实例。比如下面的例子就表示,创建 okhttpclient 需要 @InterceptorBASIC 对应的Interceptor实例。
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class InterceptorBASIC
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class InterceptorHEADERS
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
fun provideLogInterceptorOkHttpClient(
@InterceptorHEADERS loggingInterceptor: Interceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
}
@Provides
@InterceptorHEADERS
fun provideLogInterceptorBASIC(): Interceptor {
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)
}
@Provides
@InterceptorBASIC
fun provideLogInterceptorHEADERS(): Interceptor {
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS)
}
}
@Binds
: 提供接口实例
interface AnalyticsService {
fun analyticsMethods()
}
// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
...
) : AnalyticsService { ... }
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
以上代码表示,AnalyticsModule
通过@Binds
标注bindAnalyticsService方法,表示当前其可提供接口 AnalyticsService 的具体实现 AnalyticsServiceImpl, 其中的
bindAnalyticsService方法的返回值告诉表示可提供的实例, 其参数表示提供的实例。
补充
Component hierarchy
@AndroidEntryPoint 会为每一个 Android Class 生成 component,component会接受依赖,而且具有传递性。
@ApplicationCotext、@ActivityContext:预制的 qualifers
在项目中,我们经常会用 context, 我们通过以上两个注解,就可以拿到 ApplicationCotext
和 ActivityContext
@InstallIn()
我们前面已经知道 Component 表示当前类需要被注入, 那Component 是怎么拿到实例的呢? 通过在 @module标注的类上使用 @InstallIn 告诉 hilt, module 提供的这些实例可以注入给哪些类使用。
如何选择作用域
通过这张图选择, 比如 singletonComponet 的 module 可以给箭头指向的所有 component 提供实例。 但是 viewComponet 就只有它自己能获得实例。
一些建议
- viewmodel 中不能持有 Activity Context. 因为 ViewModel 会在横竖屏切换时保存实例,从而导致内存泄漏