安卓架构组件-依赖注入

安卓依赖注入

什么是依赖注入

依赖注入(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   ) 

在需要的位置手动注入

在需要的地方创建依赖,缺点比较明显:

  1. 大量的样板代码
  2. 必须需要按照顺序声明依赖
  3. 复用困难。
/** * 在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需要依赖,其它位置不需要依赖:

  1. AppContainer 内部需要新增一个LoginContainer,用来存放UserRepository。
  2. 在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() 	} }  

发表评论

评论已关闭。

相关文章