Android Hilt

参考文档
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也必须使用.

@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之间的数据共享, 具体使用办法看上面那一章.

猜你喜欢

转载自blog.csdn.net/weixin_43662090/article/details/107839517