参考文档
https://developer.android.com/training/dependency-injection/hilt-android
https://medium.com/androiddevelopers/dependency-injection-on-android-with-hilt-67b6031e62d
https://www.zhihu.com/question/32108444
依赖注入
依赖是一个听起来有点唬人的概念. 其实很简单
本来我接受各种参数来构造一个对象,现在只接受一个参数——已经实例化的对象
//不使用依赖注入
class A(a:Int,b:Int){
val B = B(a,b)
}
//使用依赖注入
class A(val b:B)
所谓依赖, 其实就是成员变量. 之前成员变量在类的内部进行构造, 现在放到了外部. 这就是依赖注入
而Hilt, 就是Android基于Dragger开发的一套依赖注入的框架.
接入
项目根目录build.gradle添加如下代码
buildscript {
...
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}
在module下的build.gradle修改如下
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
然后这就配置好了. 如果gradle sync的话, 可能还会有一些错误, 提示gradle升级之类的, 按照提示进行操作就可以了.
注解
@HiltAndroidApp
在Application的上面必须使用这个注解. 表示开启依赖注入
@HiltAndroidApp
class ExampleApplication : Application() {
... }
@AndroidEntryPoint
表示这个类可以使用依赖注入项.
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
... }
目前AndroidEntryPoint可以在以下来中使用
- Application
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
其中, 如果Fragment使用, 那么包含Fragment的Activity必须也要用该注解
如果View使用, 对应的Fragment和Activity也必须使用.
![](/qrcode.jpg)
@Inject
该注解有两个作用
- 注解在构造函数中, 表示该类可以进行注入
- 注解在成员变量上, 表示该成员变量使用进行注入
说的有点绕哈. 直接看代码
class AnalyticsAdapter @Inject constructor(
) {
... }
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
...
}
那我们知道Activity都是有生命周期的, 那@Inject的成员变量是在哪一步被注入呢?
这个要注入的Android类有关系. 看下面
组件
对于每个可以注入的Android类, 都有一个关联的Hilt组件, 每个组件负责将其绑定注入相应的Android类
那对应关系如下
- Application 对应ApplicationComponent组件
- ViewModel对应ActivityRetainedComponent组件(这一步暂时还不是很懂)
- Activity对应ActivityComponent组件
- Fragment对应FragmentComponent组件
- View对应ViewComponent组件
- 带@WithFragmentBindings的View对应ViewWithFragmentComponent组件(没有看懂, 没试过)
- Service对应ServiceComponent组件
注意: Hilt不会为广播接收器生成组件, 因为Hilt直接从ApplicationComponent注入广播接收器(没有看懂)
组件的生命周期
各个组件都有自己的生命周期, 在创建时机中自动创建注入, 在销毁时机中销毁生成的组件实例
组件 | 创建时机 | 销毁时机 |
---|---|---|
ApplicationComponent | Application#onCreate | Application#onDestory |
ActivityRetainedComponent | Activity#onCreate | Activity#onDestory |
ActivityComponent | Activity#onCreate | Activity#onDestory |
FragmentComponent | Fragment#onAttach | Fragment#onDestory |
ViewComponent | View#super | 视图销毁时 |
ViewWithFragmentComponent | View#super | 视图销毁时 |
ServiceComponent | Server#onCreate | Service#onDestory |
注意, ActivityRetainedComponent在配置更改后存在, 他在第一次onCreate的时候创建, 在最后一次onDestroy中销毁, 这和ViewModel的特性保持一致
组件的作用域
怎么理解呢? 默认情况下, 每次请求绑定注入的时候, 都会生成一个新的实例. 但是有些情况不需要, 比如说单例模式. 如下:
@Singleton
class DefaultAccountService @Inject constructor(@ApplicationContext context: Context) : BaseAccountService(context) {
}
我们希望DefaultAccountService不要每次都生成一个新的实例, 那么就用@Singleton注解, 标明这是一个单例. 只会执行一次
更详细的信息如下
组件 | 作用域 |
---|---|
ApplicationComponent | @Singleton |
ActivityRetainedComponent | @ActivityRetainedScope |
ActivityComponent | @ActivityScoped |
FragmentComponent | @FragmentScoped |
ViewComponent | @ViewScoped |
ViewWithFragmentComponent | @ViewSocped |
ServiceComponent | @ServiceScoped |
例如限定作用域为ActivityScoped, 那么在整个Activity的生命周期内, 无论Activity还是Fragment还是View都会提供统一实例
@ViewModelInject
用来注解ViewModel.
@ActivityRetainedScoped
class ZdmViewModel @ViewModelInject constructor(private val adapter:AnalyticsAdapter,
@Assisted private val state:SavedStateHandle) : BaseModel(), LifecycleObserver {
}
@Module @InstallIn @Provides
对于第三方库, 我们又不能改他的源码, 难道不能用Hilt了吗? 肯定不是
如下:
@Module
@InstallIn(ApplicationComponent::class)
object AppModule {
@Singleton
@Provides
fun gson(): GsonManager {
return GsonManager.instance()
}
}
这里注意一点, 可以看到@InstallIn(ApplicationComponent::class)和@Singleton是一一对应的.
对应关系可以看上面
这里我想放一个大招
其实不仅仅对于三方库. 我们经常头疼Activity Fragment View的数据共享问题, 虽然ViewModel和LiveData的出现帮我们解决了一部分, 但是感觉Activity和View之间依然不顺畅. 因为经常一个View要对应多个Activity.
Hilt的到来解决了这个问题
我们可以针对一个业务逻辑单独拆分出一个ViewMode(而不是简单的一个Activity或者一个Fragment对应一个ViewModel), 然后在Activity和Fragment, View中使用同样的ViewModel即可.
示例:
@ActivityRetainedScoped
class SearchModel @Inject constructor() : BaseModel() {
}
@AndroidEntryPoint
class SearchActivity : ToolbarActivity() {
...
@Inject
lateinit var mModel: SearchModel
}
@AndroidEntryPoint
class SearchBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) {
@Inject
lateinit var mModel :SearchModel
}
如果觉得ViewModel"太大", 也可以直接更新一个LiveData, 除了上述的修改, 增加如下代码即可
@Module
@InstallIn(ActivityComponent::class)
object AppModuleV2{
@Provides
@ActivityScoped
fun provides(): MutableLiveData<TempUser> {
return MutableLiveData<TempUser>()
}
}
@Binds
Binds的作用是将接口与实现类绑定起来,
@InstallIn(ApplicationComponent::class)
@Module
abstract class AppBindModule {
@Binds
abstract fun bindAccountService(defaultAccountService: DefaultAccountService): AccountService
}
@ApplicationContext @ActivityContext
这次两个阈值的注解, 通过名称就能看出来, 不详细解释了
@Singleton
class DefaultConfigService @Inject constructor(@ApplicationContext context: Context) : BaseConfigService(context) {
}
@EntryPoint
从注解的名称可以看出来, 这个和@AndroidEntryPoint类似, 只不过@AndroidEntryPoint只提供了系统默认的几个类的支持, 如果想让自己实现的类中也可以实现注解, 可以用@EntryPoint, 只不过要稍微麻烦点, 如下
class ServicesManager private constructor(context: Context) {
...
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface AccountServiceEntryPoint {
fun accountService(): AccountService
}
companion object {
const val ACCOUNT_SERVICE = "account"
const val STATISTICS_SERVICE = "statistics"
const val CONFIG_SERVICE = "config"
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
val manager = ServicesManager(LibApplication.instance())
//如下使用
val accountServiceEntryPoint = EntryPointAccessors.fromApplication(LibApplication.instance(), AccountServiceEntryPoint::class.java)
manager.register(ACCOUNT_SERVICE, accountServiceEntryPoint.accountService())
manager
}
}
Hilt的使用场景
此部分copy 扔物线的观点. 扔物线真牛逼.
1. 工具类
毫无疑问, 工具类最适合不过了
2. 数据类
用于Activity Fragment View之间的数据共享, 具体使用办法看上面那一章.