为什么要依赖注入?
假设:
class Test{
public Test() {
SubTest sub= new SubTest();
...
}
}
上例中,每次使用Test时都会在Constructor内调用new SubTest(),而Test须依赖SubTest才能顺利使用,我们无法单独使用Test,这样会产生几个问题:
- 可重复性:SubTest无法共享。
- 测试:无法单独测试。
- 可维护性:难以维护。
更好的设计:
class Test{
private SubTest sub;
public Test(SubTest sub) {
this.sub = sub;
}
}
这样就可以解决上面的3个问题
这样的设计称做依赖反转(IoC),即一个类若需要用到其他的类则应由外部取代,而不是在内部用新实例化,也是DI框架的核心概念。
Dagger2
Dagger2的起手比较难理解一点,新手需要有一点耐心
dagger2针对安卓出了专门的api,官方地址:https://dagger.dev/dev-guide/android
大概需要这么些类:
后面会一一说明
源码地址:https://github.com/robinfjb/Android_scaffold_Dagger
1.建立Module:
建立一个类并用@Module
注记,表示此模块内部的物件可以让其他类用注入的方式获得。
AppModule
@Module(includes = [ViewModelModule::class,NetworkModule::class])
class AppModule {
@Singleton
@Provides
fun provideContext(app: Application): Context {
return app
}
...
}
- 建立方法
provideContext
并加@Provides
标注,返回值为Context 表示在模块内放了Context
供其他类取用 -
@Singleton
是会处理成单例。 - 这样我们就在Module中添加了一个
Singleton
的Context
可以使用了,达到跟原本Application中实现一个单例对象一样的效果。 -
includes
表示可以包含其他Module
,其中NetworkModule
里都是网络相关对象,可查看源码,ViewModelModule
后续会说明
ActivityBuildersModule
此Module中指定哪些Activity/Fragment
要用Inject的方式取得注入对象
MainActivity为例:
@Module
abstract class ActivityBuildersModule {
@ContributesAndroidInjector(modules = [MainActivityModule::class])
abstract fun contributeMainActivity(): MainActivity
@ContributesAndroidInjector(modules = [NavTestActivityModule::class])
abstract fun contributeNavTestActivity(): NavTestActivity
}
@ContributesAndroidInjector
可以自动生成AndroidInjector.Factory
,具体参照https://dagger.dev/dev-guide/android
由于Activity中还有诸多Fragment,所以也需要fragment专属的module,拿MainActivityModule举例:
@Module
abstract class MainActivityModule {
@ContributesAndroidInjector
abstract fun contributeHomeFragment(): HomeFragment
@ContributesAndroidInjector
abstract fun contributeBindFragment(): BindFragment
@ContributesAndroidInjector
abstract fun contributeRoomFragment(): RoomFragment
}
Module的部分到这边就完成了。
2.建立Component:
加上@Component注记,必须要将用到的Module加入Component说明,如此Dagger便会自动产生程序代码让我们能注入那些Module中的项目。
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityBuildersModule::class
]
)
interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(surveyApp: SampleApp)
}
AndroidInjectionModule时dagger包提供的module,可以帮我们做inject
写好后需「Make Project」
3.在Application中初始化:
Make Project完成后Dagger会产生DaggerAppComponent
类,然后即可初始化Dagger:
class SampleApp : Application(), HasActivityInjector{
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector
override fun onCreate() {
super.onCreate()
AppInjectorHelper.inject(this)
}
override fun activityInjector() = dispatchingAndroidInjector
}
如果在Activity或Fragment中用注入的类,需要在Activity的onCreate
方法(在super.onCreate
之前),Fragment的onAttach
方法中调用AndroidInjection.inject(this)
为了减少代码的侵入性,我们用了AppInjectorHelper这个类:
object AppInjectorHelper {
fun inject(app: SampleApp) {
DaggerAppComponent.builder().application(app).build().inject(app)
app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
handleActivity(activity)
}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {}
override fun onActivityDestroyed(activity: Activity) {}
})
}
private fun handleActivity(activity: Activity) {
if (activity is Injectable || activity is HasSupportFragmentInjector) {
AndroidInjection.inject(activity)
}
if (activity is FragmentActivity) {
activity.supportFragmentManager.registerFragmentLifecycleCallbacks(
object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentAttached(fm: FragmentManager, f: Fragment, context: Context) {
super.onFragmentAttached(fm, f, context)
if (f is Injectable) {
AndroidSupportInjection.inject(f)
}
}
}, true)
}
}
}
interface Injectable {
}
用registerActivityLifecycleCallbacks
注册每一个Activity的生命周期回调,在onActivity
创建的时候调用handleActivity
声明空接口Injectable
Activity如果是Injectable
或者HasSupportFragmentInjector
,则会注入
同样的Fragment如果是Injectable
,则也会通过AndroidSupportInjection
注入
4.在Activity/Fragment中实现:
看下Activity的实现,以MainActivity为例:
class MainActivity : AppCompatActivity(), Injectable, HasSupportFragmentInjector {
@Inject
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector
override fun supportFragmentInjector(): AndroidInjector = fragmentDispatchingAndroidInjector
...
}
如果acitivity中有fragment需要用注入,则需要实现HasSupportFragmentInjector
对象
再看下fragment的实现:
class HomeFragment : Fragment() , Injectable {
@Inject
lateinit var factory: MyViewModelFactory
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
homeViewModel = ViewModelProviders.of(this, factory).get(HomeViewModel::class.java)
}
...
}
只要实现空接口,以便AppInjectorHelper类运行注入
factory对象就能初始化拿到provide的MyViewModelFactory
了,MyViewModelFactory
会在下面说明
5.在ViewModel中使用:
- 我们用到了ViewModle,由于ViewModle需要通过
ViewModelProviders
来实例化而不是直接调用构造函数,所以在DI的操作上会不太相同,我们会使用Multibindings的方式来封装ViewModelFactory
- 使用Multibindings产生一个
Map
告诉> ViewModelFactory
要建立哪个ViewModel,令Key为ViewModel的class,而Value就是ViewModel本身,例如Map
> - Provider表示对象并不在inject的时候就实例化,当使用
get()
方法时才实例化一个对象,每次get都会产生一个新的对象,因为ViewModel并非单例
1.先定义一个注解类ViewModelKey:
@Documented
@Target(
AnnotationTarget.FUNCTION
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass)
@MapKey
表示此方法的class文件问key
2.建立Module:
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel
}
@Binds
是provider的一种简化写法
@IntoMap
会产生一个Map
此处ViewModelKey就将HomeViewModel.class作为key,HomeViewModel对象作为value
3.建立ViewModelProvider.Factory
@Singleton
class MyViewModelFactory @Inject constructor(
private val creators: Map, @JvmSuppressWildcards Provider>
) : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
var creator: Provider? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("unknown model class " + modelClass)
}
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
creator为上述bindHomeViewModel
产生的对象,在create方法中,由class为key找到第一个对象(可能有多个),通过Provider
返回这个对象的实例,这样Factory就完成的viewmodel对象的实例化
注意:
这里的MyViewModelFactory
并未在module中声明,也没有实例化,那么HomeFragment
是如何拿到@Inject
对象的呢,此处则是Constructor Injection概念:
当我们的注入的Class很多时,Module就会就会变得臃肿,所以对自订的Class我们可以用构造函数@Inject的方式将其加入DI框架中并让Module保持简洁。
class MyViewModelFactory @Inject constructor()
将构造方法声明为@Inject
,这样HomeFragment
就可以直接获取到@Inject
对象了
这样viewmodel
的注入就结束了
5.在Repository中使用:
注意到viewmodel
中我们需要一个repository
对象,此对象也是注入进来的:
class HomeViewModel @Inject constructor(
private val repository: HomeRepository
) : ViewModel(){
}
也是用上述提到的Constructor Injection
方式:
class HomeRepository @Inject constructor(
private val apiLibService: ApiLibService
){
fun getWeather(): LiveData> = apiLibService.weather().asResource()
}
将构造方法注解为@Inject
ApiLibService
也是通过注入进来的,但ApiLibService
并未采用Constructor Injection
方式,而是在NetworkModule
这个module
中provide
那么,什么时候采用provide
,什么时候采用Constructor Injection
。个人理解:
将普通的类用Constructor Injection
来处理,只有像Retrofit这种外部无法获得构造函数才加入Module
中,以保持Module
的简洁。
以上就是Andriod的Dagger注入的全过程
源码放在此处:https://github.com/robinfjb/Android_scaffold_Dagger