安卓依赖注入
什么是依赖注入
依赖注入(DI,Dependency Injection)是一种广泛的编程技术。把依赖(所需对象)传递给其它对象创建,好处是类的耦合更加松散,遵循依赖倒置的原则。
类获取所需对象
class Engine { fun start() { println("engine start") } } class Car { private val engine: Engine = Engine() fun start(){ engine.start() } }
Car对Engine强依赖,如果需要其它类型的Engine,需要增加一个新的Engine,必须对Car进行改动。
依赖注入获取所需对象
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
class UserRepository( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) class UserLocalDataSource() class UserRemoteDataSource( private val loginService: LoginRetrofitService )
在需要的位置手动注入
在需要的地方创建依赖,缺点比较明显:
- 大量的样板代码
- 必须需要按照顺序声明依赖
- 复用困难。
/** * 在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)
使用容器管理依赖
如果想要复用对象,可以创建一个类来初始化所需的依赖。
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中:
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生成的代码和手动实现依赖注入生成的代码相似。
- 每次调用函数都重新创建对象
//@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 */
- 首次创建对象,之后全局复用这个单例对象.
@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中的字段进行注入
/** * 为了演示方便,这里没有继承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() }
- 主动告知如何提供实例
//增加一个参数 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) }
- 子组件和作用域,限定作用域为生命周期
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
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注入
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注入
@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
@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。
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() } }