安卓依赖注入
什么是依赖注入
依赖注入(DI,Dependency Injection)是一种广泛的编程技术。把依赖(所需对象)传递给其它对象创建,好处是类的耦合更加松散,遵循依赖倒置的原则。
类获取所需对象
kotlin
class Engine {
fun start() {
println("engine start")
}
}
class Car {
private val engine: Engine = Engine()
fun start(){
engine.start()
}
}
Car对Engine强依赖,如果需要其它类型的Engine,需要增加一个新的Engine,必须对Car进行改动。
依赖注入获取所需对象
kotlin
interface Engine {
fun start()
}
class VEngine : Engine{
override fun start() {
println("VEngine start")
}
}
class WEngine : Engine{
override fun start() {
println("WEngine start")
}
}
class Car(private val engine: Engine) {
fun start(){
engine.start()
}
}
在构造函数中接收Engine对象作为参数,而不是初始化时构造自己的Engine对象,这就叫做依赖注入。
依赖注入还有很多其它的方式,如变量的get/set,就一一不介绍了。
安卓中手动实现依赖注入
手动实现依赖注入,就是依赖注入的原理。依赖注入框架会生成同样功能的样板代码。
假设有个登录场景,流程大概是这样:
LoginActivity->LoginViewModel->UserRepository
kotlin
class UserRepository(
private val localDataSource: UserLocalDataSource,
private val remoteDataSource: UserRemoteDataSource
)
class UserLocalDataSource()
class UserRemoteDataSource(
private val loginService: LoginRetrofitService
)
在需要的位置手动注入
在需要的地方创建依赖,缺点比较明显:
- 大量的样板代码
- 必须需要按照顺序声明依赖
- 复用困难。
kotlin
/**
* 在LoginActivity的onCreate函数里:
*/
//创建UserRemoteDataSource需要的依赖
val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
//创建Repository需要的依赖
val remoteDataSource = UserRemoteDataSource(retrofit)
val localDataSource = UserLocalDataSource()
//创建ViewModel需要的依赖
val userRepository = UserRepository(localDataSource, remoteDataSource)
//创建ViewModel
loginViewModel = LoginViewModel(userRepository)
使用容器管理依赖
如果想要复用对象,可以创建一个类来初始化所需的依赖。
kotlin
class AppContainer {
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com").build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
val userRepository = UserRepository(localDataSource, remoteDataSource)
}
如果需要在整个app中使用,可以放到application中:
kotlin
class MyApplication : Application(){
val appContainer = AppContainer()
}
/**
* 在LoginActivity的onCreate函数里:
*/
val appContainer = (application as MyApplication).appContainer
loginViewModel = LoginViewModel(appContainer.userRepository)
使用容器来管理依赖还是有样板代码,且需要手动为依赖项创建实例对象。
管理依赖项的声明周期
之前把UserRepository的生命周期放在了application,在app被关闭前永远不会被释放。如果数据非常大,会导致内存占用过高。
假如,只有在LoginActivity需要依赖,其它位置不需要依赖:
- AppContainer 内部需要新增一个LoginContainer,用来存放UserRepository。
- 在LoginActivity:onCreate时手动创建LoginContainer并放到AppContainer,onDestory时把AppContainer设置为null,主动释放引用。
根据生命周期来管理依赖项,这样时比较合理的。
Dagger实现依赖注入
什么是Dagger
Dagger是一个依赖注入的库,通过自动生成代码的方式,实现依赖注入。由于是在编译时生成的代码,性能会高于基于反射的方案。Dagger生成的代码和手动实现依赖注入生成的代码相似。
- 每次调用函数都重新创建对象
kotlin
//@Inject 注解会告诉Dagger,需要注入.
class UserRepository @Inject constructor(
private val localDataSource: UserLocalDataSource,
private val remoteDataSource: UserRemoteDataSource
){
init {
println("UserRepository Created")
}
}
class UserLocalDataSource @Inject constructor() {
init {
println("UserLocalDataSource Created")
}
}
class UserRemoteDataSource @Inject constructor(){
init {
println("UserRemoteDataSource Created")
}
}
//DaggerApplicationGraph.create() 会创建新对象
private val applicationGraph:ApplicationGraph = DaggerApplicationGraph.create()
//applicationGraph.repository() 会创建新对象
private val repository1 = applicationGraph.repository()
private val repository2 = applicationGraph.repository()
/**
输出如下:
UserLocalDataSource Created
UserRemoteDataSource Created
UserRepository Created
UserLocalDataSource Created
UserRemoteDataSource Created
UserRepository Created
*/
- 首次创建对象,之后全局复用这个单例对象.
kotlin
@Singleton
class UserRepository @Inject constructor(
private val localDataSource: UserLocalDataSource,
private val remoteDataSource: UserRemoteDataSource
){
init {
println("UserRepository Created")
}
}
class UserLocalDataSource @Inject constructor() {
init {
println("UserLocalDataSource Created")
}
}
class UserRemoteDataSource @Inject constructor(){
init {
println("UserRemoteDataSource Created")
}
}
/**
* @Component 注解,用于 interface
* Dagger会生成一个对应的类,以Dagger开头,ApplicationGraph就是DaggerApplicationGraph
* 调用函数会返回对应的对象, Dagger会自动添加依赖
* @Singleton 注解,用于标识为全局单例
*/
@Singleton
@Component
interface ApplicationGraph {
fun repository(): UserRepository
}
@Singleton
@Component
interface LoginGraph {
fun repository(): UserRepository
}
class One{
//DaggerApplicationGraph.create() 会创建新对象
private val applicationGraph:ApplicationGraph = DaggerApplicationGraph.create()
//applicationGraph.repository() 会创建新对象
private val repository1 = applicationGraph.repository()
private val repository2 = applicationGraph.repository()
init {
println("One Created")
}
}
class Two{
private val loginGraph:LoginGraph = DaggerLoginGraph.create()
private val repository1 = loginGraph.repository()
private val repository2 = loginGraph.repository()
init {
println("Two Created")
}
}
/**
One 和 Two 使用同一个对象,因为@Singleton注解是全局单例
输出如下:
UserLocalDataSource Created
UserRemoteDataSource Created
UserRepository Created
One Created
UserLocalDataSource Created
UserRemoteDataSource Created
UserRepository Created
Two Created
*/
在安卓中使用Dagger
- 对Activity中的字段进行注入
kotlin
/**
* 为了演示方便,这里没有继承ViewModel
*/
class LoginViewModel @Inject constructor(
private val userRepository: UserRepository
) {
init {
println("LoginViewModel Created")
}
}
/**
* Activity 是用安卓系统实例化的,所以无法被Dagger创建
* 初始化的代码需要放在onCreate方法中
* 使用手动调用inject的方式,对字段进行注入,需要被注入的字段必须有@Inject注解
*/
class LoginActivity : AppCompatActivity() {
@Inject lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
//调用inject,告诉Dagger,可以对当前对象的@Inject字段进行注入了
(applicationContext as MyApplication).applicationGraph.inject(this)
//调用完成,loginViewModel可以使用了
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
}
}
/**
* @Component 注解,用于 interface * Dagger会生成一个对应的类
* 调用函数会返回对应的对象, Dagger会自动添加依赖
* @Singleton 注解,用于标识为全局单例
* inject 调用函数手动注入带有@Inject注解的字段,函数名称是任意的,参数是需要注入的对象.
*/
@Singleton
@Component
interface ApplicationGraph {
fun repository(): UserRepository
fun inject(activity: LoginActivity)
}
class MyApplication : Application() {
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
}
- 主动告知如何提供实例
kotlin
//增加一个参数
class UserRemoteDataSource @Inject constructor(private val loginService: LoginService){
init {
println("UserRemoteDataSource Created")
}
fun login(){
loginService.login()
}
}
//LoginService 只能通过Builder.create()创建
interface LoginService {
private class LoginServiceImpl : LoginService{
init {
println("LoginServiceImpl Created")
}
}
fun login() = println("login")
class Builder{
fun create(): LoginService {
return LoginServiceImpl()
}
}
}
/**
* @DisableInstallInCheck 用于屏蔽绑定生命周期,这个是hilt的内容.
* @Module 是dagger的注解,用来告诉dagger可以提供实例对象.
* @Provides 用于@Module注解内,提供对应类型的实例对象,函数名任意.也可以添加@Singleton注解创建单例.
*/
@DisableInstallInCheck
@Module
class LoginModule {
@Provides
fun providerLoginService(): LoginService{
return LoginService.Builder().create()
}
}
/**
* @Component 注解,用于 interface * Dagger会生成一个对应的类,以Dagger开头,ApplicationGraph就是DaggerApplicationGraph
* modules 参数用来指定对象该如何提供,必须带有@Module注解
* 调用函数会返回对应的对象, Dagger会自动添加依赖
* @Singleton 注解,用于标识为全局单例
* inject 调用函数手动注入带有@Inject注解的字段,函数名称是任意的,参数是需要注入的对象.
*/
@Singleton
@Component(modules = [LoginModule::class])
interface ApplicationGraph {
fun repository(): UserRepository
fun inject(activity: LoginActivity)
}
- 子组件和作用域,限定作用域为生命周期
kotlin
class MyApplication : Application() {
val applicationGraph: ApplicationComponent = DaggerApplicationComponent.create()
}
@Singleton
@Component(modules = [LoginModule::class, Subcomponent::class])
interface ApplicationComponent {
fun loginComponent(): LoginComponent.Factory
}
@Scope
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope
@ActivityScope
class LoginViewModel @Inject constructor(
private val userRepository: UserRepository,
) {
init {
println("LoginViewModel Created")
}
fun login(){
userRepository.login()
}
}
@ActivityScope
@Subcomponent
interface LoginComponent {
@Subcomponent.Factory
interface Factory{
fun create(): LoginComponent
}
fun inject(activity: LoginActivity)
fun inject(fragment: LoginFragment)
fun repository(): UserRepository
}
@DisableInstallInCheck
@Module
class LoginModule {
@Singleton
@Provides fun providerLoginService(): LoginService{
return LoginService.Builder().create()
}
}
@ActivityScope
class UserRepository @Inject constructor(
private val localDataSource: UserLocalDataSource,
private val remoteDataSource: UserRemoteDataSource
){
init {
println("UserRepository Created")
}
fun login(){
remoteDataSource.login()
}
}
class UserRemoteDataSource @Inject constructor(
private val loginService: LoginService,
private val loginService2: LoginService,
){
init {
println("UserRemoteDataSource Created")
}
fun login(){
loginService.login()
}
}
class UserLocalDataSource @Inject constructor() {
init {
println("UserLocalDataSource Created")
}
}
class LoginActivity : AppCompatActivity() {
lateinit var loginComponent: LoginComponent
@Inject lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
loginComponent = (application as MyApplication).applicationGraph.loginComponent().create()
loginComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
findViewById<Button>(R.id.activity_login_bt_open).setOnClickListener {
startActivity(Intent(this, LoginActivity::class.java))
}
}
}
class LoginFragment : Fragment() {
@Inject
lateinit var loginViewModel: LoginViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
(activity as LoginActivity).loginComponent.inject(this)
val view = inflater.inflate(R.layout.fragment_login, container, false)
view.findViewById<Button>(R.id.fragment_login_bt_login).setOnClickListener {
loginViewModel.login()
}
return view
}
}
@ActivityScope
class LoginViewModel @Inject constructor(
private val userRepository: UserRepository,
) {
init {
println("LoginViewModel Created")
}
fun login(){
userRepository.login()
}
}
interface LoginService {
private class LoginServiceImpl : LoginService{
init {
println("LoginServiceImpl Created")
}
}
fun login() = println("login")
class Builder{
fun create(): LoginService {
return LoginServiceImpl()
}
}
}
@ActivityScope
@Subcomponent
interface LoginComponent {
@Subcomponent.Factory
interface Factory{
fun create(): LoginComponent
}
fun inject(activity: LoginActivity)
fun inject(fragment: LoginFragment)
fun repository(): UserRepository
}
通过限定作用域的方式,把依赖注入和生命周期绑定,Activity和Activity中的Fragment使用同一个ViewModel。
Dagger虽然可以实现精细的依赖注入,但是使用起来非常繁琐。
Hilt实现依赖注入
什么是Hilt
Hilt是基于Dagger构建的用于安卓的依赖注入库,简化在安卓上实现依赖注入。
把依赖项注入安卓类
Hilt可以很方便的注入到安卓类中
比如把ViewModel
kotlin
class UserRepository @Inject constructor(
private val localDataSource: UserLocalDataSource,
private val remoteDataSource: UserRemoteDataSource
){
init {
println("UserRepository Created")
}
fun login(){
remoteDataSource.login()
}
}
class UserLocalDataSource @Inject constructor() {
init {
println("UserLocalDataSource Created")
}
}
class UserRemoteDataSource @Inject constructor(){
init {
println("UserRemoteDataSource Created")
}
fun login(){
println("login")
}
}
class LoginViewModel @Inject constructor(
private val userRepository: UserRepository,
) {
init {
println("LoginViewModel Created")
}
fun login(){
userRepository.login()
}
}
/**
* @AndroidEntryPoint 注解,可以被Hilt注入
*/
@AndroidEntryPoint
class LoginActivity : AppCompatActivity() {
@Inject lateinit var viewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
findViewById<Button>(R.id.activity_login_bt_open).setOnClickListener {
startActivity(Intent(this, LoginActivity::class.java))
}
}
}
class LoginFragment : Fragment() {
lateinit var viewModel: LoginViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_login, container, false)
viewModel = (activity as LoginActivity).viewModel
view.findViewById<Button>(R.id.fragment_login_bt_login).setOnClickListener {
viewModel.login()
}
return view
}
}
/**
* @HiltAndroidApp 注解:
* 必须在Application 中添加, Hilt会生成一个类作为依赖的容器, 作为依赖注入的入口.
*/@HiltAndroidApp
class MyApplication : Application()
Hilt模块
模块的Bind
可以被直接构造的接口实现可以通过Bind注入
kotlin
class UserRemoteDataSource @Inject constructor(
private val analyticsService: AnalyticsService
){
init {
println("UserRemoteDataSource Created")
}
fun login(){
println("login")
analyticsService.logEvent("login")
}
}
interface AnalyticsService {
fun logEvent(eventName: String)
}
class AnalyticsServiceImpl @Inject constructor(
@ApplicationContext val context: Context
) : AnalyticsService {
override fun logEvent(eventName: String) {
println("Context: $context LogEvent: $eventName")
}
}
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsServiceModule {
@Binds
abstract fun bindAnalyticsService(analyticsService: AnalyticsServiceImpl): AnalyticsService
}
模块的Provider
无法被直接构造的接口实现可以通过Provider注入
kotlin
@Module
@InstallIn(ActivityComponent::class)
class LoginServiceModule {
@Provides
fun provideLoginService(): LoginService {
return LoginServiceImpl.Builder().build()
}
}
interface LoginService {
fun login()
}
class LoginServiceImpl private constructor(): LoginService {
class Builder {
fun build(): LoginService {
return LoginServiceImpl()
}
}
init {
println("LoginServiceImpl Created")
}
override fun login() {
println("login")
}
}
class UserRemoteDataSource @Inject constructor(
private val analyticsService: AnalyticsService,
private val loginService: LoginService
){
init {
println("UserRemoteDataSource Created")
}
fun login(){
loginService.login()
analyticsService.logEvent("login")
}
}
同一类型提供多个绑定
Provides如果不带限定标签,只会返回一种类型的实现。
可以通过限定标签来区分,返回对应的实现。
Hilt限定符默认提供了 @ApplicationContex 和 @ActivityContext,用来提供两种不同类型的Context
kotlin
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DebugLog
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ErrorLog
@Module
@InstallIn(SingletonComponent::class)
object LogServiceModule {
@DebugLog
@Provides
fun provideDebugLogger(): LogService {
return LogServiceDebugImpl()
}
@ErrorLog
@Provides
fun provideErrorLogger(): LogService {
return LogServiceErrorImpl()
}
}
@Module
@InstallIn(ActivityComponent::class)
class LoginServiceModule {
@Provides
fun provideLoginService(@DebugLog logService: LogService): LoginService {
return LoginServiceImpl.Builder().build(logService)
}
}
class LoginServiceImpl private constructor(val logService:LogService): LoginService {
class Builder {
fun build(logService:LogService): LoginService {
return LoginServiceImpl(logService)
}
}
init {
logService.log("LoginServiceImpl Created")
}
override fun login() {
logService.log("login")
}
}
class AnalyticsServiceImpl @Inject constructor(
@ApplicationContext val context: Context,
@ErrorLog val logService: LogService
) : AnalyticsService {
override fun logEvent(eventName: String) {
logService.log("Context: $context LogEvent: $eventName")
}
}
Hilt为安卓类生成的组件
组件生命周期和作用域
Hilt组件 | 创建时机 | 销毁时机 | 作用域 | 备注 |
---|---|---|---|---|
SingletonComponent | Application#onCreate() | Application被销毁 | @Singleton | 相当于是单例的 |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() | @ActivityScoped | 会随着生命周期注入 |
ActivityRetainedComponent | 首次Activity#onCreate() | 最后一次Activity#onDestroy() | @ActivityRetainedScoped | Fragment的ViewModel会随着Fragment回收,但ActivityRetainedComponent只会随着Activity回收,比ViewModel生命周期更长。 |
ViewModelComponent | ViewModel 已创建 | ViewModel 已销毁 | @ViewModelScoped | 和ViewModel的生命周期相同 |
ViewComponent | View#super() | View 已销毁 | @ViewScoped | 和View的生命周期相同 |
ViewWithFragmentComponent | View#super() | View的拥有者被销毁 | @ViewScoped带有 @WithFragmentBindings注解的View | 比如Fragment导航离开屏幕,Fragment还在,但View被销毁时仍然保留。 |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() | @FragmentScoped | 和Fragment的生命周期相同 |
ServiceComponent | Service#onCreate() | Service#onDestroy() | @ServiceScoped | 和Service的生命周期相同 |
组件默认绑定
可以使用Model安装到默认绑定,实现注入。比如上面提到的"同一类型提供多个绑定"
安卓组件 | 默认绑定 |
---|---|
SingletonComponent | Application |
ActivityRetainedComponent | Application |
ViewModelComponent | SavedStateHandle |
ActivityComponent | Application、Activity |
FragmentComponent | Application、Activity、Fragment |
ViewComponent | Application、Activity、View |
ViewWithFragmentComponent | Application、Activity、Fragment、View |
ServiceComponent | Application、Service |
在Hilt不支持的类中注入依赖项
使用@EntryPoint让任意接口可以被注入.
使用@EntryPointAccessors获取被注入的对象.
因为是SingletonComponent,所以要使用Application的Context 。如果是ActivityComponent就需要使用Activity的Context。
kotlin
class ExampleContentProvider : ContentProvider() {
@EntryPoint
@InstallIn(SingletonComponent::class)
interface ExampleContentProviderEntryPoint {
fun analyticsService(): AnalyticsService
}
fun doSomeThing(){
val appContext = context?.applicationContext ?: throw IllegalStateException()
val hiltEntryPoint = EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)
val analyticsService = hiltEntryPoint.analyticsService()
}
}