安卓依赖注入
什么是依赖注入
依赖注入(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()
	}
}