安卓架构组件-依赖注入

安卓依赖注入

什么是依赖注入

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

在需要的位置手动注入

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

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

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